@quickpay/react
React components and hooks for integrating YardPay payments into React applications.
Try it live
Plug in your sandbox keys and run a real payment against the components below.
Installation
npm install @quickpay/react @quickpay/jsPeer dependency
@quickpay/react requires @quickpay/js as a peer dependency. Both must be installed.
QuickPayProvider
Wrap your application (or payment page) with QuickPayProvider to initialize the SDK.
import { QuickPayProvider } from '@quickpay/react';
function App() {
return (
<QuickPayProvider publishableKey="pk_live_your_publishable_key">
<YourApp />
</QuickPayProvider>
);
}ElementsProvider
The ElementsProvider binds a checkout session to the payment elements. Pass the clientSecret from your server-created checkout session.
import { ElementsProvider } from '@quickpay/react';
function CheckoutPage({ clientSecret }: { clientSecret: string }) {
return (
<ElementsProvider clientSecret={clientSecret}>
<CheckoutForm />
</ElementsProvider>
);
}Payment options: card, wallet QR and more
A session can offer several payment options: a card (MPGS), a wallet QR paid from a Jam-Dex / JN Pay wallet, account balance, and so on. <PaymentOptions /> lists them as radio buttons and mounts the card fields only while a card option is selected. For a wallet QR it shows a short note instead; confirming then opens a QR for the payer to scan.
The selection is shared through the ElementsProvider, so useConfirmPayment() sends the chosen option without being told, and reads the card only when that option takes one. The component is unstyled: pass className / classNames, or target its data-quickpay-* attributes.
import { PaymentOptions, useConfirmPayment, usePaymentOptions } from '@quickpay/react';
function CheckoutForm() {
const { confirmPayment, loading } = useConfirmPayment();
const { isWalletQr } = usePaymentOptions(); // same selection as <PaymentOptions />
const [message, setMessage] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const { paymentResult, error } = await confirmPayment({ returnUrl: window.location.href });
if (error) {
setMessage(error.message); // e.g. code 'card_element_missing', 'qr_timeout'
} else if (paymentResult?.qrWithdrawn) {
setMessage('Choose another way to pay.'); // payer left the QR via "Use a different payment method"
} else if (paymentResult?.status === 'completed') {
// Paid
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentOptions
classNames={{ option: 'option', optionSelected: 'option--selected', fee: 'option__fee' }}
onCardChange={(event) => event.error && setMessage(event.error.message)}
/>
{message && <p role="alert">{message}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Processing...' : isWalletQr ? 'Show QR code' : 'Pay'}
</button>
</form>
);
}Building your own picker? usePaymentOptions() returns { items, loading, error, selected, select, requiresCard, isWalletQr }. It starts on the merchant's primary option, else the first card option, else the first one. Render <CardElement /> only when requiresCard is true; the helpers requiresCardEntry(option) and isWalletQr(option) are exported too. Card methods are MPGS and CreditCard; everything else, WalletQr included, needs no card.
import { CardElement, usePaymentOptions } from '@quickpay/react';
function MyPicker() {
const { items, selected, select, requiresCard } = usePaymentOptions();
return (
<>
{items.map((option) => (
<button key={option.id} type="button" aria-pressed={selected?.id === option.id} onClick={() => select(option)}>
{option.displayName}
</button>
))}
{requiresCard && <CardElement />}
</>
);
}PaymentElement
The card fields, in a PCI-isolated iframe. It does not choose between payment methods: use <PaymentOptions /> above when the session may offer more than a card.
import { PaymentElement } from '@quickpay/react';
function CheckoutForm() {
return (
<form>
<PaymentElement
layout="tabs"
defaultValues={{
billingDetails: { name: 'Jane Doe', email: '[email protected]' },
}}
onChange={(event) => {
// event.complete, event.empty, event.error
}}
/>
<button type="submit">Pay</button>
</form>
);
}CardElement
A standalone card input component for when you only need card payments.
import { CardElement } from '@quickpay/react';
function CardForm() {
return (
<CardElement
style={{
base: { fontSize: '16px', color: '#1a1a1a' },
invalid: { color: '#e53e3e' },
}}
hidePostalCode={false}
onChange={(event) => {
if (event.error) {
setError(event.error.message);
}
}}
/>
);
}ClickToPayElement
Renders the Click to Pay (SRC) button for card networks that support it. Automatically handles enrollment lookup and authentication.
import { ClickToPayElement } from '@quickpay/react';
function ClickToPaySection() {
return (
<ClickToPayElement
onReady={() => setLoading(false)}
onAuthenticated={(result) => {
// result.token — tokenized card
// result.cardBrand, result.lastFour
}}
onError={(error) => setError(error.message)}
/>
);
}useQuickPay() Hook
Access the YardPay instance from any component within QuickPayProvider.
import { useQuickPay } from '@quickpay/react';
function CheckoutForm() {
const qp = useQuickPay();
const handleRedirect = async () => {
await qp.redirectToCheckout({ sessionId: 'cs_live_abc123_secret_xyz789' });
};
return <button onClick={handleRedirect}>Pay with Hosted Checkout</button>;
}useElements() Hook
Access the Elements instance from any component within ElementsProvider.
import { useElements } from '@quickpay/react';
function FormActions() {
const elements = useElements();
const handleReset = () => {
// Clear all element inputs
elements?.getElement('payment')?.clear();
};
return <button onClick={handleReset}>Reset</button>;
}useConfirmPayment() Hook
A convenience hook that wraps qp.confirmPayment() with a loading flag. It sends the option selected in <PaymentOptions /> / usePaymentOptions() unless you pass a paymentOptionId, and tokenizes the card only for a card option.
import { PaymentOptions, useConfirmPayment } from '@quickpay/react';
function CheckoutForm() {
const { confirmPayment, loading } = useConfirmPayment();
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const { paymentResult, error } = await confirmPayment({
returnUrl: 'https://yoursite.com/order/complete',
// paymentOptionId: 42, // optional; defaults to the selected option
});
if (error) {
setError(error.message);
} else if (paymentResult?.status === 'completed') {
// Payment succeeded without redirect
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentOptions />
{error && <p className="text-red-600">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Processing...' : 'Pay'}
</button>
</form>
);
}Complete Example
import {
QuickPayProvider,
ElementsProvider,
PaymentOptions,
useConfirmPayment,
} from '@quickpay/react';
function App() {
const [clientSecret, setClientSecret] = useState<string | null>(null);
useEffect(() => {
fetch('/api/create-checkout-session', { method: 'POST' })
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
if (!clientSecret) return <div>Loading...</div>;
return (
<QuickPayProvider publishableKey="pk_live_your_publishable_key">
<ElementsProvider clientSecret={clientSecret}>
<CheckoutForm />
</ElementsProvider>
</QuickPayProvider>
);
}
function CheckoutForm() {
const { confirmPayment, loading } = useConfirmPayment();
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await confirmPayment({ returnUrl: window.location.origin + '/complete' });
if (result.error) setError(result.error.message);
else if (result.paymentResult?.qrWithdrawn) setError('Choose another way to pay.');
};
return (
<form onSubmit={handleSubmit}>
{/* Card fields for card options; a QR to scan for Jam-Dex / JN Pay */}
<PaymentOptions />
{error && <p style={{ color: 'red' }}>{error}</p>}
<button disabled={loading}>{loading ? 'Processing...' : 'Pay'}</button>
</form>
);
}