@movmo/express-checkout-button
@movmo/express-checkout-button embeds the Movmo Express Checkout flow as a “Buy with Movmo” button on a partner page. When the user clicks it, the button persists the selected flight offer to Movmo and opens the Movmo checkout SPA in a full-screen iframe over the partner page. Payment, passenger collection, and the airline booking call all happen inside the iframe — the partner page stays in control of the surrounding URL.
It ships in two forms; both render the same button and the same checkout:
- A React component (
MovmoCheckoutButton) for React 16.8+ apps. - A self-contained script tag (since
1.9.0) for everything else — Vue, Angular, server-rendered templates, plain HTML. No build step, no framework, no npm install: the bundle carries React internally and injects its own styles.
| Current version | 1.10.1 |
| Peer dependencies | React version — React 16.8+ (16.8 / 17 / 18). Script-tag version — none. |
| License | MIT |
| npm | @movmo/express-checkout-button |
| CDN (script tag) | https://cdn.e2e.movmo.io/sdk/express-checkout/v1/express-checkout.js |
Install (React)
Section titled “Install (React)”npm install @movmo/express-checkout-buttonImport the package stylesheet once at app boot:
import "@movmo/express-checkout-button/dist/index.css";The CSS does not include Tailwind’s preflight reset, so it will not interfere with your existing styles. It imports the Inter font from Google Fonts; override font-family if you serve fonts locally.
Install (script tag — any stack)
Section titled “Install (script tag — any stack)”Add one script tag and mount the button into any element. This is the complete integration:
<div id="movmo-checkout-button" style="width: 240px; height: 44px"></div>
<script src="https://cdn.e2e.movmo.io/sdk/express-checkout/v1/express-checkout.js"></script><script> MovmoExpressCheckout.mount("#movmo-checkout-button", { iataCode: "PR", apiKey: "YOUR_MOVMO_API_KEY", providerID: "amadeus", flightOffer: selectedOffer, });</script>The /v1/ URL auto-updates across releases, which is why the example carries no integrity attribute — if your security policy requires script-integrity verification, use the version-pinned + SRI form below instead.
The bundle is fully self-contained (~66 KB gzipped): React is compiled in, and the button’s styles are injected at load — you do not need a <link> tag, a stylesheet import, or React on your page. If your page already runs React, the two copies do not interact; the button manages its own isolated root.
mount(target, options) takes a CSS selector or a DOM element, plus the same options as the React component’s props (below). It returns a handle:
const handle = MovmoExpressCheckout.mount(el, options);handle.unmount(); // remove the button and clean upMovmoExpressCheckout.version; // e.g. "1.10.1"mount() validates its options at the call boundary and throws with a message naming exactly what is missing (apiKey, iataCode, providerID, and one of flightOffer/flightOfferID), and throws if the selector matches no element. Calling mount() a second time on the same element logs a warning and returns the existing handle instead of double-rendering.
Version pinning and Subresource Integrity
Section titled “Version pinning and Subresource Integrity”The /v1/ URL above tracks the latest 1.x release — new releases apply automatically with no change on your side. If your security policy requires it (e.g. PCI DSS script-integrity controls), pin an exact version and verify it with SRI. Per-version bundles are immutable, and each release publishes its sha384 hash as a sidecar file next to the bundle — fetch https://cdn.e2e.movmo.io/sdk/express-checkout/{version}/express-checkout.js.sri (e.g. for 1.10.1) to get the exact integrity value:
<script src="https://cdn.e2e.movmo.io/sdk/express-checkout/1.10.1/express-checkout.js" integrity="sha384-keTPgRzv5TRDUPiYlyJDPj1qUHUWu0gbBYaB6j+Kqdzrp9HvvV8u1DX1NEe5mgiS" crossorigin="anonymous"></script>An SRI-pinned tag does not auto-update — you take new releases by updating the URL and hash together.
Mount the button (React)
Section titled “Mount the button (React)”import { MovmoCheckoutButton } from "@movmo/express-checkout-button";
<MovmoCheckoutButton iataCode="PR" apiKey={process.env.REACT_APP_MOVMO_API_KEY} providerID="amadeus" flightOffer={selectedOffer} isProd={false}/>;On click, the component:
- Saves the offer to Movmo via
createProviderOffer(POST /v1/flights/providers/{provider}/offers) — skipped if you pass a pre-savedflightOfferIDinstead of a rawflightOffer. - Pushes a
/movmo/checkoutURL ontowindow.historyso the partner page can route around the iframe. - Mounts a full-screen
<iframe>pointing athttps://flights.movmo.io/index.html#/booking?...(orhttps://flights.e2e.movmo.io/...whenisProdisfalse). - Listens for a
MOVMO_BOOKING_COMPLETEpostMessagefrom the iframe origin and, when the booking lands, replaces the checkout history entry with/movmo/checkout/booking/{bookingId}(areplaceState, not a push — so when the traveler later clicks “Return to airline” inside the confirmation, a single back-navigation lands on your real pre-checkout page instead of a stale checkout entry).
| Prop | Type | Required | Description |
|---|---|---|---|
iataCode | string | yes | Your airline IATA code (e.g. "PR", "AA"). Sent to Movmo as movmo-customer-id. |
apiKey | string | yes | Your Movmo API Gateway key. Sent as x-api-key. Provisioned at onboarding — see Authentication. |
providerID | string | yes | The flight provider the offer came from. One of: amadeus, sabre, duffel, darwin, dohop. |
flightOffer | object | conditional | The raw flight offer object from your provider. Required if flightOfferID is not set. |
flightOfferID | string | conditional | The Movmo offer UUID returned by a prior createProviderOffer call. Required if flightOffer is not set. |
isProd | boolean | no | true targets flights.movmo.io and api.movmo.io; false (default) targets flights.e2e.movmo.io and e2e.api.movmo.io. |
className | string | no | Appended to the button’s class list. Useful for w-full and similar layout tweaks (React consumers — see Styling). |
onExit | function | no | Called after the user leaves checkout. Receives { reason }: "search_again" or "offer_unavailable" when checkout ended without a booking, or "booking_confirmed" when the traveler clicked “Return to airline” on the confirmation screen (the booking succeeded — a MOVMO_BOOKING_COMPLETE was already delivered for it). The overlay is already removed and the URL restored when it fires. Use it to return the user to the right step of your flow. |
Exactly one of flightOffer or flightOfferID must be set. The two props are mutually exclusive: pass the raw offer if you have not persisted it yet, or the saved offer ID if you already have. The script-tag mount() accepts exactly this options object.
Styling
Section titled “Styling”No styling is required. The button ships with Movmo’s standard look (the “Express Checkout” label with the Movmo badge) fully self-contained — in React via the package stylesheet, in the script-tag build via auto-injected styles. Dropping it onto an unstyled page yields the finished, branded button; you only control the size of the element you mount it into.
To match your own design system:
- React: pass utility classes via
className— your app’s build compiles them, so anything goes. - Script tag:
classNameonly toggles classes that exist inside the bundle’s own stylesheet, so arbitrary utility classes from your page’s framework will not take effect. Style with plain CSS descendant selectors on your mount container instead:
#movmo-checkout-button button { background: #fafaf9; border: 1px solid #d5d7da; border-radius: 8px; color: #525961;}Keep the button’s “Express Checkout” label and Movmo badge visible — they are part of the checkout trust contract shown to travelers.
Two usage modes
Section titled “Two usage modes”Raw offer (most common)
Section titled “Raw offer (most common)”The partner page already has the user’s selected offer (e.g. from an Amadeus search response). Pass the raw object — the component persists it on click:
<MovmoCheckoutButton iataCode="PR" apiKey={process.env.REACT_APP_MOVMO_API_KEY} providerID="amadeus" flightOffer={{ offer: amadeusOffer, dictionaries: amadeusDictionaries, }}/>The component sends the object as-is to createProviderOffer. Movmo’s backend understands each provider’s native shape — no client-side normalization is required.
Pre-saved offer ID
Section titled “Pre-saved offer ID”The partner has already saved the offer to Movmo (for example, after a server-side search) and only needs to launch checkout:
<MovmoCheckoutButton iataCode="PR" apiKey={process.env.REACT_APP_MOVMO_API_KEY} providerID="amadeus" flightOfferID="33333333-3333-3333-3333-333333333333"/>No persistence call is made — the iframe loads immediately.
Iframe lifecycle and postMessage
Section titled “Iframe lifecycle and postMessage”Once mounted, the iframe overlay is full-screen (position: fixed, z-index: 9999). The Movmo checkout SPA inside handles passenger collection, payment-method selection, and the airline booking call.
On a successful booking, the iframe posts a message with this shape:
type MovmoBookingComplete = { type: "MOVMO_BOOKING_COMPLETE"; bookingId: string; airlineRef: string; providerId: string;};The component listens only to messages from flights.movmo.io (or flights.e2e.movmo.io in pre-prod) and pushes /movmo/checkout/booking/{bookingId} onto the partner page’s history. If you want to redirect to your own confirmation page or fire analytics, listen to the same message on your end and intercept it before the component’s listener runs.
If checkout ends without a booking — the offer expired or became unavailable and the traveler chose to go back — the iframe posts MOVMO_BOOKING_FAILED with a reason. The button closes the overlay, restores the URL, and then invokes your onExit({ reason }) callback so you can return the traveler to the right step (your search results, your passenger form, etc.). If you don’t pass onExit, the overlay simply closes in place.
Security
Section titled “Security”The button’s footprint on your page, the PCI boundary for card data, and Movmo’s data-handling practices are covered on the Security & data handling page — useful reading for your security team during integration review.
Underlying REST operation
Section titled “Underlying REST operation”The SDK calls one OpenAPI operation in raw-offer mode:
| Operation | Path |
|---|---|
createProviderOffer | POST /v1/flights/providers/{provider}/offers |
In flightOfferID mode, no REST call is made from the partner page — the iframe handles all subsequent calls itself. Full schemas in the OpenAPI spec under tags: [Flights].