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. Useagentic-payfor every Agentic Pay order regardless of card network — Visa, Mastercard, and Discover alike; the network travels inside theflow_token, andvisa/mastercard-unifiedare 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:amountandcurrency_code(the capture;amountmust be ≤ the authorizedmandate.amount), plus optionalmerchant_name,merchant_url, andmerchant_country_code. Submitting anamountgreater than themandate.amountauthorized at Create Intent is rejected with a400 BadRequestbefore the network is called. The amount compared against the mandate is the cart total on file, not the value you send here —transactionis 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 forsamsung-wallet. Fields:first_name(string) — Buyer’s first namelast_name(string) — Buyer’s last nameemail(string) — Email addressphone(string) — Phone numberaddress1(string) — Primary address linecity(string) — Citystate_or_province(string) — State/province codecountry(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 forvisa,paze,mastercard-unified; not needed foragentic-payorsamsung-wallet) -
encrypted_card(string) — Samsung network-token JWE (required forsamsung-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 withcurrency,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 persistedcard_artandmaskedso receipts/order views can render the funding card (distinct from the top-levellast_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 tokenpayment_summary.month(integer) — Expiry monthpayment_summary.year(integer) — Expiry yearpayment_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_typeandissuer_nameare 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 requestsresponse = 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." }
Related Endpoints
- Intent Challenge — Get the flow_token and intent_id
- Browser Session — Get device JWT for authentication
- Complete Order — Alternative flow with encrypted card data