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

# Razorpay 金流

> 將 Razorpay 訂單、訂閱、手動 Webhook 與 Webhook 履行整合到 InsForge 中。

如果您需要使用 Razorpay Orders、Razorpay Checkout、Items、Plans 和 Subscriptions，請使用 Razorpay 整合。Razorpay 不像 Stripe Checkout 那樣是託管的重新導向流程。您的後端建立服務商物件，您的前端開啟 Razorpay Checkout 指令碼，接著由您的後端驗證回傳的簽章。

## Razorpay 模型

| Razorpay 概念   | 在 InsForge 中的意義                                                                                     |
| ------------- | --------------------------------------------------------------------------------------------------- |
| Item          | 帶金額的可銷售單位。鏡像同步至 `payments.razorpay_items`。                                                          |
| Plan          | 圍繞某個 Item 建立的週期性訂閱定義。鏡像同步至 `payments.razorpay_plans`。                                               |
| Order         | 一次性付款物件。透過 `POST /api/payments/razorpay/{environment}/orders` 建立，並鏡像同步至 `payments.razorpay_orders`。 |
| Payment       | 服務商回傳的付款結果。透過 Webhook 與同步投影到 `payments.transactions`。                                               |
| Subscription  | 與某個 Plan 綁定的 Razorpay 訂閱。鏡像同步至 `payments.razorpay_subscriptions`。                                   |
| Webhook event | 存放於 `payments.webhook_events` 中且 `provider = 'razorpay'` 的已驗證服務商事件。                                 |

Razorpay 沒有相當於 Stripe Billing Portal 的功能。InsForge 提供後端路由，在依據 `payments.razorpay_subscriptions` 的 RLS `UPDATE` 政策檢查呼叫方之後，用於取消、暫停與恢復訂閱。

對於一次性產品，建立 Razorpay Order 時只需提供金額、幣別和 receipt 即可。不過，仍建議為可銷售的產品建立 Razorpay Item，這樣在同步之後，產品目錄就能在 InsForge 與 Razorpay 中可見。請將 Order 視為一次付款嘗試，而非您的產品目錄。

## 設定

在 Dashboard -> Payments -> Settings 中或透過管理 API 設定 `test` 與 `live` 的 Razorpay Key ID 與 Key Secret。若尚不存在 Webhook 簽署密鑰，InsForge 會自動產生一個。

Razorpay 的 Webhook 必須在 Razorpay Dashboard 中手動建立。一般的 Razorpay 商戶 API 金鑰不支援自動註冊 Webhook。

1. 開啟 Dashboard -> Payments -> Settings -> Webhooks。
2. 複製 Razorpay Webhook URL 與 Webhook Secret。
3. 在 Razorpay Dashboard 中，使用複製的 URL 與密鑰建立一個 Webhook。
4. 選擇 InsForge 處理的事件。
5. 儲存 Webhook 並完成一次測試付款以確認送達。

Razorpay 只能將 Webhook 投遞到公開的 HTTPS URL。Localhost URL 需要借助公開通道或已部署的後端。

已處理的事件：

* `payment.authorized`
* `payment.captured`
* `payment.failed`
* `order.paid`
* `invoice.paid`
* `invoice.expired`
* `refund.created`
* `refund.processed`
* `refund.failed`
* `subscription.created`
* `subscription.activated`
* `subscription.charged`
* `subscription.updated`
* `subscription.cancelled`
* `subscription.paused`
* `subscription.resumed`
* `subscription.halted`
* `subscription.completed`
* `subscription.expired`

## 一次性訂單

首先建立一個由應用程式擁有的待處理訂單（pending order）。接著使用目前的 InsForge 使用者權杖建立一個 Razorpay Order。

```typescript theme={null}
const { data, error } = await insforge.payments.razorpay.createOrder('test', {
  amount: 50000,
  currency: 'INR',
  receipt: 'order_123',
  subject: { type: 'team', id: 'team_123' },
  customerEmail: 'buyer@example.com',
  notes: { order_id: 'order_123' }
});

if (error) throw error;
```

該 SDK 封裝了 `POST /api/payments/razorpay/{environment}/orders`。回應中包含 `checkoutOptions`，其中帶有 Razorpay Checkout 原生欄位，例如 `key` 和 `order_id`。請在前端載入 `https://checkout.razorpay.com/v1/checkout.js`，並將這些選項傳給 `new Razorpay(options).open()`。

如果您的履行觸發器讀取 `notes.order_id`，請在建立 Order 或 Subscription 時傳入 `notes: { order_id: ... }`。

在 Razorpay Checkout 回傳 `razorpay_order_id`、`razorpay_payment_id` 和 `razorpay_signature` 之後，請在後端驗證簽章：

