Payment Flow
A walkthrough of the complete payment lifecycle — from discovering payment options to paying for an order and adding a tip.
What you'll build
An end-to-end payment integration that:
- Lists available payment options for the store
- Gets a customer token from the payment provider
- Lists existing payment sources
- Adds a new payment source (credit card)
- Creates a payment session
- Pays for an order
- Adds a tip
Prerequisites
- A Store UUID (staging:
e87437f2-3e35-4738-af5e-6307e368255c) - A valid JWT token — see the Authentication guide
- An order UUID created via cart checkout — see the Cart & Checkout Flow
- A cart UUID (needed for customer registration and sessions)
- cURL or any HTTP client
All payment endpoints (except payment options) require authentication (jwt_authenticated pipeline).
Setup
STORE_UUID="e87437f2-3e35-4738-af5e-6307e368255c"
BASE_URL="https://ecom-api.staging.blaze.me"
TOKEN="YOUR_JWT_TOKEN"
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const token = "YOUR_JWT_TOKEN";
const headers = {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
Authorization: `Bearer ${token}`,
};
Understanding the {service} Parameter
All payment endpoints use a {service} path parameter that identifies the payment provider. The API abstracts provider-specific logic behind a common interface — the same endpoint paths work across all providers.
Supported {service} values: adyen, aeropay, stronghold, moneris, swifter, ledgergreen, merrco, spence, greenbax, blazepay_widget.
The examples below use adyen, but you can substitute any supported provider.
Step 1: List Payment Options
Discover which payment methods are configured for the store. This determines which {service} values are available.
GET /api/v1/store/payment-options
No authentication required.
cURL
curl -X GET "$BASE_URL/api/v1/store/payment-options" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID"
JavaScript
const optionsRes = await fetch(`${BASE_URL}/api/v1/store/payment-options`, {
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
},
});
const { data: paymentOptions } = await optionsRes.json();
console.log("Available payment options:");
// Identify which services are available for subsequent calls
Use the response to determine which {service} to use in the following steps. The rest of this example uses adyen.
Step 2: Get Payment Customer Token
Some providers require a customer-specific token before adding sources or making payments. This token is used for provider-side authentication.
GET /api/v1/store/payments/{service}/token
cURL
curl -X GET "$BASE_URL/api/v1/store/payments/adyen/token" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN"
JavaScript
const service = "adyen";
const tokenRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/token`,
{ headers }
);
if (tokenRes.ok) {
const { data: tokenData } = await tokenRes.json();
console.log(`Payment token: ${tokenData.attributes.token}`);
console.log(`Expires: ${tokenData.attributes.expiry}`);
console.log(`MFA required: ${tokenData.attributes.mfa}`);
} else {
const { errors } = await tokenRes.json();
// payment_customer_not_found — need to register customer first
console.log("Customer not yet registered with provider");
}
Response
{
"data": {
"id": null,
"type": "payments_token",
"attributes": {
"mfa": false,
"token": "pay_tok_abc123def456...",
"expiry": "2026-06-01T00:00:00Z"
}
}
}
Note: If you get a
payment_customer_not_founderror, register the customer first withPUT /api/v1/store/payments/{service}/customers. See the Payments guide for details.
Step 3: List Payment Sources
Check if the user has any saved payment methods. Use v1 for a specific provider, or v2 to get all providers at once.
GET /api/v1/store/payments/{service}/sources
cURL
curl -X GET "$BASE_URL/api/v1/store/payments/adyen/sources" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN"
JavaScript
const sourcesRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/sources`,
{ headers }
);
const { data: sources } = await sourcesRes.json();
console.log(`${Array.isArray(sources) ? sources.length : 0} saved source(s):`);
if (Array.isArray(sources)) {
sources.forEach((s) => {
const attrs = s.attributes;
console.log(
` ${attrs.label_display} (${attrs.type}) — default: ${attrs.is_default}, expired: ${attrs.is_expired}`
);
});
}
Response
{
"data": [
{
"id": "source-uuid",
"type": "payment_sources",
"attributes": {
"label": "Visa",
"label_display": "Visa •••• 4242",
"active": true,
"external_service": "adyen",
"external_id": "ext_src_abc123",
"type": "credit_card",
"provider": "visa",
"provider_display": "Visa",
"mask": "4242",
"is_default": true,
"expiry_date": "12/2027",
"cardholder_name": "Jane Doe",
"is_expired": false
}
}
]
}
All Providers at Once (v2)
Use GET /api/v2/store/payments/sources to get sources grouped by provider in a single request:
curl -X GET "$BASE_URL/api/v2/store/payments/sources" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN"
Step 4: Add a Payment Source
Register a new payment method. The exact attributes vary by provider — common fields include token, type, and is_default.
POST /api/v1/store/payments/{service}/sources
cURL
curl -X POST "$BASE_URL/api/v1/store/payments/adyen/sources" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"data": {
"type": "payment_sources",
"attributes": {
"token": "tok_abc123...",
"type": "credit_card",
"is_default": true
}
}
}'
JavaScript
const addSourceRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/sources`,
{
method: "POST",
headers,
body: JSON.stringify({
data: {
type: "payment_sources",
attributes: {
token: "tok_abc123...", // from provider's client-side SDK
type: "credit_card",
is_default: true,
},
},
}),
}
);
const { data: newSource } = await addSourceRes.json();
const sourceId = newSource.id;
console.log(`Source added: ${newSource.attributes.label_display}`);
console.log(`Source ID: ${sourceId}`);
Response
{
"data": {
"id": "source-uuid",
"type": "payment_sources",
"attributes": {
"label": "Visa",
"label_display": "Visa •••• 4242",
"active": true,
"external_service": "adyen",
"external_id": "ext_src_abc123",
"type": "credit_card",
"provider": "visa",
"provider_display": "Visa",
"mask": "4242",
"is_default": true,
"expiry_date": "12/2027",
"cardholder_name": "Jane Doe",
"is_expired": false
}
}
}
Common Errors
invalid_payment_source(400) — Required fields are missing for this provider.guest_cannot_add_source(400) — Cannot add sources during guest checkout.expired_payment_token(400) — The payment card has expired.invalid_payment_token(400) — The token could not be validated by the provider.
Step 5: Create a Payment Session
Some providers (e.g., blazepay_widget, adyen) require creating a payment session before charging. The session ties together the store, cart, and user in the external provider.
POST /api/v1/store/payments/{service}/sessions
cURL
curl -X POST "$BASE_URL/api/v1/store/payments/adyen/sessions" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"data": {
"type": "payment_sessions",
"attributes": {},
"relationships": {
"cart": {
"data": {
"type": "carts",
"id": "CART_UUID"
}
}
}
}
}'
JavaScript
const cartId = "CART_UUID"; // from your cart creation
const sessionRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/sessions`,
{
method: "POST",
headers,
body: JSON.stringify({
data: {
type: "payment_sessions",
attributes: {},
relationships: {
cart: {
data: { type: "carts", id: cartId },
},
},
},
}),
}
);
const { data: session } = await sessionRes.json();
console.log(`Session created: ${session.id}`);
console.log(`Access token: ${session.attributes.access_token}`);
Response (201 Created)
{
"data": {
"id": "session-uuid",
"type": "payment_session",
"attributes": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
}
Note: Not all providers require sessions. Check the provider documentation or the Payments guide for provider-specific requirements.
Step 6: Pay for an Order
Charge the order using the payment source. This is the core payment step.
POST /api/v1/store/payments/{service}/orders/{uuid}/pay
cURL
curl -X POST "$BASE_URL/api/v1/store/payments/adyen/orders/ORDER_UUID/pay" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"data": {
"type": "payments_charge",
"attributes": {
"source_id": "SOURCE_UUID",
"postal_code": "90210"
}
}
}'
JavaScript
const orderUuid = "ORDER_UUID"; // from your order creation
const payRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/orders/${orderUuid}/pay`,
{
method: "POST",
headers,
body: JSON.stringify({
data: {
type: "payments_charge",
attributes: {
source_id: sourceId,
postal_code: "90210",
},
},
}),
}
);
const result = await payRes.json();
if (payRes.ok) {
const charge = result.data;
console.log(`Payment status: ${charge.attributes.status}`);
console.log(
`Amount: $${(charge.attributes.amount.amount / 100).toFixed(2)}`
);
// Check for 3DS redirect
if (charge.attributes.external_url) {
console.log(`3DS redirect required: ${charge.attributes.external_url}`);
}
} else {
console.error("Payment failed:", result.errors);
}
Response
{
"data": {
"id": "charge-uuid",
"type": "payment_charges",
"attributes": {
"status": "authorized",
"type": "charge",
"fee": { "amount": 0, "currency": "USD" },
"convenience_fee": { "amount": 0, "currency": "USD" },
"amount": { "amount": 4928, "currency": "USD" },
"external_id": "ext_charge_abc123",
"external_service": "adyen",
"external_url": null,
"created_at": "2026-05-30T20:00:00Z",
"authorized_at": "2026-05-30T20:00:01Z",
"captured_at": null,
"is_3ds_authenticated": false,
"guest_payment_source": null
},
"relationships": {
"payment_customer": {
"data": { "id": "customer-uuid", "type": "payment_customers" }
},
"payment_source": {
"data": { "id": "source-uuid", "type": "payment_sources" }
},
"payment_tip": { "data": null }
}
},
"included": [
{
"id": "customer-uuid",
"type": "payment_customers",
"attributes": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"external_service": "adyen",
"external_id": "ext_cust_abc123"
}
},
{
"id": "source-uuid",
"type": "payment_sources",
"attributes": {
"label": "Visa",
"mask": "4242",
"is_default": true,
"is_expired": false
}
}
]
}
Charge statuses: authorized (pending capture), captured (funds collected), failed.
Note: Amounts are in cents (e.g.,
4928= $49.28).
Common Errors
order_already_paid(400) — This order has already been paid.order_user_mismatch(400) — The order belongs to a different user.payment_failed(400) — Payment authorization was declined by the provider.missing_payment_source(400) — Nosource_idwas provided.missing_payment_postal_code(400) — Postal code is required by this provider.payment_source_not_found(400) — The source ID doesn't match any saved source.
Step 7: Add a Tip
After payment, optionally add a tip. Tips are processed through the same payment provider.
POST /api/v1/store/payments/{service}/tip
cURL
curl -X POST "$BASE_URL/api/v1/store/payments/adyen/tip" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"data": {
"type": "payment_sources",
"attributes": {
"external_id": "ext_charge_abc123",
"percentage": 15
}
}
}'
JavaScript
const chargeExternalId = result.data.attributes.external_id;
const tipRes = await fetch(
`${BASE_URL}/api/v1/store/payments/${service}/tip`,
{
method: "POST",
headers,
body: JSON.stringify({
data: {
type: "payment_sources",
attributes: {
external_id: chargeExternalId,
percentage: 15,
},
},
}),
}
);
const { data: tip } = await tipRes.json();
console.log(`Tip status: ${tip.attributes.status}`);
console.log(
`Tip amount: $${(tip.attributes.amount.amount / 100).toFixed(2)}`
);
console.log(`Tip percentage: ${tip.attributes.percentage}%`);
Response
{
"data": {
"id": "tip-uuid",
"type": "payment_tips",
"attributes": {
"status": "captured",
"beneficiary_name": "Store Name",
"fee": { "amount": 0, "currency": "USD" },
"amount": { "amount": 740, "currency": "USD" },
"external_id": "ext_tip_abc123",
"external_service": "adyen",
"created_at": "2026-05-30T20:05:00Z",
"authorized_at": "2026-05-30T20:05:00Z",
"captured_at": "2026-05-30T20:05:01Z",
"percentage": 15
},
"relationships": {
"payment_source": {
"data": { "id": "source-uuid", "type": "payment_sources" }
}
}
}
}
Common Errors
tips_not_allowed(400) — Tips are not enabled for this payment provider.already_tipped(400) — A tip has already been processed for this order.
Complete Flow Summary
GET /api/v1/store/payment-options → Discover available providers
GET /api/v1/store/payments/{service}/token → Get customer token
GET /api/v1/store/payments/{service}/sources → List saved payment sources
POST /api/v1/store/payments/{service}/sources → Add a new payment source
POST /api/v1/store/payments/{service}/sessions → Create payment session
POST /api/v1/store/payments/{service}/orders/{uuid}/pay → Pay for the order
POST /api/v1/store/payments/{service}/tip → Add a tip
What's Next?
- Create an order first → see the Cart & Checkout Flow example
- Manage your account → see the User Account Flow example
- Full payment reference → see the Payments guide