Skip to main content
The Buy Button frame takes a quote signature to payment. It renders the button that matches the payment method in the quote: Apple Pay, Google Pay, or a saved card. When the customer taps it, it opens the native Apple Pay or Google Pay sheet, or goes straight to the purchase for a card. Behind the button, it evaluates transaction requirements, creates the transaction, and completes post-transaction processing. Pass headless=true to run the same purchase with no button. The frame renders nothing and starts the purchase as soon as it loads, so your confirmation screen, loading states, and purchase button stay fully under your control. Use headless mode for card and bank transfer quotes when you draw your own button.
The Buy Button frame replaces the Apple Pay, Google Pay, and Buy frames. If you load /platform/v1/apple-pay or /platform/v1/google-pay, MoonPay serves this frame. If you load /platform/v1/buy, MoonPay serves this frame in headless mode. Existing integrations keep working, and the events below apply to them.
For platform-specific walkthroughs, see manual integration: Web, React Native, Flutter, iOS, and Android.

URL

Payment methods

The quote’s paymentMethod.type decides which mode to use. Card works in both modes. SEPA bank transfer has no button to tap, so it runs in headless mode. Apple Pay and Google Pay open a payment sheet from the customer’s tap, so they run with the button. If the quote’s payment method doesn’t run in the mode you load, the frame sends error with unsupportedPaymentMethod. Guest checkout runs with the button only. Card and bank transfer quotes need a connected customer, not a guest. See Pay with card and Pay with bank transfer.

Requirements

Size

With the button, the frame container height must be 44px. Width is flexible; the button inside the frame uses 100% of the container width. If you set a custom height through applePayButtonSettings or googlePayButtonSettings, size the frame container to match. In headless mode, the frame renders nothing. Mount it with zero dimensions so it does not affect your layout:
Example

Permissions

The payment permission policy is required for Apple Pay and Google Pay. If you use the MoonPay web SDK to render the frame, it sets this for you; the guidance below applies to manual integrations, where you build the iframe yourself.
Example
In test mode, the frame simulates the wallet payment with a JavaScript window.prompt dialog, for both Apple Pay and Google Pay. If your iframe uses the sandbox attribute, include allow-modals.

Sandboxed iframe (PCI DSS v4)

If you render the frame in a sandboxed iframe (for example, for PCI DSS v4 compliance), include allow-scripts allow-popups allow-same-origin allow-forms. Sandboxing is one way to isolate the frame when Google Pay’s pay.js cannot provide a stable Subresource Integrity hash. See Google Pay inside sandboxed iframe for PCI DSS v4 compliance for details. When you use the sandbox attribute, each value serves a specific purpose:
  • allow-scripts runs pay.js.
  • allow-popups opens the payment window on user interaction.
  • allow-same-origin grants the browser storage and cookie access needed for compatibility.
  • allow-forms submits the Google Pay sign-in form.
  • allow-modals shows the test-mode payment dialog (window.prompt).

Mobile WebViews

Rendering the frame inside a mobile WebView has additional requirements:
  • Apple Pay in an iOS WKWebView: handle the JavaScript dialogs the frame uses. See WKWebView.
  • Google Pay in an Android WebView: needs the AndroidX WebKit dependency, intent queries, the Payment Request API enabled, and an approved app. See Android WebView.
  • Google Pay in an iOS WKWebView: not available. Offer another payment method. See iOS WKWebView.

WKWebView

In a native iOS app, you render the frame inside a WKWebView. The frame communicates with the host app through the postMessage-based frames protocol. See Events below. You must handle JavaScript dialogs. The frame uses window.prompt to present the simulated Apple Pay sheet in test mode. By default a WKWebView silently dismisses alert, confirm, and prompt: window.prompt returns null with no UI shown, and the frame reads that as the customer cancelling, so the transaction comes back with status: "failed". To fix this, conform your view controller to WKUIDelegate, set webView.uiDelegate, and present the text input panel, for example as a UIAlertController with a text field:
Returning a string (even an empty one) authorizes the test payment; returning nil declines it. Wire this even if you only ship live mode, since the default behavior applies to any dialog the frame surfaces. For a complete native walkthrough that covers rendering the frame, wiring the bridge and events, and the full dialog handler, see iOS manual integration.

Initialization parameters

Headless mode

Pass headless=true when you draw your own purchase button. The frame renders nothing: after the handshake, it sends ready and starts the purchase straight away. Mount the frame when the customer confirms the purchase in your UI, not before, because mounting it is what starts the purchase. Headless mode runs the same steps as a tap on the card button, and uses the same events, with these differences:
  • It accepts card and SEPA bank transfer quotes only. See Payment methods.
  • It needs a connected customer. A guest checkout quote gets error with invalidQuote.
  • It never sends buttonPressed or cancelled.
  • After ready, it sends exactly one final event: complete, challenge, or error. Every quote that decodes gets ready first, including one the frame then rejects with unsupportedPaymentMethod or invalidQuote.
  • It reads setQuote only after an error with quoteExpired. The new quote starts a new attempt. At any other time the frame ignores setQuote, so it never starts a second purchase.
  • On challenge, open the challenge frame without resume. The challenge frame finishes the purchase and sends its own complete.

Bank transfer

For a SEPA bank transfer quote, the frame creates the transaction, but the customer still has to send the money from their bank. MoonPay doesn’t show the bank details, so you do:
  1. Read the transaction id from complete.
  2. Fetch the transaction with that id from the transaction endpoint, or with client.getTransaction() in the SDK.
  3. Show the customer the bank details from bankTransferDepositInfo and the amount to send from source.amount.
