> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withgale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Checkout

> Keep customers on your site with iframe-embedded checkout - the perfect balance of simplicity and branding

# Embedded Checkout Integration

Embed Gale's secure checkout directly on your website using an iframe. Customers stay on your domain while Gale handles payment processing, eligibility verification, and compliance.

## When to Use Embedded Checkout

<Check>Perfect for you if:</Check>

* You want customers to stay on your website during checkout
* You need a branded checkout experience
* You're building a platform or multi-tenant solution
* You want Gale to handle checkout UI and compliance
* You can integrate via API to create checkout sessions

## How It Works

1. **Customer adds items** to cart on your website
2. **Your site** creates a checkout session via `POST /api/v2/checkout`
3. **Gale API returns** a `checkout_url`
4. **Your site embeds** the checkout URL in an iframe
5. **Customer completes** payment in the iframe (stays on your domain)
6. **Gale processes** the payment securely
7. **Gale sends** webhook notification with `order.created` event
8. **Your site shows** confirmation to customer

## Implementation Steps

<Steps>
  <Step title="Create Checkout Session via API">
    When customer is ready to checkout, create a checkout session with their items
  </Step>

  <Step title="Embed the Checkout Iframe">
    Use the returned `checkout_url` in an iframe on your checkout page
  </Step>

  <Step title="Handle Payment Confirmation">
    Listen for webhooks or postMessage events to confirm payment
  </Step>

  <Step title="Fulfill the Order">
    Process the order and show confirmation to customer
  </Step>
</Steps>

## Step 1: Create Checkout Session

Create a checkout session with customer info and line items.

> All monetary amounts are integers in cents (e.g., 2995 = \$29.95).

```bash theme={null}
POST /api/v2/checkout
```

```json theme={null}
{
  "customer": {
    "email": "customer@example.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "phone": "+1-555-123-4567"
  },
  "reference_id": "your-cart-123",
  "line_items": [
    {
      "product_id": "PROD-001",
      "name": "Digital Thermometer",
      "quantity": 1,
      "price": 2995,
      "currency": "USD"
    }
  ],
  "shipping_info": {
    "address_line_1": "123 Main St",
    "city": "New York",
    "state": "NY",
    "postal_code": "10001",
    "country": "US"
  },
  "shipping": 995,
  "tax": 245,
  "success_url": "https://yoursite.com/checkout/success",
  "failure_url": "https://yoursite.com/checkout/cancel",
  "metadata": {
    "platform": "your_platform"
  }
}
```

**Response:**

```json theme={null}
{
  "checkout_id": "checkout_abc123xyz",
  "checkout_url": "https://checkout.withgale.com/c/checkout_abc123xyz",
  "status": "open",
  "expires_at": "2025-10-19T14:30:00Z",
  "created_at": "2025-10-18T14:30:00Z"
}
```

## Step 2: Embed Checkout

Use the `checkout_url` in an iframe on your page:

