Skip to main content
Enterprise feature. Contact us for access.

Introduction

This guide shows you how to configure Crossmint’s Embedded Checkout so it renders only the Apple Pay or Google Pay button, and everything else on the screen is yours: your amount selector, your token UI, your success states. Rendering just the wallet button gives users a smooth one-tap purchase experience inside your own UI. You’ll learn how to:
  • Restrict the embedded checkout to a single payment method
  • Create the order server-side so the pay button is ready when the user sees it
  • Hide the checkout’s built-in inputs and surface payment errors in your layout
  • Detect success and update your own UI

The five required settings

The whole experience is this configuration. There is no single “Apple Pay only” switch: restricting the payment methods removes the tabs and forms, hiding the two inputs removes the rest, and the error rule gives payment failures a place to render.
GlobalMessage: { display: "visible" } is essential for pay-button-only integrations. It renders payment failures (for example, a payment blocked by risk checks) directly above the button. Without it, this layout has no other surface for errors, so a failed payment looks like nothing happened.
To reach buyers on Android and desktop Chrome too, enable Google Pay — but keep exactly one wallet enabled per visitor, or the checkout shows a method selector in browsers that support both. Detect Apple Pay in your page and flip the flags:
This keeps the one-tap, single-button look everywhere: Apple Pay in Safari and on iPhone, Google Pay elsewhere.

Prerequisites

Create everything below in the same project: the server key that creates the order, the client key the widget renders with, and the Apple Pay domain registration all work together, and the payment sheet validates against the project the order belongs to.
1

Get API keys