The customer must include the payment reference from bankTransferDepositInfo with their transfer, or it is rejected. See Pay with bank transfer.

Apple Pay button settings

The Buy Button frame renders the button that matches the payment method in the quote signature. applePayButtonSettings styles the Apple Pay button variant only. It has no effect when the frame renders the card or Google Pay button, or runs in headless mode. Use applePayButtonSettings to style the Apple Pay button without injecting custom CSS. Build a settings object, pass it through JSON.stringify, URL-encode the result, and append it to the frame URL. Every property is optional. Any property you omit keeps the button’s default. Dimensions accept pixels only. A bare number is treated as pixels and normalized to a px value.

Validation

Validation applies each property on its own:
  • A property with an invalid value is ignored, and the remaining valid properties still apply. Invalid values include a non-pixel dimension such as 50%, and a style, type, or locale outside its allowed set.
  • If the whole value is not valid JSON, the frame ignores applePayButtonSettings entirely and uses the defaults.

Example

This object sets a 52px-tall white button with a 26px corner radius, the buy label, and French text:
Run it through JSON.stringify, URL-encode the result, and append it to the frame URL:
If you set a custom height, size the frame container to match.

Google Pay button settings

googlePayButtonSettings styles the Google Pay button variant only. It has no effect when the frame renders the card or Apple Pay button, or runs in headless mode. You can pass both applePayButtonSettings and googlePayButtonSettings on the same frame URL. The frame applies the one that matches the button it renders. Build a settings object, pass it through JSON.stringify, URL-encode the result, and append it to the frame URL. Every property is optional. Any property you omit keeps the button’s default. Unlike Apple Pay, the Google Pay button has no type (label) or locale setting.

Validation

Validation applies each property on its own:
  • A property with an invalid value is ignored, and the remaining valid properties still apply. Dimensions must match ^\d+(\.\d+)?(px)?$, so a value in another unit, such as 50% or 2rem, is ignored. A style outside its allowed set is ignored.
  • The frame ignores googlePayButtonSettings entirely and uses the defaults if the value is not valid JSON, is longer than 4,096 characters, or is not a JSON object.

Example

This object sets a 48px-tall black button with a 28px corner radius:
Run it through JSON.stringify, URL-encode the result, and append it to the frame URL:
If you set a custom height, size the frame container to match.

Events

All events are dispatched using the message pattern described in the frames protocol. Headless mode uses the same events; see Headless mode for which ones it sends.

Outbound events

frame->parent These events are sent from this frame to the parent window.

handshake

The frame requests that you open a message channel.

ready

With the button, the frame finished loading and the payment button is rendered. Use this to coordinate UI transitions if needed. The frame sends ready again after a setQuote with a different currency has been checked. In headless mode, the purchase is starting. Use this to show a loading indicator in your UI. After ready, the frame sends exactly one final event: complete, challenge, or error.

buttonPressed

The customer tapped the pay button. The frame emits this the instant the button is tapped. For Apple Pay and Google Pay, it fires before the OS presents the payment sheet (PassKit on iOS, the Google Pay sheet on Android). For card, it fires before the card pipeline runs. Treat it as an intent-to-buy signal: use it to react to the tap, for example to show a loading state or fire analytics. Headless mode does not send this event. This event carries no payload. Route it by channelId. buttonPressed is not a purchase outcome. It still fires if the customer opens the payment sheet and then cancels, and it fires before any authorization happens. For the transaction result, listen for complete, which reports the outcome once the payment resolves.

complete

The purchase finished. Check transaction.status before treating it as a success:
  • Not failed: the transaction was created and payment is processing. Use the transaction id to track final status, for example by polling or via webhooks. For a bank transfer quote, also use the id to fetch the bank details to show the customer. See Bank transfer.
  • failed: the payment did not go through. failureCode says why and failureReason gives developer-facing details. If id is present, the transaction exists and you can fetch it. If id is missing, the transaction was never created, so there is nothing to poll.
After a guest checkout recovery that finished on this button with resume, the payload also carries sessionUpgraded: true: create a new session and check the connection again. See Finish on your buy button.
Failure codes
Branch on failureCode, not failureReason, for programmatic handling.
Quote expiry is reported through the error event with code: "quoteExpired", not through complete. Listen for both events to cover all failure paths.

challenge

Verification is required before the transaction can proceed. Render the challenge frame at the provided URL. Do not construct the URL yourself — use it as-is. For an Apple Pay or Google Pay quote, open the challenge frame with resume to have the customer pay on this button once the challenge is done. Keep this button mounted. The challenge frame then emits resolved, and the customer taps this button again. See Finish on your buy button. For a card or bank transfer quote, including headless mode, open the challenge frame without resume. It finishes the purchase and sends complete.

cancelled

The customer dismissed the Apple Pay or Google Pay sheet without paying. This is a cancellation, not a failure, so handle it separately from error. The button stays on screen and the customer can tap it again. Card payments and headless mode do not send this event. The payload carries code: "payment_sheet_dismissed" and a developer-facing message. The message is not intended to be rendered in UI.

error

The purchase could not run. Remove the frame and log the message for debugging, except after quoteExpired: keep the frame and send a new quote with setQuote. error covers setup problems, an expired quote, and unexpected errors (generic). A payment that fails arrives as a complete event with status: "failed".

Inbound events

parent->frame These events are sent from the parent window to this frame.

ack

Acknowledge the handshake.

setQuote

Provide a new quote to the frame. Pass signature as returned by the quote endpoint. With the button, send this when the current quote expires before the customer completes the purchase. In headless mode, send this only after an error with quoteExpired. The frame retries the purchase with the new quote. At any other time it ignores setQuote, so it never starts a second purchase.