Payments
Take card payments through the store's own Stripe account and PayPal, handle test and live mode, and turn declines into clear messages.
Elara One takes payments through the store's own Stripe and PayPal accounts. The money goes straight to the merchant, and card details are entered into Stripe's or PayPal's own form, so they never reach Elara One or your servers. This page covers how the two providers fit into checkout and how to handle a payment that does not go through.
Card data never touches Elara One
- Stripe. The shopper types their card into Stripe's Payment Element, which runs in an iframe served by Stripe. Your page and the API only ever see a PaymentIntent id (
pi_…). - PayPal. The shopper pays in PayPal's own buttons and window. The API only sees the PayPal order id.
Your storefront server and the API never handle card numbers.
Connect the providers
Merchants connect their accounts in the store admin under Settings → Payments. Each provider has a set of Test keys and a set of Live keys. The page also shows the webhook address on the store's own hooks host and the provider events to send to it.
Your storefront reads what it needs from GET /v1/checkout/config: which gateways are on, the Stripe publishable key and the PayPal client id. You never handle the merchant's secret keys.
Test and live mode
gateway_mode in GET /v1/checkout/config is test or live, and says which set of keys checkout is using. It is set for the store by Elara One rather than switched in the admin, and the top of the Payments page shows which mode is in use.
- In
testmode, show a "test payments" banner, and use the provider's test cards and sandbox accounts. - Load PayPal's JavaScript SDK with the client id for the same mode.
- Staging stores, where your plan includes them, always use test payments.
Stripe
The Stripe flow uses a PaymentIntent and the Payment Element:
POST /v1/checkout/payment-intentreturns aclient_secretand thepublishable_keyfor the cart's current total.- Mount the Payment Element with the client secret and let the shopper confirm.
- Call
POST /v1/checkout/completewith{ "payment": { "gateway": "stripe", "payment_intent": "pi_…" } }.
const stripe = Stripe(publishableKey)
const elements = stripe.elements({ clientSecret })
elements.create('payment').mount('#payment-element')
// On "Pay":
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
redirect: 'if_required',
confirmParams: { return_url: 'https://hearthandoak.example/checkout/return' },
})
if (!error) {
// Ask your server to call POST /v1/checkout/complete with paymentIntent.id.
}When the money is taken
stripe_capture_method in GET /v1/checkout/config tells you how the PaymentIntent takes the money:
| Value | What happens |
|---|---|
manual | Confirming the payment in the browser only authorises the card (the PaymentIntent ends in requires_capture). checkout/complete checks stock, shipping and promotions, and only then captures. If it refuses, the authorisation is released and nothing is charged. |
automatic | Confirming the payment takes the money. If the order can then no longer be placed, the payment is refunded and checkout returns 409 PAYMENT_REFUNDED. |
If you mount the Payment Element before creating the PaymentIntent (a deferred intent), pass stripe_capture_method as Elements' captureMethod. After any refusal from checkout/complete, call POST /v1/checkout/payment-intent again before letting the shopper pay again.
PayPal
- In the PayPal buttons'
createOrder, callPOST /v1/checkout/paypal/orderand return itspaypal_order_id. - In
onApprove, callPOST /v1/checkout/paypal/capturewith{ "paypal_order_id": "…" }and anIdempotency-Key.
The capture is recorded before the order is placed, so a retry never captures twice. If PayPal refuses the shopper's funding source, nothing is taken: let them choose another in the PayPal buttons and call the capture again for the same PayPal order.
The starter storefronts offer Stripe card checkout. PayPal is available in the API for storefronts you build yourself.
Declined payments
When a payment does not go through, POST /v1/checkout/complete and POST /v1/checkout/paypal/capture answer 402 with error.code PAYMENT_FAILED. When the provider declined it, details.decline_code says why:
{
"error": {
"code": "PAYMENT_FAILED",
"message": "PayPal declined the payment method. Choose another way to pay and try again.",
"details": { "decline_code": "try_another_method" }
}
}decline_code | Meaning | What to show |
|---|---|---|
card_declined | The card issuer or PayPal refused it without a reason the shopper can act on. Lost, stolen and fraud-filter declines are reported this way on purpose. | "Your card was declined. Try another card or way to pay." |
card_expired | The card has expired. | Ask for another card. |
insufficient_funds | Not enough funds or credit. | Ask for another card or way to pay. |
incorrect_card_details | A wrong card number, security code (CVC) or postcode. | Ask the shopper to check and re-enter their details. |
authentication_required | 3-D Secure (Stripe) or PayPal's own check was not completed. | Ask the shopper to complete their bank's check, then try again. |
try_another_method | PayPal refused this funding source. | Reopen the PayPal buttons so the shopper can pick another, then capture the same PayPal order again. |
unknown | The provider declined for a reason Elara One does not map, or gave none. | Show error.message. |
- Treat any value you do not recognise as
unknown. The list may grow. error.messageis Elara One's own wording, safe to show. The provider's raw text is never returned.detailsis only present on a provider decline. Other refusals, such as a released authorisation or a payment made for another store, have onlycodeandmessage: show the message.- After a
402, the cart and stock are unchanged and the shopper can try again. Branch onerror.codefirst: a402withTENANT_PAYMENT_REQUIREDmeans the store itself is unavailable, not that the payment failed.
const body = await res.json()
if (res.status === 402 && body.error.code === 'PAYMENT_FAILED') {
const code = body.error.details?.decline_code ?? 'unknown'
if (code === 'try_another_method') reopenPayPalButtons()
showPaymentError(body.error.message)
}The other checkout outcomes, such as SHIPPING_OPTION_CHANGED and PAYMENT_REFUNDED, are covered in checkout.
Customer accounts
Register and sign in shoppers with bearer tokens, verify email, reset passwords, manage the address book, show order history and delete an account.
Search
Results pages, instant search suggestions, the merchant's search settings, the anonymous search events beacon, synonyms and search rate limits.