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

# Dodo payments

# Dodo Payments

Dodo setup, card payment flow, webhooks, and testing.

***

## Dodo Payments Setup

### 1. Account and dashboard

1. Sign up at [Dodo Payments](https://dodopayments.com) and open the [Dashboard](https://dashboard.dodopayments.com).
2. Create products and set prices (e.g. one product per platform: Website, Web Application, Android, iOS) or use a single product and pass metadata for platform.

***

### 2. Environment variables

```
DODO_API_KEY=your_dodo_api_key
DODO_WEBHOOK_SECRET=your_webhook_secret
DODO_PRODUCT_ID=your_default_product_id
NEXT_PUBLIC_APP_URL=https://your-domain
```

* **DODO\_API\_KEY** — Used for Create Checkout and any other server-side Dodo API calls. Required for `POST /api/dodo/create-checkout`.
* **DODO\_WEBHOOK\_SECRET** — Used to verify `x-dodo-signature` (or `dodo-signature`) on `POST /api/dodo/webhook`. If unset, verification is skipped (unsafe in production).
* **DODO\_PRODUCT\_ID** — Default product when the selected platform is not in the internal `PRODUCT_MAP` in `create-checkout`.
* **NEXT\_PUBLIC\_APP\_URL** — Base URL for `return_url` (e.g. `https://your-domain` or `http://localhost:3000`). Must match how users reach the app.

***

### 3. Product mapping (create-checkout)

`/api/dodo/create-checkout` maps `platform` to a Dodo product:

| `platform` (request) | Product id source                |
| :------------------- | :------------------------------- |
| `Web Application`    | `PRODUCT_MAP['Web Application']` |
| `IOS`                | `PRODUCT_MAP['IOS']`             |
| `Android App`        | `PRODUCT_MAP['Android App']`     |
| `Website`            | `PRODUCT_MAP['Website']`         |
| Other / missing      | `DODO_PRODUCT_ID`                |

`PRODUCT_MAP` is defined in code; adjust it to match your Dodo product IDs.

***

## Card Payment Flow

### 1. Create checkout

**POST** `/api/dodo/create-checkout`

**Body:** `{ sessionId, platform? }`

* `sessionId` required; `platform` defaults to `Website`.
* Builds Dodo payload:
  * `product_cart: [{ product_id, quantity: 1 }]`
  * `return_url`: `{NEXT_PUBLIC_APP_URL}/payment/success?sessionId=...&platform=...`
  * `metadata`: `{ session_id: sessionId, platform }`
  * `customization.redirect_immediately: true`
* Calls `https://live.dodopayments.com/checkouts` with `Authorization: Bearer DODO_API_KEY`.
* Response: `{ status: 'success', checkout_url, payment_link }` (both same URL). On error: `{ status: 'error', error, debug? }`.

***

### 2. User pays

* Frontend redirects the user to `checkout_url`.
* User completes card/UPI on Dodo’s page.
* Dodo redirects to `return_url` (success or failure).

***

### 3. Webhook (payment.succeeded)

* Dodo sends **POST** to your **Webhook URL** (e.g. `https://your-domain/api/dodo/webhook`) with `payment.succeeded` (or `payment_succeeded`, `payment.completed`).
* Payload includes `metadata.session_id` and `metadata.platform`; `data.payment_id` or `data.id` as the Dodo payment ID.
* Scriptonia updates `Workflow`: `paymentMethod: 'dodo'`, `dodoPaymentId`, `currentStage: 'payment_processing'`, `selectedPlatform` from metadata.
* The user can then go to **Execute**; `POST /api/execute` does not require `transactionSignature` when `dodoPaymentId` / `paymentMethod: 'dodo'` are set.

***

### 4. Confirm payment (optional)

**POST** `/api/dodo/confirm-payment`

**Body:** `{ sessionId, platform?, paymentId? }`

* Used when the client needs to persist payment context (e.g. after redirect from `return_url`) before the webhook has run or if the webhook is delayed.
* Ensures a `Workflow` exists for `sessionId` (creates if missing).
* Updates: `currentStage: 'payment_processing'`, `selectedPlatform` (default `Web Application`), `paymentDetails: { amountScript: UNIFIED_PRICE_SCRIPT, paymentMethod: 'dodo', dodoPaymentId }`.
* Response: `{ status: 'success', message }` or `{ status: 'error', error }`.

The webhook remains the source of truth when it arrives; confirm-payment is a fallback so the user can proceed to Execute without waiting for the webhook.

***

### 5. Execute

* User opens Execution with the same `sessionId` (and optionally `platform` in the URL).
* **POST** `/api/execute` with `{ sessionId }` (no `transactionSignature` for Dodo).
* Server loads workflow; sees `paymentMethod: 'dodo'` and/or `dodoPaymentId` and skips Solana `verifyTransaction`. Execution runs as for Solana-paid sessions.

***

## Webhook Configuration

### URL

In the Dodo dashboard, set the webhook URL to:

```
https://your-domain/api/dodo/webhook
```

For local testing, use a tunnel (e.g. ngrok) and set the webhook to `https://your-ngrok-url/api/dodo/webhook`. `NEXT_PUBLIC_APP_URL` for `return_url` can stay `http://localhost:3000` if users are redirected back to localhost; for Dodo’s server-to-server webhook, the URL must be the public tunnel.

***

### Signature verification

* **Header:** `x-dodo-signature` or `dodo-signature` (single string; arrays are rejected).
* **Secret:** `DODO_WEBHOOK_SECRET`.
* **Algorithm:** HMAC-SHA256 of the **raw body** (bytes as received), hex-encoded. The handler uses `crypto.timingSafeEqual` to compare with the header.
* **Body parsing:** `bodyParser: false` for `/api/dodo/webhook` so the raw body can be used for HMAC. The same raw string is then `JSON.parse`’d for `type`/`data`/`metadata`.

If the signature is invalid, the handler returns **401** `{ received: false, error: 'Invalid signature' }`. If `DODO_WEBHOOK_SECRET` is not set, verification is skipped (do not use in production).

***

### Handled events

| Event                                                         | Action                                                                                                                                 |
| :------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------- |
| `payment.succeeded`, `payment_succeeded`, `payment.completed` | Update `Workflow`: `paymentMethod: 'dodo'`, `dodoPaymentId`, `currentStage: 'payment_processing'`, `selectedPlatform` from `metadata`. |
| `payment.failed`, `payment_failed`                            | Log; optionally update workflow.                                                                                                       |
| `refund.succeeded`, `refund_succeeded`                        | If `metadata.session_id` present: set `paymentMethod: null`, `dodoPaymentId: null` on that `Workflow`.                                 |
| Others                                                        | Log as unhandled.                                                                                                                      |

The handler always returns **200** (or 401 on bad signature) so Dodo does not retry on “success”. On processing errors it still returns 200 and `{ received: true, error }` to avoid retries for malformed payloads.

***

## Testing

### 1. Local webhook

* Run a tunnel: `ngrok http 3000` (or your dev port).
* In Dodo, set webhook URL to `https://<ngrok-host>/api/dodo/webhook`.
* Use Dodo test cards (see Dodo docs) and trigger a payment. Check:
  * Your server logs for `Dodo webhook received: payment.succeeded` and any errors.
  * DB: `Workflow` row for `sessionId` has `paymentMethod: 'dodo'`, `dodoPaymentId`, `selectedPlatform`.

***

### 2. Create checkout

```
curl -X POST http://localhost:3000/api/dodo/create-checkout \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"test-session-1","platform":"Web Application"}'
```

Expect `checkout_url` in the response. Open it in a browser and pay with a test card.

***

### 3. Confirm payment (without webhook)

If the webhook is not yet configured or is delayed:

```
curl -X POST http://localhost:3000/api/dodo/confirm-payment \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"test-session-1","platform":"Web Application","paymentId":"pay_xxx"}'
```

Then call Execute with `sessionId`. The workflow must have `refinedRequirements` (from Intent) for execution to succeed; if the session was created only for payment tests, you may need to run Idea → Intent → Platform first.

***

### 4. End-to-end

1. Idea → Intent → Platform (choose any; Dodo product mapping may vary).
2. On Payment, choose “Buy with Card” (or equivalent) → `create-checkout` → redirect to Dodo.
3. Pay with a test card.
4. Redirect to `return_url` (`/payment/success?sessionId=...&platform=...`). Optionally call `confirm-payment` from the success page.
5. When the webhook has run (or after `confirm-payment`), go to Execute and run. Execution should complete without `transactionSignature`.

***

### 5. Webhook signature

To test verification, send a POST with a wrong `x-dodo-signature`; the handler should return 401. With `DODO_WEBHOOK_SECRET` unset, it skips verification and returns 200.

***
