# Customer Details & Shipping (/fr/online/integration/customer_and_shipping)



<WalletDetailsBanner />

Collect contact details and a delivery address at checkout, offer delivery options, and charge only a total your server approved. The same settings apply to the card form, Apple Pay, and Google Pay, in both Hosted and Embedded Checkout.

## What the wallet sheet collects [#what-the-wallet-sheet-collects]

Apple Pay and Google Pay do not use your form. They open their own payment sheet, ask for the details you request, and return them to you.

| In the sheet       | You request it with                          | You read it from                             |
| ------------------ | -------------------------------------------- | -------------------------------------------- |
| Delivery address   | `shippingOptions`                            | `customer.shippingAddress`                   |
| Delivery method    | `shippingOptions` options or approved quotes | `shippingQuote`                              |
| Email, name, phone | `customerCollection`                         | `customer`, and `walletContact` for receipts |
| Billing address    | `customerCollection.billingAddress`          | `customer`                                   |
| Total              | Your server approves it                      | The payment, read on your server             |

If the total changes, the shopper authorizes again in the sheet, and an expired quote is refused before charging.

## Where Apple Pay and Google Pay differ [#where-apple-pay-and-google-pay-differ]

|                       | Apple Pay                                                                                                                                                                | Google Pay                                                                          |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| **Before it appears** | Embedded Checkout needs an [active shop domain](/online/integration/checkout_settings#register-apple-pay-shop-domains). Cross-origin Apple Pay needs Safari 17 or later. | Live payments need a configured Google Pay merchant ID.                             |
| **Delivery**          | Needs supported native shipping callbacks. Otherwise card entry remains available.                                                                                       | Native shipping callbacks.                                                          |
| **Testing**           | Real supported devices only.                                                                                                                                             | Real supported devices. A simulated TEST sheet does not verify production behavior. |

A wallet button also depends on the shopper's device and browser, so enabling a wallet does not guarantee it appears everywhere. See [Checkout settings](/online/integration/checkout_settings). For design rules, see Apple's [Apple Pay guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay) and Google's [Google Pay brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines).

## Implementation [#implementation]

<Steps>
  <Step>
    ### Collect contact and billing details [#collect-contact-and-billing-details]

    Set the fields to collect when creating a hosted session or embedded payment:

    ```ts
    customerCollection: {
    	email: 'required',
    	name: 'optional',
    	phone: 'optional',
    	billingAddress: 'none',
    }
    ```

    * Each field accepts `none`, `optional`, or `required`.
    * An omitted policy or `null` uses merchant defaults. An explicit object sets omitted fields to `none`, and `{}` disables collection.
    * Prefill values through `customer.email`, `name`, `phone`, `billingAddress`, and `shippingAddress`.

    Read accepted details from `customer` on session or payment status reads. These are shopper-supplied details, not verified identity.

    <Callout title="Wallets" type="info">
      For a wallet opened before preparation completes, pass the matching `wallets.customerCollection` hint, so the sheet asks for the same fields. The server policy stays authoritative.

      Wallet results can expose `walletContact: { method, customer }` with email, name, phone, and billing address for receipt presentation. This browser snapshot does not prove payment or replace the order's server-approved delivery address.
    </Callout>
  </Step>

  <Step>
    ### Offer fixed delivery options [#offer-fixed-delivery-options]

    Pass `shippingOptions` when creating the payment or hosted session:

    ```ts
    shippingOptions: {
    	dynamic: false,
    	options: [{
    		id: 'standard',
    		label: 'Standard delivery',
    		price: 300,
    		amount: 1500,
    		tax: 200,
    		countries: ['CZ', 'SK'],
    	}],
    }
    ```

    | Field          | Meaning                                                                                                |
    | -------------- | ------------------------------------------------------------------------------------------------------ |
    | `amount`       | The complete final charge, including the delivery `price` and `tax`. Checkout does not add them again. |
    | `price`, `tax` | Integer minor units. `tax: null` or omitted means unspecified VAT; `0` means no VAT.                   |
    | `countries`    | Uppercase two-letter country codes. An empty list permits any country.                                 |

    The form and the wallet sheets collect a delivery address and offer the matching options. Without `shippingOptions`, the creation amount stays fixed.

    If a quote changes the displayed total during submission, card checkout asks the shopper to review the new amount and select Pay again. Read the selected option and revision through `shippingQuote`.
  </Step>

  <Step>
    ### Approve dynamic delivery [#approve-dynamic-delivery]

    When delivery depends on the address, set `dynamic: true` and approve each quote revision from your server. Embedded Checkout asks through browser handlers; Hosted Checkout calls your server directly.

    <Tabs defaultValue="embedded" groupId="checkout-mode" className="border-0 bg-transparent">
      <TabsList>
        <TabsTrigger value="embedded">
          Embedded Checkout
        </TabsTrigger>

        <TabsTrigger value="hosted">
          Hosted Checkout
        </TabsTrigger>
      </TabsList>

      <TabsContent value="embedded">
        Create the payment with `shippingOptions: { dynamic: true }`. Configure both change handlers to ask your backend for approval. Like `wallets.customerCollection`, `wallets.shippingRequired` is a hint for wallet sheets opened before preparation completes:

        ```ts
        import type { ShippingChangeEvent } from '@tapayadot/checkout/client'

        const approveDelivery = async (event: ShippingChangeEvent) => {
        	const response = await fetch('/api/delivery', {
        		method: 'POST',
        		headers: { 'content-type': 'application/json' },
        		body: JSON.stringify(event),
        	})
        	if (!response.ok) throw new Error('Delivery could not be approved.')
        }

        const payment = browserTapaya.createPayment({
        	clientSecret,
        	wallets: { amount: 1200, currency: 'EUR', shippingRequired: true },
        	onShippingAddressChange: approveDelivery,
        	onShippingOptionChange: approveDelivery,
        	onShippingQuoteChange: ({ amount, currency }) => updateOrderTotal(amount, currency),
        })
        ```

        Events contain `paymentId`, `revision`, `address`, and `selectedOptionId`. Your backend authenticates the shopper, checks access to the order, and reads the current pending quote. Calculate the options from your stored cart and delivery rules, then approve that revision:

        ```ts
        await tapaya.checkout.sessions.approveShippingQuote(paymentId, {
        	revision,
        	options,
        	selectedOptionId,
        	expiresAt: Math.floor(Date.now() / 1000) + 300,
        })
        ```

        `expiresAt` is Unix time in seconds. To reject delivery, approve with no options and `error: 'unsupported_destination'` or `'quote_error'`.

        Browser handler return values cannot set the charge total. After a handler resolves, the frame reads the server-approved quote and rejects stale revisions, expired quotes, and unapproved totals. Protect your approval route with the same authentication and CSRF controls as payment preparation.

        Use `onShippingQuoteChange` or `snapshot.shippingQuote` to update the order summary and Pay label. Notifications include the revision and totals without the delivery address or a wallet token. The React hook supports the same callbacks without remounting when callback functions change.
      </TabsContent>

      <TabsContent value="hosted">
        Hosted Checkout uses a server callback instead of browser handlers:

        ```ts
        shippingOptions: {
        	dynamic: true,
        	callbackUrl: 'https://shop.example/api/tapaya/delivery',
        	callbackSecret: process.env.TAPAYA_DELIVERY_SECRET,
        }
        ```

        The callback origin must be permitted by the backend. Keep `callbackSecret` server-only and use 32 to 512 characters. Verify `X-Tapaya-Quote-Signature` against the exact raw body before parsing:

        ```ts
        import { verifyShippingQuoteSignature } from '@tapayadot/checkout'

        const callbackSecret = process.env.TAPAYA_DELIVERY_SECRET
        if (!callbackSecret) throw new Error('Configure TAPAYA_DELIVERY_SECRET')
        const body = await request.text()
        const valid = await verifyShippingQuoteSignature(
        	body,
        	request.headers.get('X-Tapaya-Quote-Signature'),
        	callbackSecret,
        )
        if (!valid) return new Response('Invalid signature', { status: 401 })
        const change = JSON.parse(body)
        ```

        The helper verifies HMAC-SHA256. The request contains `paymentId`, `merchantOrderId`, `currency`, `revision`, `address`, and `selectedOptionId`. Validate these against your stored order before pricing.

        <Callout title="Respond within five seconds" type="warn">
          Return `revision`, `options`, `selectedOptionId`, and `expiresAt` within five seconds. Never send the callback secret to the browser.
        </Callout>
      </TabsContent>
    </Tabs>
  </Step>

  <Step>
    ### Verify the accepted total [#verify-the-accepted-total]

    Read the payment on your server and compare its order reference, currency, and amount with the final total your backend approved. Persist that total with the accepted quote revision. Comparing only with the initial subtotal rejects legitimate delivery changes.

    `checkout.sessions.prepare()` validates the original creation input through replay when a delivery quote changes the read amount. Your fulfillment check still uses the accepted final total. The accepted address is in `customer.shippingAddress`, and the selected delivery option is in `shippingQuote`.
  </Step>

  <Step>
    ### Test wallets on real devices [#test-wallets-on-real-devices]

    Wallet shipping uses native address and option callbacks. A changed total needs another authorization, and an expired quote is refused before charging. Apple Pay shipping requires supported native shipping callbacks; if they are unavailable, card entry remains available.

    Test shipping, contact collection, and cancellation in both wallets on real supported devices before enabling delivery in production. The [go-live checklist](/online/getting-started/checklist) lists the checks.
  </Step>
</Steps>
