Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Wallet Complete Order

POST https://cc.firmly.work/api/v2/payment/domains/{domain}/wallet-complete-order

​​ Overview

Completes checkout using wallet-provided payment credentials. For Agentic Pay, Firmly retrieves network token credentials (PAN, expiry, cryptogram) from the wallet, then tokenizes with the merchant’s PSP and places the order.

This endpoint supports multiple wallet types. This page focuses on the Agentic Pay flow.

​​ Authentication

This endpoint uses the same deviceAuth strategy as the card Complete Order endpoint — a single device JWT in the x-firmly-authorization header. On this device-JWT path, the App ID and device ID are carried as claims inside that JWT; they are not separate request headers.

  • x-firmly-authorization (string, required) — Device authentication token (JWT) from Browser Session

​​ Path Parameters

  • domain (string, required) — The merchant domain (e.g., “staging.luma.gift”)

​​ Request Body

  • wallet (string, required) — Wallet type. One of: visa, paze, mastercard-unified, agentic-pay, samsung-wallet. Use agentic-pay for every Agentic Pay order regardless of card network — Visa, Mastercard, and Discover alike; the network travels inside the flow_token, and visa / mastercard-unified are the separate Click to Pay wallets, not Agentic Pay.

  • additional_data (object, optional) — Wallet-specific payment data (required for Agentic Pay). For Agentic Pay, provide:

  • flow_token (string, required) — Flow token from the Intent Challenge response

  • intent_id (string) — Intent ID from the Intent Challenge response

  • transaction (object) — Capture details: amount and currency_code (the capture; amount must be ≤ the authorized mandate.amount), plus optional merchant_name, merchant_url, and merchant_country_code. Submitting an amount greater than the mandate.amount authorized at Create Intent is rejected with a 400 BadRequest before the network is called. The amount compared against the mandate is the cart total on file, not the value you send here — transaction is informational and cannot lower the checked amount.

  • billing_info (object) — Billing address. Used as fallback if the network does not return billing data with the credentials. Required for samsung-wallet. Fields:

    • first_name (string) — Buyer’s first name
    • last_name (string) — Buyer’s last name
    • email (string) — Email address
    • phone (string) — Phone number
    • address1 (string) — Primary address line
    • city (string) — City
    • state_or_province (string) — State/province code
    • country (string) — Country code (ISO 3166-1 alpha-2, e.g. US)
    • postal_code (string) — ZIP or postal code
  • credit_card_id (string) — Card identifier from the wallet (required for visa, paze, mastercard-unified; not needed for agentic-pay or samsung-wallet)

  • encrypted_card (string) — Samsung network-token JWE (required for samsung-wallet; not used by the other wallet types)

  • captcha_token (string) — Captcha verification token when required by the merchant

​​ Response

Returns either a CVV requirement or the submitted cart.

​​ CVV Required (early return)

When the wallet requires CVV verification before completing, the endpoint returns early without placing the order:


{
"cvv_required": true
}

Collect the CVV from the cardholder and re-call this same wallet-complete-order endpoint with the CVV supplied in the request body to finish placing the order. The initial call is not retried automatically — you must issue a second request once the CVV is available.

​​ Order Placed (success)

Returns the full cart object with cart_status: "submitted" and a platform_order_number.

  • cart_status (string) — Cart status — "submitted" on success

  • platform_order_number (string) — Merchant-assigned order number

  • cart_id (string) — Firmly cart identifier

  • line_items (array) — Array of items in the order

  • total (object) — Order total with currency, value, number, symbol

  • billing_info (object) — Billing address used for the order

  • shipping_info (object) — Shipping address for the order

  • payment_summary (object) — Payment details. For Agentic Pay orders this also carries the persisted card_art and masked so receipts/order views can render the funding card (distinct from the top-level last_four, which is the network token).

    payment_summary properties
    • payment_summary.payment_type (string) — e.g. "CreditCard"
    • payment_summary.last_four (string) — Last four of the network token
    • payment_summary.month (integer) — Expiry month
    • payment_summary.year (integer) — Expiry year
    • payment_summary.card_type (string) — Card brand (e.g. "Visa", "Mastercard")
    • payment_summary.card_art (object) — Issuer card art { network, url, background_color, foreground_color, descriptor } (Agentic Pay). See Card Art.
    • payment_summary.masked (object) — Funding-card display fields { last4, exp_month, exp_year, brand, card_type, issuer_name } (Agentic Pay). card_type and issuer_name are Mastercard only.

​​ Code Example


const response = await fetch(
'https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/wallet-complete-order',
{
method: 'POST',
headers: {
'x-firmly-authorization': deviceJwt,
'Content-Type': 'application/json'
},
body: JSON.stringify({
wallet: 'agentic-pay',
additional_data: {
flow_token: flowToken,
intent_id: intentId,
transaction: {
amount: '585.49',
currency_code: 'USD',
merchant_name: 'My Store'
}
},
billing_info: {
first_name: 'John',
last_name: 'Smith',
address1: '123 Main St',
city: 'San Francisco',
state_or_province: 'CA',
country: 'US',
postal_code: '94105',
email: 'john@example.com',
phone: '4155551212'
}
})
}
);
const order = await response.json();
console.log('Order number:', order.platform_order_number);

import requests
response = requests.post(
'https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/wallet-complete-order',
headers={
'x-firmly-authorization': device_jwt,
'Content-Type': 'application/json'
},
json={
'wallet': 'agentic-pay',
'additional_data': {
'flow_token': flow_token,
'intent_id': intent_id,
'transaction': {
'amount': '585.49',
'currency_code': 'USD'
}
},
'billing_info': {
'first_name': 'John',
'last_name': 'Smith',
'address1': '123 Main St',
'city': 'San Francisco',
'state_or_province': 'CA',
'country': 'US',
'postal_code': '94105',
'email': 'john@example.com',
'phone': '4155551212'
}
}
)
order = response.json()