From the Crossmint Console, under Integrate → API Keys, create:
  • A client key (ck_staging_...) with the orders.read scope, plus users.create, users.read, wallets.read, wallets.create, and wallets:balance.read if you also use Crossmint Auth and Wallets as the demo app does. Add every origin the app runs on (for example http://localhost:3000 and your public domain) to the key’s origins allowlist.
  • A server key (sk_staging_...) with the orders.create and orders.read scopes.
2

Get a public HTTPS domain

Apple Pay validates the merchant against the domain serving your page, so the domain must be publicly reachable over HTTPS — localhost cannot be registered. For local development, tunnel your dev server with ngrok (ngrok http 3000) and use the resulting domain.
3

Register the domain for Apple Pay

In the console, under Integrate → Apple Pay Domains:
  1. Download the verification file and serve it at https://<your-domain>/.well-known/apple-developer-merchantid-domain-association. In Next.js, place it at public/.well-known/apple-developer-merchantid-domain-association.
  2. Enter your domain and click Verify domain. The status turns to verified immediately once the file is reachable.
See the Apple Pay setup guide for framework-specific hosting details.
4

Have a recipient wallet

The purchased tokens are delivered to a Solana wallet address. Use any address you control, or create wallets for your users with Crossmint Wallets as the demo app does.

Integration

The example buys XMEME, the staging test token, on Solana. Production tokens will not work in the staging environment.
1

Add environment variables

Create .env.local in your Next.js project root:
2

Create the order server-side

Create app/checkout/actions.ts. The server key never reaches the browser, and the order carries the recipient and the receipt email, so the checkout never needs to ask for them:
The base URL must match your key’s environment: sk_staging_ keys pair with staging.crossmint.com and sk_production_ keys with www.crossmint.com.
3

Render only the Apple Pay button

Create app/checkout/page.tsx. Three details make the experience feel native:
  • Create the order as soon as the user picks an amount, not on a separate confirm button. Order creation takes a moment, so starting early means the Apple Pay button is ready by the time the user looks at it.
  • Show a loader until the iframe reports content via the ui:height.changed message, then reveal the button.
  • The Apple Pay button is the CTA. Do not add your own “Continue” button in front of it; one tap should open the payment sheet.
Note the key={order.orderId}: changing the amount creates a fresh order, and keying the component by order id remounts the widget cleanly instead of navigating the iframe in place.
4

Run your app

Visit website (e.g http://localhost:3000) to see your checkout!
5

Test with Apple Pay

Open your registered HTTPS domain (not localhost) in Safari 17+ on macOS, or on an iPhone running iOS 17+, with a card added to Apple Wallet. Use a physical device rather than the iOS Simulator, which does not render Apple Pay. In staging the payment is processed in the PSP’s sandbox, so the card in your Wallet is never actually charged.To exercise the full flow from any browser during development, temporarily flip the allowed methods to the card form and pay with the staging test card 4242 4242 4242 4242 (any future expiry, any CVC):
More on testing can be found here.

One-Tap on Mobile

The five settings above are order and URL parameters, so the same configuration renders inside a native app unchanged. The recommended integration paths are the Swift and Kotlin SDKs, which render and configure the checkout for you. If you prefer full control and do not want to add an SDK, create the order exactly as in step 2 and render the checkout in your own WebView following the Mobile WebView Integration guide:
Two things get simpler on mobile:
  • Simpler wallet detection. The operating system determines the wallet, so replace the ApplePaySession check from the tip above with a platform check: enable Apple Pay on iOS and Google Pay on Android (Platform.OS === "ios" in React Native), keeping one wallet enabled per device.
  • No Apple Pay domain registration. The checkout page is served from crossmint.com, which is already enabled for Apple Pay, so the domain steps in the prerequisites apply to the web integration only.
The Mobile WebView Integration guide covers the WebView configuration, order tracking from a native app, and mobile testing.

Troubleshooting

Apple could not validate the domain serving your page. Check, in order:
  1. You are on a registered domain. The page must be served from a domain that shows verified under Apple Pay Domains in the console — localhost always fails validation. Use your ngrok or production domain instead.
  2. The domain matches exactly. The registration is per host: app.example.com and example.com are different domains, and a new ngrok tunnel gets a new domain that needs registering again.
  3. Everything belongs to one project. The order (server key), the widget’s apiKey (client key), and the Apple Pay domain registration must all come from the same console project. If the order was created with a key from a different project, validation fails even with a verified domain.
  • Serve the page from your registered HTTPS domain: even in Safari, Apple Pay is unavailable on plain http://localhost, which surfaces as an “Apple Pay is not available” message. Open the app through your ngrok or production domain instead.
  • Apple Pay renders in Safari 17+ on macOS and iOS 17+; other browsers show nothing where the button would be (enable googlePay: true to cover them). Test in Safari or on an iPhone.
  • Use a physical device: the iOS Simulator does not render Apple Pay.
  • Confirm payment.fiat.allowedMethods sets applePay: true and the order was created successfully (the ui:height.changed message only fires once the iframe has content).
  • Add the exact origin the app runs on (scheme + host + port) to the client key’s origins allowlist in the console.
  • Match the base URL to the key environment: ck_staging_/sk_staging_ keys pair with staging.crossmint.com, production keys with www.crossmint.com.
The error names the missing scope. The widget’s client key needs orders.read; if you also use Crossmint Auth and Wallets like the demo app, add users.create, users.read, wallets.read, wallets.create, and wallets:balance.read. Edit the key’s scopes in the console under Integrate → API Keys.
Pass GlobalMessage: { display: "visible" } in appearance.rules. In the pay-button-only layout this rule is the only surface where payment errors (such as a payment blocked by risk checks) can render.

Next steps

Take it to mobile

Render the same experience inside your iOS, Android, or React Native app

Apple Pay domain setup

Host the domain association file and verify your domain

Customize UI

Match the button and messages to your brand

Payment methods

Enable Google Pay or cards with the same pattern

FAQ

Yes, you can use our embedded checkout or hosted checkout options. Contact our sales team to learn more about these solutions and find the best fit for your needs.
Crossmint KYC is currently disabled for memecoin purchases. This means that if your buyers typically have high risk profiles, their transaction may be declined. To increase conversion, please read the improving conversion guide.
When a user attempts a purchase, Crossmint puts a hold on their credit card. Only if the blockchain transaction succeeds, funds are captured. If the blockchain transaction fails, funds are released and the user is never charged.
The default transaction limit is 1,000 USD per user. A single wallet address is subject to a daily limit of 1,000 USD per user, the same as for NFT purchases. If you need a higher limit, speak to your Crossmint representative.
Yes, there is a volume limit of 1,000,000 USD worth of credit card purchases per day. This limit resets daily at 11:59 PM EST.
The order creation request includes several important parameters:Payment Object
  • method: Set to card for credit card payments
  • currency: Set to usd for US Dollar payments
  • receiptEmail: Optional. If the buyer pays with Apple Pay or Google Pay, Crossmint obtains the email automatically. Providing it improves fraud signal quality.
Line Items Object
  • tokenLocator: Specifies the memecoin token address in the format solana:tokenAddress
  • executionParameters:
    • mode: Set to "exact-in" for memecoin purchases (specifies exact USD amount to spend)
    • amount: Amount to purchase in USD
    • maxSlippageBps: Set to "500" for 5% slippage tolerance. If not provided, the default slippage will be applied from the available liquidity provider.
Response Parameters The API response includes a clientSecret and an order object with an orderId. Pass these to the Crossmint Embedded Checkout component to collect payment from the user.