# Go live

When your integration works end to end in `development`, a few steps move it to production: link your real bank account, connect your real e-invoice account, switch keys and environment, make sure your handlers are strict, then test with a small real transfer. This page walks through each step and ends with what to watch once you are live.

## Before you start

Make sure the whole flow works in development:

- A payment created from your checkout reaches `paid` after a [simulated transfer](https://hatang.dev/docs/guides/payments/environments.md#simulate-a-transfer).
- Your IPN handler verifies the signature, updates the order once, and answers `2xx`.
- Partial payments, overpayments, expiry and late money behave the way you want (see the [test scenarios](https://hatang.dev/docs/guides/payments/environments.md)).
- If you issue invoices, a sandbox invoice reaches `issued`.

Nothing carries over between environments: bank accounts, matching settings, payments, invoice providers and integration keys all have to exist in production too.

## 1. Link your real bank account

Switch the dashboard to production (the environment bar on top of the Payments pages, **Switch to production**), then open **Payments settings → Bank accounts → Add bank account** and pick your bank:

| Bank | What you need |
|---|---|
| MB Bank | The account number, the holder's name as MB has it, and the national ID or tax code the account was opened with. MB texts a code to the holder's registered phone |
| BIDV | Register BIDV's balance notification service for the account at BIDV, then add the account. It activates when BIDV sends its first message |
| ACB | The account number. The holder then consents on ACB's page; the consent lasts up to 180 days |
| Pay2S | The account linked on Pay2S, a Hook pointing at the URL shown in the form, and the Hook's token |

The details are in [Bank connections](https://hatang.dev/docs/guides/payments/bank-connections.md). Then set the **matching settings** for the production account again: the payment code prefix, your own order code format, and which transfers `transaction.received` reports.

> **Caution**
>
> The person who receives the bank's code or consents on the bank's page must be the account holder, or someone they authorized. Plan the link for a time they are available.

## 2. Connect your e-invoice provider

If you issue invoices, open **Payments settings → Invoice providers** in production and connect your real Mắt Bão account: the tax code, API user and password from "Cấu hình API" in the Mắt Bão portal. Pick the template and series registered for your business, and choose what happens when a paid order asks for an invoice: stop at the draft, or send it for approval.

Then send the connection's **callback URL** to Mắt Bão support, so that changes made on their side reach Ha Tang at once. See [E-invoices](https://hatang.dev/docs/guides/payments/einvoices.md).

## 3. Switch keys and environment

| Setting | Development | Production |
|---|---|---|
| Environment | `Hatang-Environment: development` or SDK `environment: "development"` | Remove the header and the option (or send `production`) |
| IPN secret key | `sk_test_…` | `sk_…` from **Payments settings → Integration keys** in production |
| Webhook secret | `whsec_…` | The same endpoint serves both environments; keep its secret |
| API key | `ht_…` with `payments:read`, `payments:write` | The same key works; create a separate one per server if you prefer |

Keep these values in configuration, not in code:

```bash
HATANG_API_KEY=ht_123...
HATANG_SECRET_KEY=sk_123...         # production secret key, for IPN
HATANG_WEBHOOK_SECRET=whsec_123...  # webhook endpoint secret
# HATANG_ENVIRONMENT unset in production
```

## 4. Check your endpoints

- Your `ipn_url` and webhook endpoint are **https** on the public internet, answer `2xx` within 10 seconds (15 for webhooks), and do not redirect.
- They read the **raw body** for verification (no JSON middleware before it).
- The webhook endpoint is subscribed to the events you need, for example `payment.succeeded`, `payment.overpaid`, `transaction.received`, `bank_connection.revoked`, `invoice.failed`.
- Trust the signature, not the caller's IP address.

## 5. Make your handlers strict

Your handler must:

1. Verify the signature with the **production** key, and reject anything that does not verify.
2. Check `environment` is `production`; a development payment must never unlock a real order.
3. Compare `paid_amount` with the order total.
4. Dedupe on the IPN `id` or the webhook `svix-id`.
5. Move the order forward only, whatever order the calls arrive in.

Before you hand over goods after the payer returns to `return_url`, confirm on the server with `GET /payments/{id}` or the SDK's `payments.verify()`.

## 6. Test with a small real transfer

1. Create a payment in production for a small amount, for example 10,000 VND, with your real `ipn_url`.
2. Pay it from your own banking app by scanning the QR.
3. Check that the payment becomes `paid` (for ACB, allow up to 10 minutes: Ha Tang reads its history on that cycle), that your IPN arrived (`GET /payments/{id}/ipn-deliveries`), and that the transfer is listed under **Payments → Transactions**.
4. If the order asked for an invoice, check it reaches `issued` and has a number.
5. Then test the mistakes: pay a second small payment without the code and confirm that `transaction.received` arrives with `payment_id: null`.

The money lands in your own account; nothing needs refunding through Ha Tang.

## 7. Watch it once you are live

| What to watch | Where |
|---|---|
| IPN calls that keep failing | `GET /payments/{id}/ipn-deliveries` (`status: "exhausted"`), and the payment page |
| Webhook deliveries that fail | `GET /webhooks/{id}/deliveries`, and **Webhooks** in the dashboard |
| A bank account that stops working | `bank_connection.revoked` and `bank_connection.needs_reauth` webhooks; the account's status and last transaction under **Bank accounts** |
| ACB consent expiry | The connection's `consent.expires_at`; link the account again before it passes |
| Stuck calls to banks or the invoice provider | **Payments → Integration health** |
| Invoices that failed or were rejected | `invoice.failed`, `invoice.tax_rejected` webhooks, and the alert emails Ha Tang sends to owners and admins |
| Money in without an invoice | **Payments → Invoice reconciliation**, and the daily email at 08:00 |
| Transfers the bank never reported | [Import a statement](https://hatang.dev/docs/guides/payments/bank-connections.md#statement-import) for the missing days |

## Launch checklist

- [ ] The real bank account is linked and `active` in production.
- [ ] The production account's matching settings are set.
- [ ] The e-invoice provider is connected in production, with the right template, and Mắt Bão has the callback URL.
- [ ] The production **secret key** and the webhook secret are configured on your server.
- [ ] The `Hatang-Environment: development` header (or the SDK option) is gone from production.
- [ ] Your handlers check the signature, `environment`, the amount and duplicates, and move orders forward only.
- [ ] The IPN and webhook URLs are public `https` and answer `2xx` within 10 seconds.
- [ ] A small real transfer went through end to end.
- [ ] Someone watches failed deliveries, `bank_connection.*` events and invoice alerts.

## Next steps

- [Receive payment results](https://hatang.dev/docs/guides/payments/payment-results.md).
- [Bank connections](https://hatang.dev/docs/guides/payments/bank-connections.md).
- [Errors](https://hatang.dev/docs/reference/errors.md) and [Limits](https://hatang.dev/docs/reference/limits.md).

Source: https://hatang.dev/docs/guides/payments/go-live
