---
title: "Move from frappe/payments"
space: "BWH Payments (Next)"
url: "https://docs.bwh.tech/bwh-payments-next/get-started/move-from-frappe-payments"
updated: "2026-10-12"
---

An app that takes payments through `frappe/payments` can move to BWH Payments and keep its gateways and its payment history. Old payments stay readable, and old Razorpay payments stay refundable.

## Before you start

- BWH Payments installed on the site, with `frappe/payments` still installed. The move reads the frappe/payments records.
- A patch in your app, in the `post_model_sync` section of `patches.txt`

:::caution[Two Stripe SDKs]
BWH Payments needs the Stripe SDK 12.5 or later. frappe/payments pins `stripe~=10.12`. Both apps can be installed for the move, but uninstall frappe/payments soon after.
:::

## 1. Import the gateways

```python
from bwh_payments.migrate.frappe_payments import import_gateways

import_gateways()
```

Each frappe/payments **Payment Gateway** becomes a **Payment Gateway Profile** with the same name, for example `Razorpay` or `Stripe-Main`. Links in your app that point at a gateway name stay valid. Change the Link fields' options from `Payment Gateway` to `Payment Gateway Profile`.

| frappe/payments gateway | Profile |
|---|---|
| Razorpay | Enabled. The key id, key secret and webhook secret are copied to Razorpay Gateway Settings. |
| The first Stripe account | Enabled. The keys are copied to Stripe Gateway Settings. The mode follows the secret key. |
| Another Stripe account | Disabled, without settings. BWH Payments holds one Stripe account. |
| Any other gateway, for example PayPal or Paymob | Disabled, without settings |

A disabled profile without settings keeps the gateway's name for old payments. New payments cannot use it.

`import_gateways` never overwrites settings that are already filled in, and skips profiles that exist. You can run it again.

## 2. Record the payment history

Give each old payment a Gateway Payment Request, and each old refund a Gateway Refund:

```python
from bwh_payments.api import record_external_payment, record_external_refund

payment_request = record_external_payment(
	"Sales Order",
	order.name,
	order.grand_total,
	order.currency,
	"Razorpay",  # the gateway name, now a profile
	"Paid",  # or Expired for a payment that never completed
	order_ref=payment_id,  # the gateway's order or payment id
	transaction_ref=payment_id,  # Razorpay: the pay_… id
)

record_external_refund(payment_request.name, refund_id, "Processed", 400, reference=my_refund.name)
```

- No checkout opens, and your document's `on_payment_authorized` and `on_refund_update` are not called.
- A `Processed` refund updates the payment's refund total and status.
- Both are idempotent, on `order_ref` and on `refund_id`.
- For Razorpay, an `order_ref` that starts with `pay_` is refunded against that payment id.

Run each row in a savepoint, and log a row that fails, for example a payment for a deleted order. One bad row must not stop the site's migration.

## 3. Set up the webhooks

frappe/payments used its own webhook URLs. Register the BWH Payments URL for each gateway:

```
https://your.site/api/v2/method/bwh_payments.bwh_payments.webhook.handle?gateway=<profile name>
```

1. Add the webhook in the gateway's dashboard, with the events on the gateway's page ([Stripe](/bwh-payments-next/gateways/stripe), [Razorpay](/bwh-payments-next/gateways/razorpay)).
2. Paste its secret into the gateway's settings. frappe/payments stored no Stripe webhook secret, so Stripe always needs this step.

## 4. Uninstall frappe/payments

Uninstall it in a later update, after the patch has run on every site:

```bash
bench --site your.site uninstall-app payments
```

## Next steps

- [Accept a payment](/bwh-payments-next/get-started/accept-a-payment): take new payments
- [Public API](/bwh-payments-next/reference/public-api): every function in `bwh_payments.api`
