GuestOps/docs/payments.md

52 lines
5.6 KiB
Markdown

# NMI hosted payment requests
The Payments page prepares a reviewed invoice, creates it once through NMI, and checks its status. MongoDB stores each hotel's enablement setting, immutable proposal details and the latest verification result. Invoice creation and verification require an owner account. Provider credentials stay on the server.
## Configure a sandbox merchant
Copy `deploy/payments.example.json` to a private `payments.local.json` outside the checkout:
```json
{
"Payments": {
"Hotels": {
"REPLACE_WITH_INTERNAL_GUESTOPS_HOTEL_ID": {
"BaseUrl": "https://sandbox.nmi.com",
"MerchantAccount": "YOUR_STABLE_MERCHANT_ACCOUNT_ID",
"SecurityKey": "YOUR_NMI_V5_MERCHANT_KEY",
"CreatesEnabled": false
}
}
}
}
```
Use the internal 32-character hotel ID shown on the Payments page. `MerchantAccount` is an administrator-maintained identity binding: verify it against the merchant account behind the key. Do not reuse the same identity for a different merchant. The API accepts only `https://sandbox.nmi.com` and `https://secure.nmi.com`; confirm the correct environment and key with NMI.
Set `PAYMENTS_CONFIG_FILE_HOST` in the deployment `.env` to the absolute private file path. Compose mounts it read-only in the API only. Restrict file permissions to the administrator and the container user/group that needs read access. Determine the image's user with `docker run --rm --entrypoint id YOUR_API_IMAGE -u` before assigning permissions. Restart the API after changes. The default empty example disables the integration. Do not commit private configuration or put keys in the browser.
After sandbox acceptance, enable `CreatesEnabled` on the server and **Allow owner-approved payment invoices** in the hotel's Payments page. Both controls are required. Turning off creation leaves read-only reconciliation available. Key rotation preserves reconciliation for the same merchant binding; pending approvals require the original credential revision and must be replaced after rotation. Changing the merchant identity blocks reconciliation until the original binding is restored.
## Staff workflow
1. Enter a unique payment reference, customer email, description, amount and currency. This milestone supports GBP, EUR and USD, with two decimal places and a maximum of 100,000 per invoice. Confirm the merchant supports the chosen currency.
2. Prepare the request. This only writes a proposal to MongoDB. Check the guest's agreed amount and booking terms separately; preparation does not reserve inventory.
3. Review and approve the amount, currency, recipient and customer email. Approval expires after fifteen minutes. Creating an NMI invoice can email the customer a hosted payment link. GuestOps does not call a separate send endpoint.
4. Use **Verify with NMI** to refresh invoice status. It verifies invoice ID, order reference, recipient, amount and currency before accepting a known status. Partial payment is displayed separately. `Paid` means NMI reports the invoice paid; it is not proof of bank settlement and does not create or update a booking.
NMI's published [Create Invoice](https://docs.nmi.com/reference/create-invoice-v5) and [Get Invoice](https://docs.nmi.com/reference/get-invoice-v5) schemas were inspected on 2026-09-14. The published InvoiceResponse does not guarantee a `payment_url`. This implementation relies on NMI's hosted invoice email; it does not construct checkout URLs or insert a payment URL into Gmail drafts. Confirm invoice email delivery and hosted checkout with your sandbox merchant before enabling live creation.
## Interrupted requests and reconciliation
Every request has a unique hotel/payment reference enforced by MongoDB, including cancelled requests. Concurrent approvals create at most one local submission. No provider idempotency guarantee is assumed. A timeout or ambiguous response remains `NeedsReview` and creation cannot be repeated. Do not work around an uncertain result by inventing a new reference.
Verification is read-only. If the invoice ID was lost, GuestOps searches NMI by the server-generated order ID, starting one day before proposal creation, with at most ten pages of 100 invoices. An incomplete search, duplicate matches, missing invoice or mismatched details stays held for merchant-portal investigation. Interrupted `Creating` requests become eligible after five minutes; active requests have a ninety-second deadline. No automatic polling, invoice recreation, email retry or force-clear is implemented.
Only unsubmitted proposals can be cancelled in GuestOps. Invoice closure, refunds, disputes and settlement reconciliation are handled in the NMI merchant portal. Keep the operation reference and invoice ID when investigating. The latest 500 local records are shown; records are retained rather than automatically deleted. Status is an observation at the displayed verification time, not a live balance.
## Acceptance before live use
Automated tests use an in-process fake HTTP handler and never contact NMI. They cover tenant isolation, amount/currency/identity mismatches, partial status, concurrent approvals, lost create responses, pagination and merchant changes. CI checks the production configuration mount and disabled defaults.
Use a dedicated sandbox merchant and recipient to validate authentication, supported currency, exact request/response fields, preservation of `order_details.order_id`, customer invoice email and hosted checkout, partial/full payments and interrupted-request recovery. Live acceptance remains pending. Planet, direct payment links in GuestOps replies, automatic booking after payment, automated expiry/closure and background polling are follow-on work.