<Tabs>
  <Tab title="HTML">
    ```html theme={null}
    <!DOCTYPE html>
    <html>
    <head>
      <title>Checkout - Your Store</title>
      <style>
        #gale-checkout {
          width: 100%;
          height: 600px;
          border: none;
          border-radius: 8px;
        }

        .checkout-container {
          max-width: 800px;
          margin: 0 auto;
          padding: 20px;
        }
      </style>
    </head>
    <body>
      <div class="checkout-container">
        <h1>Complete Your Purchase</h1>
        <iframe
          id="gale-checkout"
          src="https://checkout.withgale.com/c/checkout_abc123xyz"
          allow="payment">
        </iframe>
      </div>

      <script>
        // Listen for checkout completion
        window.addEventListener('message', (event) => {
          if (event.origin !== 'https://checkout.withgale.com') return;

          if (event.data.type === 'checkout.completed') {
            // Redirect to success page
            window.location.href = '/checkout/success?order_id=' + event.data.order_id;
          }

          if (event.data.type === 'checkout.cancelled') {
            // Handle cancellation
            window.location.href = '/checkout/cancel';
          }
        });
      </script>
    </body>
    </html>
    ```
  </Tab>

  <Tab title="React">
    ```jsx theme={null}
    import { useEffect, useState } from 'react';

    function GaleCheckout({ cartId }) {
      const [checkoutUrl, setCheckoutUrl] = useState(null);

      useEffect(() => {
        // Create checkout session
        const createCheckout = async () => {
          const response = await fetch('https://api.withgale.com/api/v2/checkout', {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${process.env.GALE_API_KEY}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              // checkout data
            })
          });

          const { checkout_url } = await response.json();
          setCheckoutUrl(checkout_url);
        };

        createCheckout();
      }, []);

      useEffect(() => {
        // Listen for checkout events
        const handleMessage = (event) => {
          if (event.origin !== 'https://checkout.withgale.com') return;

          if (event.data.type === 'checkout.completed') {
            // Handle success
            window.location.href = `/success?order=${event.data.order_id}`;
          }
        };

        window.addEventListener('message', handleMessage);
        return () => window.removeEventListener('message', handleMessage);
      }, []);

      if (!checkoutUrl) return <div>Loading checkout...</div>;

      return (
        <div className="checkout-container">
          <h1>Complete Your Purchase</h1>
          <iframe
            src={checkoutUrl}
            width="100%"
            height="600px"
            allow="payment"
            style={{ border: 'none', borderRadius: '8px' }}
          />
        </div>
      );
    }

    export default GaleCheckout;
    ```
  </Tab>

  <Tab title="Next.js">
    ```tsx theme={null}
    'use client';

    import { useEffect, useState } from 'react';
    import { useRouter } from 'next/navigation';

    export default function CheckoutPage() {
      const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);
      const router = useRouter();

      useEffect(() => {
        // Create checkout session on mount
        const initCheckout = async () => {
          const response = await fetch('/api/create-checkout', {
            method: 'POST',
            body: JSON.stringify({
              items: [...], // your cart items
              customer: {...} // customer info
            })
          });

          const { checkout_url } = await response.json();
          setCheckoutUrl(checkout_url);
        };

        initCheckout();
      }, []);

      useEffect(() => {
        const handleCheckoutEvent = (event: MessageEvent) => {
          if (event.origin !== 'https://checkout.withgale.com') return;

          switch (event.data.type) {
            case 'checkout.completed':
              router.push(`/success?order=${event.data.order_id}`);
              break;
            case 'checkout.cancelled':
              router.push('/checkout/cancel');
              break;
          }
        };

        window.addEventListener('message', handleCheckoutEvent);
        return () => window.removeEventListener('message', handleCheckoutEvent);
      }, [router]);

      return (
        <div className="max-w-4xl mx-auto p-6">
          <h1 className="text-2xl font-bold mb-6">Secure Checkout</h1>
          {checkoutUrl ? (
            <iframe
              src={checkoutUrl}
              className="w-full h-[600px] border-0 rounded-lg"
              allow="payment"
            />
          ) : (
            <div>Loading...</div>
          )}
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

## Step 3: Handle Payment Confirmation

### Option A: Webhooks (Recommended)

```javascript theme={null}
// /api/webhooks/gale
app.post('/webhooks/gale', async (req, res) => {
  const event = req.body;

  // Verify webhook signature
  const isValid = verifyGaleSignature(
    req.headers['x-gale-signature'],
    req.body
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  switch (event.type) {
    case 'order.created':
      // Order was created successfully
      const { id, customer, line_items } = event.data;

      await fulfillOrder(id, line_items);
      await sendConfirmationEmail(customer.email);
      break;

    case 'checkout.failed':
      // Payment failed
      await handlePaymentFailure(event.data.checkout_id);
      break;
  }

  res.status(200).send('OK');
});
```

[Learn more about Webhooks →](/developer-manual/webhooks)

### Option B: PostMessage Events

Listen for iframe postMessage events:

```javascript theme={null}
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://checkout.withgale.com') return;

  switch (event.data.type) {
    case 'checkout.completed':
      // Payment successful
      console.log('Checkout ID:', event.data.checkout_id);
      console.log('Order ID:', event.data.order_id);

      // Show success message or redirect
      window.location.href = '/success';
      break;

    case 'checkout.cancelled':
      // Customer cancelled
      window.location.href = '/checkout';
      break;

    case 'checkout.error':
      // Error occurred
      console.error('Checkout error:', event.data.error);
      break;
  }
});
```

### Option C: Polling (Not Recommended)

Check order status via webhooks instead of polling. If you must poll, use the Order API:

```javascript theme={null}
const checkOrderStatus = async (orderId) => {
  const response = await fetch(
    `https://api.withgale.com/api/v2/orders/${orderId}`,
    {
      headers: {
        'Authorization': `Bearer ${API_KEY}`
      }
    }
  );

  const order = await response.json();

  if (order.status === 'completed') {
    // Payment completed
    return true;
  }

  return false;
};
```

## Subscription Support

Embedded checkout fully supports subscriptions:

```json theme={null}
{
  "line_items": [
    {
      "product_id": "MONTHLY_VITAMINS",
      "name": "Monthly Vitamin Subscription",
      "quantity": 1,
      "price": 2999,
      "currency": "USD"
    }
  ],
  "payment_type": "subscription",
  "subscription": {
    "interval": "monthly",
    "interval_count": 1,
    "trial_period_days": 7
  }
}
```

Customers will see subscription terms in the checkout and agree to recurring billing.

## Customization

### Iframe Styling

```css theme={null}
#gale-checkout {
  width: 100%;
  max-width: 800px;
  height: 650px;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

