Billing
Saved cards
Request buyer consent to save a card at checkout, then charge that card later without the buyer present. Card entry stays on MeridaPay's hosted fields, so saving a card does not expand your PCI scope.
How it works
When you mint a checkout with save_payment_method: true, the buyer sees a
consent notice on the checkout page before they pay:
"By completing this payment, you agree to save your card for future payments from this merchant."
On success, MeridaPay stores a token bound to your organization. The raw
card number never touches your server. Once the checkout is paid, read the
refs you need for future charges from GET /checkouts/{id}.
Mint a save-card checkout
Pass save_payment_method: true in the body of POST /checkouts. Everything
else works the same as a normal checkout.
// Your backend
const res = await fetch("https://api.meridapay.com/checkouts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MERIDA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
product_id: "<product-id>",
customer_email: "[email protected]",
success_url: "https://app.example.com/thanks",
save_payment_method: true,
}),
})
const { url } = await res.json()
return Response.redirect(url, 303)
With the TypeScript SDK:
const { data } = await merida.POST("/checkouts", {
body: {
product_id: "<product-id>",
customer_email: "[email protected]",
success_url: "https://app.example.com/thanks",
save_payment_method: true,
},
headers: { "idempotency-key": crypto.randomUUID() },
})
return Response.redirect(data.url, 303)
Read the saved card refs
After the buyer pays, fetch the checkout to get the refs you need to charge
the card later. Both fields are null on an unpaid checkout and on any
checkout where save_payment_method was not set.
curl https://api.meridapay.com/checkouts/<id> \
-H "Authorization: Bearer $MERIDA_API_KEY"
{
"id": "e7b5…",
"status": "paid",
"customer_ref": "cus_…",
"payment_method_ref": "pm_…",
"card_brand": "visa",
"card_last4": "4242"
}
Store customer_ref and payment_method_ref in your database alongside
the buyer's record. You will pass them back to charge the card later.
Charge a saved card later (off-session)
When the buyer is not present, call
POST /partner/practices/{id}/charges/off-session with the saved refs.
The call is synchronous and the response carries the outcome.
curl -X POST \
https://api.meridapay.com/partner/practices/<practice-id>/charges/off-session \
-H "Authorization: Bearer $MERIDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_ref": "cus_…",
"payment_method_ref": "pm_…",
"amount": "99.00",
"label": "Monthly plan, October 2026",
"idempotency_key": "charge-oct-2026-jane"
}'
With the TypeScript SDK:
const { data } = await merida.POST(
"/partner/practices/{id}/charges/off-session",
{
params: { path: { id: practiceId } },
body: {
customer_ref: savedCard.customerRef,
payment_method_ref: savedCard.paymentMethodRef,
amount: "99.00",
label: "Monthly plan, October 2026",
idempotency_key: "charge-oct-2026-jane",
},
}
)
customer_refstringrequiredFrom GET /checkouts/{id} on a paid save-card checkout.
payment_method_refstringrequiredPairs with customer_ref, from the same checkout read.
amountdecimal stringrequiredDecimal major-units, e.g. "99.00". The currency matches the saved card's
original checkout.
labelstringrequiredShown as the statement descriptor. Use something the buyer will recognize, like the plan name and billing period.
idempotency_keystringrequiredA unique key you choose, 8 to 64 characters. The same key always maps to the same charge so retries never double-charge.
succeededstatusThe charge went through. Funds will settle to your account.
failedstatusThe charge was declined. Check error_code (e.g. card_declined,
expired_card) and decide whether to retry or ask the buyer to update
their card.
requires_actionstatusThe card issuer requires additional authentication. Off-session charges cannot complete 3DS challenges, so treat this like a decline and request new card details from the buyer.
On a succeeded charge the response also carries a payment_id. Store it if
you may need to refund this charge later. It is the identifier the refund
endpoint takes, and the only one that works for an off-session charge since it
has no checkout. See Refunds.
View and manage saved cards in the console
You do not need to write any code to see or act on saved cards from the
dashboard. Open a customer record in the MeridaPay dashboard (or the embedded
/manage console if your platform embeds it), find the Saved cards panel
in the customer drawer, and you will see the masked card identity (brand and
last 4 only) for every card on file.
From that same panel, a merchant with the charge capability (owner or admin by default) can trigger an off-session charge directly in the console. No API call is needed from your side.
Authorization requirement
You are responsible for obtaining and recording the buyer's written authorization before charging them off-session. The consent notice on the checkout page covers the save itself. For recurring or future charges, make sure your terms of service and checkout flow make it clear to the buyer what they are authorizing. MeridaPay records that the consent notice was shown and accepted, but the scope of the authorization is yours to define.
PCI scope
Card entry always happens on MeridaPay's hosted fields, whether through the hosted checkout page or the embedded overlay. MeridaPay stores a payment token, never the raw card number. This means enabling saved cards does not expand your PCI compliance scope.
Next up
Customer portal →