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:

  1. Lists available payment options for the store
  2. Gets a customer token from the payment provider
  3. Lists existing payment sources
  4. Adds a new payment source (credit card)
  5. Creates a payment session
  6. Pays for an order
  7. 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_found error, register the customer first with PUT /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) — No source_id was 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?