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 ifaurus_ott_refis present the other shapes are ignored, and ifgoogle_pay_tokenis 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 wheneveraurus_ott_refis sent.aurus_enonce(string, required) — Aurus encrypted nonce. Required wheneveraurus_ott_refis sent.
All three must be sent together; sending
aurus_ott_refalone returns400 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_...ortok_...) 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 PaypaymentData.shippingAddress. When supplied with the token flow it is mapped onto the cart’s shipping info; pair it withemail. Same field shape as the nonce flow below.email(string) — Optional. Buyer email, used withgoogle_pay_addresswhen mapping shipping info.
Braintree (nonce flow)
nonce(string, required) — The Braintree payment nonce (UUID format) returned bygooglePayInstance.parseResponse().email(string, required) — Buyer email address from the Google PaypaymentData.email.google_pay_address(object, required) — Shipping address from Google PaypaymentData.shippingAddress.
Address Fields
name(string, required) — Full nameaddress1(string, required) — Street address line 1address2(string) — Street address line 2address3(string) — Street address line 3locality(string, required) — CityadministrativeArea(string, required) — State/province code (e.g., “CA”)postalCode(string, required) — Postal/ZIP codecountryCode(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 orderURLs
thank_you_page(string) — URL to the merchant’s order confirmation page
-
line_items(array) — Array of items in the orderLine Item Details
line_item_id(string) — Unique identifier for the line itemsku(string) — Product SKUquantity(number) — Quantity ordereddescription(string) — Product descriptionprice(object) — Unit priceline_price(object) — Total price for this line itemimage(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 paymentPayment 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 popuppaymentRequest.on('paymentmethod', async (ev) => {const googlePayToken = ev.paymentMethod.id; // pm_... / tok_... for Stripeev.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 popupconst 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 |
Related Resources
- Google Pay Web Integration — Google’s official documentation
- Stripe Payment Request API — Stripe’s Google Pay integration
- Braintree Google Pay — Braintree’s Google Pay integration
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." }