@droplinked_inc/payment-intent-react
v0.3.0
Published
React Stripe-Elements wrapper for @droplinked_inc/payment-intent. Port of the <DroplinkedPaymentIntent> client surface from [email protected]. Pairs with the server-side @droplinked_inc/payment-intent state machine.
Readme
@droplinked_inc/payment-intent-react
React Stripe-Elements wrapper for @droplinked_inc/payment-intent. Drop-in
replacement for the <DroplinkedPaymentIntent> component that the legacy
[email protected] package shipped.
Pairs with the server-side state machine in
@droplinked_inc/payment-intent.
Why a separate package
The server package (@droplinked_inc/payment-intent) is intentionally
Node-only — state machine, idempotency store, zod schemas. Bundling React
into the same package would force every server consumer to pull in
react, react-dom, @stripe/stripe-js, etc.
Splitting the React surface into its own package keeps the dependency
graph honest: server callers depend on @droplinked_inc/payment-intent,
storefront callers depend on @droplinked_inc/payment-intent-react.
Install
pnpm add @droplinked_inc/payment-intent-react @stripe/react-stripe-js @stripe/stripe-jsPeer deps: react >=18, react-dom >=18, @stripe/react-stripe-js >=2,
@stripe/stripe-js >=2.
Usage
Mode A — clientSecret (Next-ShopFront pattern)
import { DroplinkedPaymentIntent } from '@droplinked_inc/payment-intent-react';
<DroplinkedPaymentIntent
clientSecret={stripe.client_secret}
type="stripe"
isTestnet={process.env.NODE_ENV !== 'production'}
return_url={`${window.location.origin}/orders/${orderId}`}
publishableKey={process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
onSuccess={(pi) => toast.success(`Payment ${pi.status}`)}
onError={(err) => toast.error('Something went wrong')}
onCancel={() => toast.info('Payment cancelled')}
commonStyle={{
backgroundBody: '#fff',
colorContainer: '#fff',
textColorLabel: '#000',
colorInput: '#fff',
textColorInput: '#000',
colorBorderInput: '#F2F2F2',
borderRadius: '8px',
cancelButton: { backgroundColor: '#F2F2F2', borderRadius: '8px', textColor: '#000' },
submitButton: { backgroundColor: '#000', borderRadius: '8px', textColor: '#fff' },
verticalPadding: '1rem',
theme: 'light',
}}
/>Mode B — orderId (droplinked-checkout pattern)
The caller supplies a mintClientSecret(orderId) async callback. This
keeps the API-base-URL out of the component:
<DroplinkedPaymentIntent
type="STRIPE"
orderId={orderId}
isTestnet={appDevelopment}
publishableKey={process.env.STRIPE_PUBLISHABLE_KEY}
mintClientSecret={async (id) => {
const res = await fetch(`/api/orders/${id}/intent`, { method: 'POST' });
const { client_secret } = await res.json();
return client_secret;
}}
onSuccess={() => navigate(`/success-payment/${orderId}`, { replace: true })}
onError={(err) => toast.error((err as Error)?.message ?? 'Payment failed')}
onCancel={() => {}}
commonStyle={{ cancelButton: { display: 'none' }, submitButton: { width: '100%' } }}
/>Migration from [email protected]
| Legacy | Rebuild |
| ---------------------------------------------- | ------------------------------------------------ |
| import { DroplinkedPaymentIntent } from 'droplinked-payment-intent' | import { DroplinkedPaymentIntent } from '@droplinked_inc/payment-intent-react' |
| Implicit Stripe publishable key | Pass publishableKey prop or set NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
| onSuccess: () => void | onSuccess: (pi: StripePaymentIntentLike) => void (legacy ()=>void still accepted) |
| onError: () => void | onError: (err: unknown) => void (legacy ()=>void still accepted) |
| orderId mode w/ implicit API base | orderId mode + explicit mintClientSecret callback |
Security notes
- The package never embeds a Stripe publishable key. The caller supplies
it explicitly (
publishableKey) or via env var. confirmPaymentis invoked withredirect: 'if_required'so we surface paymentIntent status to the consumer when no redirect is necessary. The consumer'sreturn_urlis still honoured when a 3DS redirect is required.- No external network calls beyond Stripe's own
loadStriperesolution andconfirmPayment. No telemetry. No phone-home.
Dual ESM / CJS output
The package emits both ESM (dist/esm/) and CJS (dist/cjs/) outputs
so that jest+babel consumers that don't have full ESM support can still
require it from CommonJS. The exports field routes resolvers to the
right entry automatically.