```typescript theme={null}
await insforge.payments.razorpay.verifyOrder('test', {
  orderId: response.razorpay_order_id,
  paymentId: response.razorpay_payment_id,
  signature: response.razorpay_signature
});
```

簽章驗證只能證明該次即時的 Checkout 回呼確實來自 Razorpay。持久化的訂單履行仍應以已驗證的 Razorpay Webhook 事件為依據來執行。

## 訂閱

在建立訂閱之前，請先建立或同步一個 Razorpay Plan。Plan 與 Stripe 的 Price 並非同一回事，它是圍繞 Razorpay Item 建立的週期性定義。

```typescript theme={null}
const { data, error } = await insforge.payments.razorpay.createSubscription('test', {
  planId: 'plan_123',
  totalCount: 12,
  subject: { type: 'team', id: 'team_123' },
  customerEmail: 'buyer@example.com'
});

if (error) throw error;
```

該 SDK 封裝了 `POST /api/payments/razorpay/{environment}/subscriptions`。回應中包含 `checkoutOptions.subscription_id`。請使用該訂閱 ID 開啟 Razorpay Checkout。在 Checkout 回傳 `razorpay_subscription_id`、`razorpay_payment_id` 和 `razorpay_signature` 之後，驗證該筆授權付款：

```typescript theme={null}
await insforge.payments.razorpay.verifySubscription('test', {
  subscriptionId: response.razorpay_subscription_id,
  paymentId: response.razorpay_payment_id,
  signature: response.razorpay_signature
});
```

透過後端路由管理訂閱：

```typescript theme={null}
await insforge.payments.razorpay.cancelSubscription('test', 'sub_123', {
  cancelAtCycleEnd: false
});

await insforge.payments.razorpay.pauseSubscription('test', 'sub_123');
await insforge.payments.razorpay.resumeSubscription('test', 'sub_123');
```

建立訂閱時會評估 `payments.razorpay_subscriptions` 上的 `INSERT` 政策。取消、暫停與恢復操作則會評估同一張表上的 `UPDATE` 政策。PostgreSQL 也會對 `INSERT/UPDATE ... RETURNING` 回傳的資料列套用 `SELECT` 政策，因此當政策檢查需要回傳該資料列時，請確保同一主體（subject）對呼叫方可見。僅授予使用者政策檢查所需的資料表存取權限；對服務商的實際變更操作仍透過後端執行。

## Webhook 與履行

Razorpay 的 Checkout 回呼與簽章驗證並不能取代 Webhook。請使用 `payments.webhook_events` 來觸發履行。請勿將履行觸發器附加到諸如 `payments.razorpay_subscriptions` 之類的服務商鏡像表上；同步與 Webhook 投影可能會獨立於服務商事件送達而更新這些資料列。

```sql theme={null}
CREATE OR REPLACE FUNCTION public.fulfill_razorpay_order()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.provider = 'razorpay'
     AND NEW.event_type IN ('payment.captured', 'order.paid', 'invoice.paid')
     AND NEW.processing_status = 'processed'
     AND COALESCE(
       NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id',
       NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id'
     ) IS NOT NULL THEN
    UPDATE public.orders
    SET status = 'paid',
        paid_at = COALESCE(NEW.processed_at, NOW())
    WHERE id::text = COALESCE(
      NEW.payload -> 'payload' -> 'payment' -> 'entity' -> 'notes' ->> 'order_id',
      NEW.payload -> 'payload' -> 'invoice' -> 'entity' -> 'notes' ->> 'order_id'
    )
      AND status = 'pending';
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER fulfill_razorpay_order_from_webhook
  AFTER INSERT OR UPDATE ON payments.webhook_events
  FOR EACH ROW
  EXECUTE FUNCTION public.fulfill_razorpay_order();
```

## 同步與儀表板狀態

Razorpay 同步會鏡像 Items、Plans、Customers、Subscriptions、Invoices 和 Payments。Invoices 與 Payments 會填入 `payments.transactions` 這個儀表板／報表投影。Transactions 中包含服務商的參照 ID，例如 payment、invoice、order、subscription 和 refund 的 ID，方便您在 Razorpay Dashboard 中查看原始紀錄。

請將面向使用者的訂單、額度與權益狀態保存在您自己的應用程式資料表中。請將 `payments.transactions` 視為儀表板／報表狀態，而非主要的業務流程。

## 參考資料

* [Razorpay 標準 Checkout 整合](https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/integration-steps/)
* [Razorpay Subscriptions 整合](https://razorpay.com/docs/payments/subscriptions/integration-guide/)
* [Razorpay Webhooks 設定](https://razorpay.com/docs/payments/dashboard/account-settings/webhooks/)
* [TypeScript Razorpay 金流指南](/sdks/typescript/payments-razorpay)
