React SDK
The official bindings for React 18/19. The Provider + hooks pattern lets you declaratively handle everything from SDK loading to popup checkout and embedded widgets.
Compatibility
| Item | Supported versions |
|---|---|
| React | 18.x · 19.x (peer dependency) |
| TypeScript | 5.0+ |
| Module formats | ESM · CJS |
Installation
pnpm add @nexus-cross/cross-pay-react
Prerequisites
In every example, checkoutId / idempotencyKey are values your merchant backend obtains by calling POST /v1/payments and passes to the client. The React app never holds MERCHANT_API_KEY.
Copy and start
import { CrossPayProvider, CrossPaySDKError, usePayment } from '@nexus-cross/cross-pay-react';
export default function App() {
return (
<CrossPayProvider theme="light" locale="en">
<CheckoutButton />
</CrossPayProvider>
);
}
function CheckoutButton() {
const { requestPayment, isLoading } = usePayment();
async function handlePay() {
try {
const result = await requestPayment({
checkoutId: 'cs_issued_checkout_id',
idempotencyKey: 'ik_issued_idempotency_key',
embeddedWallet: true,
});
console.log(result.paymentId, result.status);
} catch (error) {
if (error instanceof CrossPaySDKError && error.code === 'USER_REJECTED') {
return;
}
throw error;
}
}
return (
<button type="button" onClick={handlePay} disabled={isLoading}>
{isLoading ? 'Loading SDK...' : 'Pay'}
</button>
);
}
Provider
CrossPayProvider accepts SDK loader options and default UI options.
<CrossPayProvider
src="https://js.crosspay.ai/pay-js/latest/cross-pay.js"
integrity="sha384-abc123..."
theme="auto"
locale="en"
onError={(error) => console.error(error)}
>
<Checkout />
</CrossPayProvider>
| prop | Description |
|---|---|
src | Override the cross-pay.js URL to load |
integrity | Enforce SRI verification on pinned-URL deployments |
theme | Default Provider theme. Injected when the hook call omits it |
locale | Default Provider locale. Injected when the hook call omits it |
onError | Handle SDK load failure via callback. When set, the Provider does not re-throw the error |
If onError is omitted, a load failure throws an Error on the next render. In that mode, wrap with a React Error Boundary.
Popup checkout
function Checkout() {
const { requestPayment, isLoading } = usePayment();
async function handlePay() {
const result = await requestPayment({
checkoutId: 'cs_abc123',
idempotencyKey: 'ik_xyz',
successUrl: 'https://shop.example.com/pay/success',
failUrl: 'https://shop.example.com/pay/fail',
embeddedWallet: false,
});
console.log(result.paymentId, result.status);
}
return <button onClick={handlePay} disabled={isLoading}>Pay</button>;
}
embeddedWallet is a per-checkout option, not a Provider prop. It defaults to true.
Widget mode
import { usePaymentWidget, CheckoutStatus } from '@nexus-cross/cross-pay-react';
import { useEffect, useState } from 'react';
function WidgetCheckout({ checkoutId }: { checkoutId: string }) {
const [embeddedWallet, setEmbeddedWallet] = useState(true);
const { widgets, status, paymentMethodWidget, renderPaymentMethods } = usePaymentWidget();
useEffect(() => {
if (!checkoutId) return;
renderPaymentMethods('#payment-methods', {
checkoutId,
idempotencyKey: 'ik_xyz',
embeddedWallet,
});
}, [checkoutId, embeddedWallet, renderPaymentMethods]);
useEffect(() => {
paymentMethodWidget?.update({ embeddedWallet });
}, [embeddedWallet, paymentMethodWidget]);
async function handlePay() {
const result = await widgets?.submit({ idempotencyKey: 'ik_xyz' });
console.log(result);
}
return (
<>
<label>
<input
type="checkbox"
checked={embeddedWallet}
onChange={(event) => setEmbeddedWallet(event.currentTarget.checked)}
/>
Show embedded wallet
</label>
<div id="payment-methods" />
<button onClick={handlePay} disabled={status !== CheckoutStatus.READY}>
Pay
</button>
</>
);
}
usePaymentWidget replays the last render parameters when the widget instance is recreated. To start a new payment after a terminal state, always re-render with a new checkoutId/idempotencyKey.
Surfacing errors
import { CrossPayProvider, useCrossPayError } from '@nexus-cross/cross-pay-react';
function App() {
return (
<CrossPayProvider onError={(error) => console.error(error)}>
<Checkout />
</CrossPayProvider>
);
}
function Checkout() {
const error = useCrossPayError();
if (error) return <div>SDK load failed: {error.message}</div>;
return <button type="button">Pay</button>;
}
Hooks reference
| Hook | Return type | Description |
|---|---|---|
useCrossPay() | CrossPayInstance | null | Direct access to the low-level instance |
useCrossPayLoading() | boolean | Whether the script is currently loading |
useCrossPayError() | Error | null | SDK load failure error (used with onError mode) |
usePayment() | { requestPayment, isLoading } | Convenience hook for popup checkout |
usePaymentWidget(options?) | { widgets, status, paymentMethodWidget, renderPaymentMethods } | Widget lifecycle management |
Re-exported API
@nexus-cross/cross-pay-react re-exports the SDK API needed for React integration alongside key shared contracts.
- Values:
loadCrossPay,clearCrossPay,CrossPaySDKError,ERROR_CODES - Hooks/Provider:
CrossPayProvider,useCrossPay,useCrossPayError,useCrossPayLoading,usePayment,usePaymentWidget - Key types:
Amount,CheckoutParams,CheckoutPaymentResult,CrossPayAppearance,CrossPayError,CrossPayInstance,CrossPayLocale,CrossPayTheme,ErrorCode,ErrorType,RenderPaymentMethodsParams,WidgetHandle,WidgetsInstance,WidgetsOptions - Enums/constants:
CheckoutStatus,Currency,PaymentType,ProductCategory,UpdateReason