/* Responsive */
@media (max-width: 768px) {
  #gale-checkout {
    height: 700px;
  }
}
```

### Success/Failure URLs

Customize where customers land after checkout:

```json theme={null}
{
  "success_url": "https://yoursite.com/thank-you?session_id={CHECKOUT_SESSION_ID}",
  "failure_url": "https://yoursite.com/checkout/cancel"
}
```

Variables available:

* `{CHECKOUT_SESSION_ID}` - Checkout session identifier
* `{ORDER_ID}` - Order identifier (after payment)
* `{CUSTOMER_EMAIL}` - Customer's email

## Platform Integration Examples

### Multi-Tenant Platform

```javascript theme={null}
// Create checkout for specific merchant/store
const createMerchantCheckout = async (merchantId, storeId, checkoutData) => {
  const response = await fetch('https://api.withgale.com/api/v2/checkout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${getMerchantApiKey(merchantId)}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      ...checkoutData,
      metadata: {
        merchant_id: merchantId,
        store_id: storeId,
        platform: 'your_platform_name'
      }
    })
  });

  return response.json();
};
```

### Shopify App Example

```javascript theme={null}
// Shopify app creating embedded checkout
app.post('/shopify/create-checkout', async (req, res) => {
  const { shop, cart } = req.body;

  // Create Gale checkout session
  const galeCheckout = await fetch('https://api.withgale.com/api/v2/checkout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${getShopApiKey(shop)}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      customer: {
        email: cart.customer.email,
        first_name: cart.customer.firstName,
        last_name: cart.customer.lastName,
        phone: cart.customer.phone
      },
      line_items: cart.lineItems.map(item => ({
        product_id: item.id,
        name: item.title,
        price: Math.round(item.price * 100), // Convert to cents
        quantity: item.quantity,
        currency: 'USD'
      })),
      metadata: {
        shop: shop
      }
    })
  });

  const checkout = await galeCheckout.json();
  res.json({ checkout_url: checkout.checkout_url });
});
```

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Verify Webhooks" icon="shield-check">
    Always verify webhook signatures before processing
  </Card>

  <Card title="Use HTTPS" icon="lock">
    Only embed checkout on HTTPS pages
  </Card>

  <Card title="Validate Origins" icon="check-circle">
    Check event.origin in postMessage listeners
  </Card>

  <Card title="Secure API Keys" icon="key">
    Never expose API keys in frontend code
  </Card>
</CardGroup>

## Testing

### Test Mode

Use test API keys for development:

```bash theme={null}
Authorization: Bearer glm_test_YOUR_TEST_KEY
```

### Test the Flow

1. Create a test cart
2. Embed checkout iframe
3. Use test HSA card: `4111111111111111`
4. Complete payment
5. Verify webhook received

[See all test cards →](/resources/test-cards)

## Troubleshooting

<AccordionGroup>
  <Accordion title="Iframe not loading">
    **Possible causes:**

    * Incorrect checkout\_url
    * Missing `allow="payment"` attribute
    * HTTPS required
    * Content Security Policy blocking iframe

    **Solution:** Check browser console for errors, ensure HTTPS, add CSP exception
  </Accordion>

  <Accordion title="Not receiving postMessage events">
    **Solution:** Verify origin check matches `https://checkout.withgale.com` exactly
  </Accordion>

  <Accordion title="Checkout shows 'Session Expired'">
    **Solution:** Checkout sessions expire after 24 hours. Create a new checkout session.
  </Accordion>

  <Accordion title="Mobile iframe issues">
    **Solution:** Some mobile browsers have iframe limitations. Consider using redirect flow (payment links) for mobile.
  </Accordion>
</AccordionGroup>

## API Reference

Complete API documentation:

* [Create Checkout Session](/api-reference/endpoint/create-checkout-v2)
* [Get Order](/api-reference/endpoint/get-order)
* [Order Object](/api-reference/objects/order)

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhooks Setup" icon="bell" href="/developer-manual/webhooks">
    Configure real-time payment notifications
  </Card>

  <Card title="Custom API" icon="code" href="/integration/custom-api">
    Need more control? Try custom API integration
  </Card>

  <Card title="Subscriptions" icon="repeat" href="/api-reference/objects/subscription">
    Learn about subscription management
  </Card>

  <Card title="Test Cards" icon="credit-card" href="/resources/test-cards">
    Test your integration with test HSA cards
  </Card>
</CardGroup>

## Support

Questions about embedded checkout?

* 📧 Email: [support@withgale.com](mailto:support@withgale.com)
* 📚 [API Reference](/api-reference/introduction)
* 💬 [Contact Support](https://www.withgale.com/contact/support)
