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

Complete Google Pay Order

POST https://api.firmly.work/api/v1/domains/{domain}/express/google-pay/complete-order

​​ Overview

Completes the checkout process by placing an order with the merchant using Google Pay. This is a single-step flow — no prior start or authorize calls are required. The endpoint accepts the payment credentials from the Google Pay SDK and places the order directly with the merchant.

Availability and the host this endpoint runs on are covered in the Google Pay Express Checkout overview.

​​ Authentication

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

​​ Path Parameters

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

​​ Prerequisites

Before calling this endpoint:

  • A cart must already exist for {domain} on this device session and hold at least one line item.
  • A shipping method must be selected on the cart. If none is selected, the request fails with 412 MissingShippingMethod.

​​ Request Body

  • attributes (object, required) — Google Pay payment attributes. Supply one of the three credential shapes below. They are checked in order: Aurus Pay first, then the token flow, then the Braintree nonce flow — so if aurus_ott_ref is present the other shapes are ignored, and if google_pay_token is present the nonce fields are ignored.
    Aurus Pay (wallet-ceremony references)
    • aurus_ott_ref (string, required) — One-time-token reference from the Aurus wallet ceremony. Its presence selects this shape.
    • aurus_prep (string, required) — Aurus prep reference. Required whenever aurus_ott_ref is sent.
    • aurus_enonce (string, required) — Aurus encrypted nonce. Required whenever aurus_ott_ref is sent.

    All three must be sent together; sending aurus_ott_ref alone returns 400 BadRequest — “Aurus Pay requires aurus_ott_ref, aurus_prep and aurus_enonce together.”

    Token flow (Stripe, Adyen, most processors)
    • google_pay_token (string, required) — The Google Pay payment token. For Stripe this is the tokenized id (pm_... or tok_...) returned by the Stripe Payment Request API. For Adyen (and other raw-token processors) this is the raw Google Pay ECv2 token blob returned by the Google Pay SDK.
    • google_pay_card_network (string) — Optional. The card network reported by Google Pay for the selected card (e.g., VISA, MASTERCARD). Forwarded to the processor when supplied.
    • google_pay_address (object) — Optional. The Google Pay paymentData.shippingAddress. When supplied with the token flow it is mapped onto the cart’s shipping info; pair it with email. Same field shape as the nonce flow below.
    • email (string) — Optional. Buyer email, used with google_pay_address when mapping shipping info.
    Braintree (nonce flow)
    • nonce (string, required) — The Braintree payment nonce (UUID format) returned by googlePayInstance.parseResponse().
    • email (string, required) — Buyer email address from the Google Pay paymentData.email.
    • google_pay_address (object, required) — Shipping address from Google Pay paymentData.shippingAddress.
    Address Fields
    • name (string, required) — Full name
    • address1 (string, required) — Street address line 1
    • address2 (string) — Street address line 2
    • address3 (string) — Street address line 3
    • locality (string, required) — City
    • administrativeArea (string, required) — State/province code (e.g., “CA”)
    • postalCode (string, required) — Postal/ZIP code
    • countryCode (string, required) — Two-letter country code (e.g., “US”)
    • sortingCode (string) — Sorting code (used in some countries)
    • device_data (object) — Optional. Braintree device-data payload for fraud/risk checks.

​​ Response

Returns the order confirmation object.

  • cart_id (string) — Unique identifier for the cart

  • platform_order_number (string) — The order number from the merchant platform

  • cart_status (string) — Status of the cart (e.g., “submitted”)

  • submitted_at (string) — ISO timestamp when the order was submitted

  • display_name (string) — Merchant’s display name

  • platform_id (string) — Identifier of the merchant’s underlying commerce platform, or "custom" for direct integrations.

  • shop_id (string) — Merchant domain

  • urls (object) — Relevant URLs for the order

    URLs
    • thank_you_page (string) — URL to the merchant’s order confirmation page

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

    Line Item Details
    • line_item_id (string) — Unique identifier for the line item
    • sku (string) — Product SKU
    • quantity (number) — Quantity ordered
    • description (string) — Product description
    • price (object) — Unit price
    • line_price (object) — Total price for this line item
    • image (object) — Product image information

  • shipping_info (object) — Delivery address details

  • billing_info (object) — Billing address details

  • shipping_method (object) — Selected shipping method with pricing

  • payment_summary (object) — Summary of the Google Pay payment

    Payment Summary
    • payment_type (string) — Always "GooglePay" for this gateway

  • total (object) — Grand total including all costs

  • sub_total (object) — Subtotal before shipping and tax

  • shipping_total (object) — Shipping cost

  • tax (object) — Tax amount

  • cart_discount (object) — Total discount applied

