Skip to main content

Card Checkout

This guide walks through the complete session-based card checkout flow: create a payment session on your server, render the hosted card fields on your frontend, and verify the result on your server.

Flow overview​

  1. Server — start a payment session
  2. Server → Client — pass only session.id to the frontend
  3. Client — render the checkout form and let the customer submit
  4. Server — verify the transaction and act on the status

Step 1: Start a session (server)​

import AccruPay, { TRANSACTION_PROVIDER, CURRENCY, COUNTRY_ISO_2 } from '@accrupay/node';

const sdk = new AccruPay({
apiSecret: process.env.ACCRUPAY_API_SECRET!,
});

export async function startPaymentSession(
customerCode: string,
transactionCode: string,
) {
const session = await sdk.transactions.clientSessions.payments.start({
merchantTransactionProviderId: process.env.ACCRUPAY_PROVIDER_ID,
data: {
merchantInternalTransactionCode: transactionCode,
amount: 10000n, // $100.00
currency: CURRENCY.USD,
storePaymentMethod: false,
customer: {
merchantInternalCustomerCode: customerCode,
},
billing: {
billingFirstName: 'Jane',
billingLastName: 'Smith',
billingEmail: 'jane@example.com',
billingAddressCountry: COUNTRY_ISO_2.US,
billingPhone: '+15551234567',
billingAddressState: 'CA',
billingAddressCity: 'San Francisco',
billingAddressLine1: '123 Market St',
billingAddressPostalCode: '94105',
},
},
});

return session;
}
BigInt amounts

AccruPay amounts are bigint in the smallest currency unit (cents for USD). 10000n = $100.00.


Step 2: Send session.id to the frontend​

Only send the session id to the client — never expose the full session object or your API secret.

// Example: Express route
app.post('/api/checkout/start', async (req, res) => {
const session = await startPaymentSession(
req.user.id,
`txn_${Date.now()}`,
);

res.json({ sessionId: session.id });
});

Step 3: Render the checkout form (React)​

import {
AccruPay,
CardholderName,
CardNumber,
CardExpiry,
CardCVC,
SubmitButton,
} from '@accrupay/react';

function CheckoutForm({
sessionId,
onSuccess,
}: {
sessionId: string;
onSuccess: (transactionCode: string) => void;
}) {
return (
<AccruPay
merchantPublicId={process.env.NEXT_PUBLIC_ACCRUPAY_MERCHANT_ID!}
transactionSessionId={sessionId}
>
{/* Standard input — accepts all HTML input attributes */}
<CardholderName
placeholder="Full name on card"
className="checkout-input"
/>

{/* Embedded secure fields — only className and style are supported */}
<CardNumber className="checkout-input" />
<CardExpiry className="checkout-input" />
<CardCVC className="checkout-input" />

<SubmitButton
className="checkout-btn"
onSuccess={(result) => onSuccess(result.merchantInternalTransactionCode)}
onError={(err) => console.error('Payment failed', err)}
>
Pay $100.00
</SubmitButton>
</AccruPay>
);
}
note

CardNumber, CardExpiry, and CardCVC are embedded secure iframes. Do not attach onChange or onBlur handlers to them — card data never touches your JavaScript context. CardholderName is a standard text input and accepts all standard HTML input attributes.


Step 4: Verify the payment (server)​

Call verify() after the customer submits. The onSuccess callback is a convenience signal only — always verify server-side.

import { TRANSACTION_STATUS } from '@accrupay/node';

export async function verifyPayment(merchantInternalTransactionCode: string) {
const transaction =
await sdk.transactions.clientSessions.payments.verify({
merchantInternalTransactionCode,
});

switch (transaction.status) {
case TRANSACTION_STATUS.SUCCEEDED:
// Fulfill the order
await fulfillOrder(transaction);
break;

case TRANSACTION_STATUS.PENDING:
case TRANSACTION_STATUS.STARTED:
// Payment is still processing — poll or wait for a webhook
await markOrderPending(transaction);
break;

case TRANSACTION_STATUS.DECLINED:
// Card was declined — prompt the customer to use a different card
throw new Error('Card declined');

case TRANSACTION_STATUS.FAILED:
case TRANSACTION_STATUS.ERROR:
// Technical failure — safe to retry with a new session
throw new Error('Payment failed — please try again');

case TRANSACTION_STATUS.CANCELED:
throw new Error('Payment was canceled');

case TRANSACTION_STATUS.EXPIRED:
// Session timed out before the customer completed checkout
throw new Error('Checkout session expired');

default:
throw new Error(`Unexpected status: ${transaction.status}`);
}

return transaction;
}
Server-side verification is required

The onSuccess callback in the React SDK confirms the form was submitted — it does not confirm the payment succeeded. verify() is the authoritative source of truth. Always verify on your server before fulfilling any order.


Status reference​

StatusMeaningAction
SUCCEEDEDPayment capturedFulfill the order
PENDINGProcessing, final status pendingWait / poll / webhook
STARTEDSession opened, not yet submittedSame as PENDING
DECLINEDCard declined by issuerPrompt for a new card
FAILEDTerminal failureRetry with a new session
ERRORTechnical errorRetry with a new session
CANCELEDExplicitly canceledAllow customer to restart
EXPIREDSession timed outStart a new session
UNKNOWNStatus could not be determinedContact support