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