​​ Code Examples

​​ Token flow (Stripe / Adyen)


// After user confirms in Google Pay popup
paymentRequest.on('paymentmethod', async (ev) => {
const googlePayToken = ev.paymentMethod.id; // pm_... / tok_... for Stripe
ev.complete('success');
const response = await fetch(
'https://api.firmly.work/api/v1/domains/staging.luma.gift/express/google-pay/complete-order',
{
method: 'POST',
headers: {
'x-firmly-authorization': authToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
attributes: {
google_pay_token: googlePayToken,
google_pay_card_network: ev.paymentMethod.card?.brand?.toUpperCase() // optional
}
})
}
);
const order = await response.json();
console.log('Order number:', order.platform_order_number);
});

curl -X POST https://api.firmly.work/api/v1/domains/staging.luma.gift/express/google-pay/complete-order \
-H "x-firmly-authorization: YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"attributes": {"google_pay_token": "pm_1ABC123xyz", "google_pay_card_network": "VISA"}}'

​​ Braintree Integration (nonce flow)


// After user confirms in Google Pay popup
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
const result = await googlePayInstance.parseResponse(paymentData);
const response = await fetch(
'https://api.firmly.work/api/v1/domains/staging.luma.gift/express/google-pay/complete-order',
{
method: 'POST',
headers: {
'x-firmly-authorization': authToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
attributes: {
nonce: result.nonce,
email: paymentData.email,
google_pay_address: {
name: paymentData.shippingAddress.name,
address1: paymentData.shippingAddress.address1,
address2: paymentData.shippingAddress.address2 || '',
address3: paymentData.shippingAddress.address3 || '',
locality: paymentData.shippingAddress.locality,
administrativeArea: paymentData.shippingAddress.administrativeArea,
postalCode: paymentData.shippingAddress.postalCode,
countryCode: paymentData.shippingAddress.countryCode,
sortingCode: paymentData.shippingAddress.sortingCode || ''
}
}
})
}
);
const order = await response.json();
console.log('Order number:', order.platform_order_number);

curl -X POST https://api.firmly.work/api/v1/domains/staging.luma.gift/express/google-pay/complete-order \
-H "x-firmly-authorization: YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"nonce": "9d4224d5-9c7e-1cf5-5d93-9805ed06c50a",
"email": "customer@example.com",
"google_pay_address": {
"name": "John Smith",
"address1": "1600 Amphitheatre Parkway",
"address2": "",
"address3": "",
"locality": "Mountain View",
"administrativeArea": "CA",
"postalCode": "94043",
"countryCode": "US",
"sortingCode": ""
}
}
}'

​​ Response Example


