# Wix Checkout Integration

Accept payments via Wix Checkout - a hosted payment page that handles collection of customer information and payment processing itself.

For webhooks (order approved, subscription canceled/expired), see the [webhooks doc](https://www.wix.com/_serverless/payments-checkout-server/statics/payments-checkout-sessions/llms-general-webhooks.md).
For subscription management (get/cancel APIs), see the [subscription management doc](https://www.wix.com/_serverless/payments-checkout-server/statics/payments-checkout-sessions/llms-general-subscriptions.md).

## Prerequisites

- `WIX_API_KEY` - API key for authentication (secret - never expose to client)
- `WIX_SITE_ID` - Site identifier for the merchant account

**Security:** `WIX_API_KEY` must be kept secret. Always call the Checkout API from your backend server, never from client-side code.

**Note:** Testing/sandbox mode is not currently supported.

## Basic Flow

1. Create Checkout Session with cart items
2. Redirect user to checkout page
3. User completes payment
4. (Optional) Receive order details via webhook — **required for subscriptions** (see Subscriptions section)

---

## Authentication

All API calls require these headers:

```
Authorization: {WIX_API_KEY}
wix-site-id: {WIX_SITE_ID}
```

---

## Create Checkout Session

**Endpoint:** `POST https://www.wixapis.com/payments/platform/v1/checkout-sessions/construct`

Creates a checkout session and returns a URL to redirect the buyer. You can optionally pass `customerInfo` to pre-fill the checkout form with known customer details (email, name, phone), so buyers don't have to re-enter their details.

### Request

```typescript
interface ConstructCheckoutSessionRequest {
  cart: {
    items: Array<{
      name: string;      // 1-255 chars
      quantity: number;  // min 1
      price: string;     // Per-unit price (e.g., "19.99") in merchant account currency.
      subscriptionInfo?: SubscriptionInfo;  // Optional. Makes this item a recurring subscription. See Subscriptions section.
    }>;  // max 100 items
    customerInfo?: {              // Optional. Pre-fill checkout form with known customer details.
      email?: string;            // Email address
      firstName?: string;        // max 100 chars
      lastName?: string;         // max 100 chars
      phone?: string;            // Phone number
    };
  };
  callbackUrls: {
    postFlowUrl: string;       // Required. URL used to redirect buyer if they click on "Continue browsing". Usually leads to original site URL
    thankYouPageUrl: string;   // Required. URL of thank you page. Buyer is redirected here after successful payment
  };
}
```

**Payment Failure:** If payment fails, the buyer remains on the checkout page to retry. There is no failure callback URL.

### Response

```typescript
interface ConstructCheckoutSessionResponse {
  checkoutSession: {
    id: string;          // GUID - equals order.checkoutId in webhook
    redirectUrl: string; // Redirect buyer here
  };
}
```

### Example (Backend)

```typescript
// Server-side code - never expose WIX_API_KEY to the client
const WIX_API_KEY = process.env.WIX_API_KEY;
const WIX_SITE_ID = process.env.WIX_SITE_ID;

async function createCheckout(
  items: Array<{ name: string; quantity: number; price: string }>,
  customerInfo?: { email?: string; firstName?: string; lastName?: string; phone?: string }
) {
  const response = await fetch(
    "https://www.wixapis.com/payments/platform/v1/checkout-sessions/construct",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": WIX_API_KEY,
        "wix-site-id": WIX_SITE_ID,
      },
      body: JSON.stringify({
        cart: { items, customerInfo },
        callbackUrls: {
          postFlowUrl: "https://myapp.com",
          thankYouPageUrl: "https://myapp.com/order-complete",
        },
      }),
    }
  );

  const data = await response.json();
  return data.checkoutSession; // Return redirectUrl to client
}
```

### Error Handling

The API returns errors in this format:

```typescript
interface ErrorResponse {
  message: string;
  details: {
    applicationError: {
      code: string;
      description: string;
      data?: {
        fieldViolations?: Array<{
          field: string;
          description: string;
        }>;
      };
    };
  };
}
```

**Validation Error Example (400):**
```json
{
  "message": "failed field validations",
  "details": {
    "applicationError": {
      "code": "INVALID_ARGUMENT",
      "description": "failed field validations",
      "data": {
        "fieldViolations": [
          { "field": "cart.items[0].price", "description": "must be a non-negative decimal" }
        ]
      }
    }
  }
}
```

**Permission Denied Example (403):**
```json
{
  "message": "Caller with id: <app-id> doesn't have permission to site: <site-id>",
  "details": {
    "applicationError": {
      "code": "PERMISSION_DENIED",
      "description": "Caller with id: <app-id> doesn't have permission to site: <site-id>"
    }
  }
}
```

---

## Subscriptions

Items can be configured as recurring subscriptions by adding `subscriptionInfo` to a line item. A single checkout can contain both one-time and subscription items (mixed cart).

**IMPORTANT — Webhook requirement:** If your cart includes subscription items, you MUST set up the order approved webhook to store `lineItems[].subscriptionInfo.id`. This ID is the only way to match future subscription canceled/expired webhooks to the original order. See the [webhooks doc](https://www.wix.com/_serverless/payments-checkout-server/statics/payments-checkout-sessions/llms-general-webhooks.md).

**Paywall flows (async activation + linking to the logged-in user):** activation is asynchronous (`subscriptionInfo.id` is only available via the webhook), and correlation to your app user is done via `checkoutSession.id`, not the buyer-typed email. See [Paywall Implementation Pattern](https://www.wix.com/_serverless/payments-checkout-server/statics/payments-checkout-sessions/llms-general-subscriptions.md#paywall-implementation-pattern).

### Subscription Types

```typescript
type SubscriptionFrequency = "DAY" | "WEEK" | "MONTH" | "YEAR";

interface SubscriptionInfo {
  subscriptionSettings: SubscriptionSettings;  // Required. Core billing configuration.
  title?: string;         // max 150 chars. Subscription name shown to buyer on checkout page.
  description?: string;   // max 500 chars. Subscription description shown to buyer on checkout page.
}

interface SubscriptionSettings {
  frequency: SubscriptionFrequency;  // Required. Billing frequency unit.
  interval?: number;       // How many frequency units between charges. Default: 1. Range: 1-3650.
                           // For DAY frequency, minimum is 7 (weekly).
  autoRenewal?: boolean;   // Whether subscription auto-renews. See auto-renewal logic below.
  billingCycles?: number;  // Number of charges before stopping. Omit for infinite renewal. Min: 1.
                           // Cannot be set when autoRenewal is true.
  freeTrialPeriod?: {
    frequency: SubscriptionFrequency;  // Required. Trial period frequency unit.
    interval: number;                  // Duration in frequency units (e.g., 14 with DAY = 14-day trial). Min: 1, max: 999.
  };
}
```

### Auto-Renewal Logic

| Configuration | Result |
|---------------|--------|
| `autoRenewal` explicitly set | Uses the provided value |
| `billingCycles` set, `autoRenewal` omitted | `autoRenewal = false` (finite plan) |
| Both omitted | `autoRenewal = true` (infinite/evergreen plan) |

**Constraint:** `autoRenewal: true` and `billingCycles` cannot both be set — they are mutually exclusive.

### Validation Rules

| Rule | Error |
|------|-------|
| `subscriptionSettings` is required when `subscriptionInfo` is provided | INVALID_ARGUMENT |
| `frequency` must be specified (not UNDEFINED) | INVALID_ARGUMENT |
| DAY frequency requires `interval >= 7` | INVALID_ARGUMENT |
| `billingCycles` cannot be set with `autoRenewal: true` | INVALID_ARGUMENT |
| Free trial `frequency` must be specified if trial period is provided | INVALID_ARGUMENT |
| Free trial `interval` must be >= 1 | INVALID_ARGUMENT |

### Examples

**Monthly subscription (infinite, auto-renewing):**
```typescript
{
  name: "Premium Plan",
  quantity: 1,
  price: "29.99",
  subscriptionInfo: {
    subscriptionSettings: {
      frequency: "MONTH",
    },
    title: "Premium Monthly Plan",
    description: "Access to all premium features, billed monthly",
  },
}
```

**Quarterly subscription with 12 billing cycles (3-year plan):**
```typescript
{
  name: "Quarterly Plan",
  quantity: 1,
  price: "79.99",
  subscriptionInfo: {
    subscriptionSettings: {
      frequency: "MONTH",
      interval: 3,
      billingCycles: 12,
    },
    title: "Quarterly Plan",
  },
}
```

**Annual subscription with 14-day free trial:**
```typescript
{
  name: "Annual Plan",
  quantity: 1,
  price: "199.99",
  subscriptionInfo: {
    subscriptionSettings: {
      frequency: "YEAR",
      freeTrialPeriod: {
        frequency: "DAY",
        interval: 14,
      },
    },
    title: "Annual Plan",
    description: "Yearly billing with a 14-day free trial",
  },
}
```

**Mixed cart (one-time item + subscription):**
```typescript
const items = [
  {
    name: "Setup Fee",
    quantity: 1,
    price: "49.99",
    // No subscriptionInfo — this is a one-time charge
  },
  {
    name: "Monthly Service",
    quantity: 1,
    price: "19.99",
    subscriptionInfo: {
      subscriptionSettings: {
        frequency: "MONTH",
      },
      title: "Monthly Service",
    },
  },
];
```

