> ## Documentation Index
> Fetch the complete documentation index at: https://flexprice-mintlify-370b7c30.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementation Guide

> Step-by-step guide to integrating Flexprice Checkout: backend setup, customer redirect, webhook handling, and go-live checklist.

This guide covers the complete integration: backend session creation, frontend redirect, webhook handling, and going live. By the end, customers will be able to select a plan, pay, and get an active subscription without any manual step on your side.

## Prerequisites

* [ ] Payment provider connected to Flexprice: see [Razorpay Setup](/docs/checkout/razorpay-checkout)
* [ ] Required webhook events enabled on your provider dashboard
* [ ] Flexprice webhook endpoint registered to receive `checkout.session.*` events
* [ ] Customer record exists in Flexprice before the checkout starts
* [ ] Plan has at least one non-zero charge

## Architecture

```
Your frontend        Your backend          Flexprice         Razorpay
─────────────        ────────────          ─────────         ────────
User clicks          POST /checkout/       Draft sub +       Payment
"Subscribe" ───────► sessions      ──────► invoice       ──► link created
                     Returns URL ◄─────────────────────────────────────
Redirect ◄───────────

User pays ──────────────────────────────────────────────────► Checkout

                                           ◄── payment_link.paid ──────
                                           Activates subscription
                                           Finalizes invoice
                     ◄── checkout.session.completed ──────────
Fulfill order ◄──────
```

## Step 1: Create a customer

A customer must exist in Flexprice before a session can be created. Create one at sign-up:

```bash theme={null}
curl -X POST https://us.api.flexprice.io/v1/customers \
  -H "Authorization: Bearer <API_KEY>" \
  -H "X-Environment-ID: <ENVIRONMENT_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_12345",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "currency": "INR"
  }'
```

Use the `external_id` as your reference in subsequent calls.

## Step 2: Create a Checkout Session

Call this from your backend only: never expose your API key to the frontend.

```bash theme={null}
curl -X POST https://us.api.flexprice.io/v1/checkout/sessions \
  -H "Authorization: Bearer <API_KEY>" \
  -H "X-Environment-ID: <ENVIRONMENT_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_external_id": "user_12345",
    "action": "create_subscription",
    "payment_provider": "razorpay",
    "configuration": {
      "create_subscription_params": {
        "plan_id": "plan_pro_monthly",
        "currency": "INR",
        "billing_period": "monthly"
      }
    },
    "success_url": "https://app.example.com/welcome?session_id={CHECKOUT_SESSION_ID}",
    "failure_url": "https://app.example.com/pricing?error=payment_failed",
    "cancel_url": "https://app.example.com/pricing",
    "idempotency_key": "order_<your_internal_order_id>"
  }'
```

Two things worth noting here:

* `{CHECKOUT_SESSION_ID}` in `success_url` is a placeholder that Razorpay replaces on redirect. Use it to identify which session the customer just completed.
* `idempotency_key` tied to your internal order ID means a retry on timeout gives you back the same session, not a duplicate.

Store the returned `id` (`chk_...`) in your database alongside the order.

## Step 3: Redirect the customer

Return the `payment_action.url` from your backend to your frontend and redirect immediately:

```javascript theme={null}
window.location.href = session.payment_action.url;
```

Do a full-page redirect, not an iframe. The payment link expires after 15 minutes, so don't add unnecessary steps before the redirect.

## Step 4: Handle the webhook

Do not rely on the redirect URL to confirm payment: it can be bypassed. The `checkout.session.completed` webhook is the authoritative signal.

Register your endpoint in **Settings → Webhooks**, then handle the events:

```javascript theme={null}
app.post(
  "/webhooks/flexprice",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const { event_type, data } = JSON.parse(req.body);

    res.status(200).send("ok"); // respond fast, process async

    if (event_type === "checkout.session.completed") {
      // subscription is active, invoice is finalized
      await grantAccess(data.customer_id);
      await sendWelcomeEmail(data.customer_id);
    }

    if (event_type === "checkout.session.failed") {
      await notifyPaymentFailed(data.id);
    }

    if (event_type === "checkout.session.expired") {
      await clearPendingState(data.id);
    }
  },
);
```

Make this handler idempotent: Flexprice delivers events at least once. Check whether you've already fulfilled the order before acting.

## Step 5: Handle success and failure pages

Use the redirect URLs for UX only, not for fulfillment logic.

**Success page**: poll the session to confirm before showing a result:

```bash theme={null}
curl https://us.api.flexprice.io/v1/checkout/sessions/<SESSION_ID> \
  -H "Authorization: Bearer <API_KEY>" \
  -H "X-Environment-ID: <ENVIRONMENT_ID>"
```

If `checkout_status` is `completed`, show the success state. If it's still `pending`, the webhook hasn't arrived yet: show a "processing" state and poll again in a few seconds.

**Failure or cancel page:** The customer can start over. Create a new session. The previous session and everything it created are cleaned up automatically.

## Step 6: Test the integration

Use your provider's test credentials before switching to live keys. For Razorpay test cards and UPI IDs, see [Razorpay Checkout Setup](/docs/checkout/razorpay-checkout#step-4-test-the-flow).

After a test payment, verify:

* [ ] Session `checkout_status` is `completed`
* [ ] Subscription is `active`
* [ ] Invoice is `finalized`
* [ ] Your backend received `checkout.session.completed`
* [ ] Fulfillment ran (access granted, emails sent, DB updated)

## Error reference

| Scenario                                       | Response                           | Fix                                                         |
| ---------------------------------------------- | ---------------------------------- | ----------------------------------------------------------- |
| Customer doesn't exist                         | 404                                | Create the customer before starting checkout                |
| Plan produces zero-amount invoice              | 400                                | Use a plan with at least one non-zero charge                |
| Duplicate `idempotency_key` for active session | 409                                | Fetch the existing session; use a new key for a new attempt |
| Customer doesn't pay in time                   | Session expires; drafts cleaned up | Let the user start a new checkout                           |
| `payment_link.paid` not enabled                | Subscription stays inactive        | Enable the event in your payment provider dashboard         |

## Recurring payments

This guide uses the default one-time payment link: each invoice generates its own link. To charge future invoices automatically, collect a mandate at checkout instead. The customer authorizes once, and renewals — plus one-off and wallet top-up invoices — auto-charge with no further action. See [Autopay & Mandates](/docs/checkout/autopay-mandates).

## Go-live checklist

* [ ] Switch provider connection from test keys to live keys
* [ ] Confirm all required webhook events are enabled on the production URL (not test)
* [ ] `success_url`, `failure_url`, `cancel_url` point to production domains
* [ ] Webhook endpoint responds within 5 seconds and uses HTTPS
* [ ] Fulfillment handler is idempotent

<CardGroup cols={2}>
  <Card icon="credit-card" href="/docs/checkout/razorpay-checkout" title="Razorpay Setup">
    Test credentials and webhook event configuration.
  </Card>

  <Card icon="code" href="/docs/checkout/checkout-sessions" title="Checkout Sessions API">
    Full field reference for the create session call.
  </Card>
</CardGroup>