{
"cart_id": "9cf76530-1344-420f-9ca6-c7a96fb6db45",
"platform_order_number": "23408043",
"cart_status": "submitted",
"submitted_at": "2026-03-17T10:30:00.000Z",
"display_name": "Test Merchant (Staging)",
"platform_id": "example_commerce",
"shop_id": "staging.luma.gift",
"urls": {
"thank_you_page": "https://staging.luma.gift/checkout/post_payment_processing?order_id=23408043"
},
"line_items": [
{
"line_item_id": "3e66bef5-43f5-4a80-8957-91c5c8233163",
"sku": "7225743",
"quantity": 1,
"description": "Radiant Tee — Blue",
"price": {
"currency": "USD",
"value": 25,
"number": 2500,
"symbol": "$"
},
"line_price": {
"currency": "USD",
"value": 25,
"number": 2500,
"symbol": "$"
},
"image": {
"url": "https://cdn.staging.luma.gift/products/radiant-tee-blue.jpg"
},
"msrp": {
"currency": "USD",
"value": 30.0,
"number": 3000,
"symbol": "$"
}
}
],
"shipping_info": {
"first_name": "John",
"last_name": "Smith",
"email": "customer@example.com",
"phone": "(650) 555-1234",
"address1": "1600 Amphitheatre Parkway",
"city": "Mountain View",
"state_or_province": "CA",
"country": "US",
"postal_code": "94043"
},
"shipping_method": {
"id": "21",
"description": "Standard Shipping",
"price": {
"currency": "USD",
"value": 5.99,
"number": 599,
"symbol": "$"
}
},
"payment_summary": {
"payment_type": "GooglePay"
},
"total": {
"currency": "USD",
"value": 35.14,
"number": 3514,
"symbol": "$"
},
"sub_total": {
"currency": "USD",
"value": 25,
"number": 2500,
"symbol": "$"
},
"shipping_total": {
"currency": "USD",
"value": 5.99,
"number": 599,
"symbol": "$"
},
"tax": {
"currency": "USD",
"value": 2.15,
"number": 215,
"symbol": "$"
},
"fees": [
{
"description": "Recycle fee",
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
}
],
"fee_total": {
"currency": "USD",
"value": 2.00,
"number": 200,
"symbol": "$"
},
"cart_discount": {
"currency": "USD",
"value": 0,
"number": 0,
"symbol": "$"
}
}

​​ 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 (nonce flow: missing address)

The Braintree nonce flow was selected but no google_pay_address was supplied, and the cart holds none.


{ "code": 400, "error": "BadRequest", "description": "Missing Google Pay address." }
400 — BadRequest (nonce flow: missing email)

The Braintree nonce flow was selected but no email was supplied, and the cart holds none.


{ "code": 400, "error": "BadRequest", "description": "Missing email." }
400 — BadRequest (Aurus Pay references incomplete)

aurus_ott_ref was sent without aurus_prep and aurus_enonce.


{ "code": 400, "error": "BadRequest", "description": "Aurus Pay requires aurus_ott_ref, aurus_prep and aurus_enonce together." }
400 — BadRequest

Required payment credentials are missing — neither a google_pay_token (token flow) nor a nonce (Braintree nonce flow) was provided. For the nonce flow, email and google_pay_address are also required.


{ "code": 400, "error": "BadRequest", "description": "Missing Google Pay nonce. Provide nonce in request body or call /authorize first." }
412 — MissingShippingMethod

A shipping method has not been selected on the cart yet.


{ "code": 412, "error": "MissingShippingMethod", "description": "Shipping method needs to be set." }
412 — CheckoutError

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


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

The payment processor declined the payment. Ask the user for a different payment method.


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

​​ Payment Processor Detection

The merchant’s payment processor determines which credential shape to send. You obtain the merchant’s Google Pay configuration — including the publishable/tokenization key and the gateway identifier — when you set up the Google Pay SDK for the merchant (via your Google Pay paymentDataRequest gateway parameters). Detect the processor from that key’s prefix:

Processor Publishable Key Format Credential shape Required Fields
Stripe pk_test_... or pk_live_... Token flow google_pay_token (pm_... / tok_...)
Adyen (and other raw-token processors) — Token flow google_pay_token (raw ECv2 blob)
Braintree sandbox_... or production_... Nonce flow nonce, email, google_pay_address

400 — InvalidToken

The x-firmly-authorization header is missing, empty, or not a well-formed JWT. Device-auth can also surface as MissingAuthHeader or PartnerNotFound; see Authentication errors for the full set.


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

The device JWT is well-formed but its 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." }
404 — DomainNotFound

The {domain} path parameter does not match any merchant configured with Firmly, or the merchant has been disabled.


{ "code": 404, "error": "DomainNotFound", "description": "This domain was not found in firmly servers." }
404 — GatewayNotFound

Google Pay is not enabled as a payment gateway for this merchant. Check payment_method_options before offering it.


{ "code": 404, "error": "GatewayNotFound", "description": "Payment gateway 'google-pay' not found" }
503 — StoreUnavailable

The merchant’s own API was unavailable or timed out. Retry with backoff.


{ "code": 503, "error": "StoreUnavailable", "description": "Store temporarily unavailable. Please try again later." }
429 — RateLimited

Too many requests from this device. Back off and retry after the Retry-After window — see Rate Limits.


{ "code": 429, "error": "RateLimited", "description": "Too many requests. Please try again later." }