curl -X POST https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/wallet-complete-order \
-H "x-firmly-authorization: DEVICE_JWT" \
-H "Content-Type: application/json" \
-d '{
"wallet": "agentic-pay",
"additional_data": {
"flow_token": "eyJhbGci...",
"intent_id": "1-5C90F150...",
"transaction": {
"amount": "585.49",
"currency_code": "USD"
}
},
"billing_info": {
"first_name": "John",
"last_name": "Smith",
"address1": "123 Main St",
"city": "San Francisco",
"state_or_province": "CA",
"country": "US",
"postal_code": "94105",
"email": "john@example.com",
"phone": "4155551212"
}
}'

​​ Response Example


{
"display_name": "Luma (Staging)",
"cart_status": "submitted",
"platform_id": "example_commerce",
"shop_id": "staging.luma.gift",
"cart_id": "4dbaf295-93d1-452b-a7e5-a5965ca2871d",
"submitted_at": "2026-04-29T07:15:35.367Z",
"platform_order_number": "29185",
"line_items": [
{
"line_item_id": "d290c5fa-0e91-462f-b53f-08d5b71ae532",
"sku": "Product-SKU",
"quantity": 1,
"description": "Product Name",
"price": {
"currency": "USD",
"value": 1,
"number": 100,
"symbol": "$"
},
"line_price": {
"currency": "USD",
"value": 1,
"number": 100,
"symbol": "$"
},
"msrp": {
"currency": "USD",
"value": 1.2,
"number": 120,
"symbol": "$"
},
"image": {
"url": "https://staging.luma.gift/product-name.jpg",
"alt": "Product Name",
"type": "default"
}
}
],
"shipping_info": {
"first_name": "John",
"last_name": "Doe",
"address1": "500 Howard St",
"city": "San Francisco",
"state_or_province": "CA",
"country": "US",
"postal_code": "94105",
"email": "john@example.com",
"phone": "14155550134"
},
"billing_info": {
"first_name": "John",
"last_name": "Doe",
"address1": "500 Howard St",
"city": "San Francisco",
"state_or_province": "CA",
"country": "US",
"postal_code": "94105"
},
"total": {
"currency": "USD",
"value": 1,
"number": 100,
"symbol": "$"
},
"sub_total": {
"currency": "USD",
"value": 1,
"number": 100,
"symbol": "$"
},
"tax": {
"currency": "USD",
"value": 0,
"number": 0,
"symbol": "$"
},
"payment_summary": {
"payment_type": "AgenticPay",
"last_four": "4444",
"month": 12,
"year": 2030,
"card_type": "Mastercard",
"card_art": {
"network": "mastercard",
"url": "https://api.firmly.work/api/v1/wallets/agentic-pay/card-art/<token>",
"background_color": "#1A1F71",
"foreground_color": "#FFFFFF",
"descriptor": "Test Bank 2"
},
"masked": {
"last4": "4595",
"exp_month": "12",
"exp_year": "2030",
"brand": "mastercard",
"card_type": "DEBIT",
"issuer_name": "Test Bank 2"
}
}
}

​​ Error Responses

Errors return a JSON body with code, error, and description. Program against the error value — descriptions are human-readable and may change.

400 — BadRequest

The wallet field is missing or is not one of the supported wallet identifiers, or (Agentic Pay) the cart total exceeds the authorized mandate.amount — the mandate check rejects the charge before the network is called.


{ "code": 400, "error": "BadRequest", "description": "Unsupported wallet identifier." }
400 — InvalidToken

The authorization token is not a valid JWT structure, or it has expired.


{ "code": 400, "error": "InvalidToken", "description": "Jwt token is invalid." }
400 — MissingRequiredParameters

The cart is missing data needed to complete the order (e.g. no payment_handle because Set Shipping Info has not been called).


{ "code": 400, "error": "MissingRequiredParameters", "description": "Cart does not have a payment handle" }
401 — InvalidJWTToken

The device JWT signature does not verify, or required claims are missing.


{ "code": 401, "error": "InvalidJWTToken", "description": "Jwt token is invalid." }
404 — CartNotFound

No active cart exists for this device on this domain.


{ "code": 404, "error": "CartNotFound", "description": "Cart was not found." }
409 — PaymentMethodNotAvailable

The requested wallet payment method is not available for this merchant.


{ "code": 409, "error": "PaymentMethodNotAvailable", "description": "Payment method not available." }
412 — CheckoutError

Merchant-side checkout failure — payment info or billing address was rejected.


{ "code": 412, "error": "CheckoutError", "description": "Payment info or billing address invalid." }
412 — CountryNotSupported

The billing_info.country is outside the supported set (currently US and AU). Firmly rejects the order before tokenization.


{ "code": 412, "error": "CountryNotSupported", "description": "We don't currently support this country/region. Enter a new address and try again." }
400 — InvalidInputBody

Request body failed schema validation.


{ "code": 400, "error": "InvalidInputBody", "description": "The body is not processable" }
422 — CreditCardDeclined

Payment was declined by the network / PSP.


{ "code": 422, "error": "CreditCardDeclined", "description": "Credit card declined" }
422 — CreditCardInvalidNumber

Card number failed PSP validation.


{ "code": 422, "error": "CreditCardInvalidNumber", "description": "Credit card number is invalid." }