From bd7edf4e1a644f21726e27a4d3501f284a60d371 Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Fri, 26 Jun 2026 02:17:43 +0530 Subject: [PATCH 1/2] docs: WhatsApp prepaid wallet + top-up lifecycle --- src/content/docs/changelog/index.mdx | 5 ++ src/content/docs/modules/billing.mdx | 1 + src/content/docs/modules/whatsapp-wallet.mdx | 89 ++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/content/docs/modules/whatsapp-wallet.mdx diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx index 180d8b52..3371f829 100644 --- a/src/content/docs/changelog/index.mdx +++ b/src/content/docs/changelog/index.mdx @@ -11,6 +11,11 @@ seo: Notable changes to the kit, newest first. +## 2026-06-26 + +- **New: a prepaid WhatsApp wallet with an operator-approved top-up lifecycle.** Phase 1 of running as a WhatsApp **BSP / Meta Tech Provider** — tenants' numbers sit under your Meta account, so Meta bills you for message traffic and you recover the cost from each tenant via a prepaid money wallet (currency is USD). A tenant submits a **top-up request** for an amount from the dashboard's new **WhatsApp wallet** page; an operator sees it in the admin console's new **Billing → Top-ups** page and clicks **Approve**, which **generates and issues an invoice** for that amount (emailed to the tenant) — or **Reject**. The tenant pays the invoice **manually/offline**, the operator marks it **Paid** on the existing invoice-detail page, and that **auto-credits the wallet** (an append-only `WalletTransaction` ledger entry) and completes the request, atomically. Balance changes **only** when the invoice is marked Paid — there is no provisional credit. New endpoints under `/api/v1/billing`: tenant `GET /wallet/me`, `POST /wallet/topup-requests`, `GET /wallet/topup-requests/me`; operator `GET /wallet/topup-requests`, `POST /wallet/topup-requests/{id}/approve`, `POST /wallet/topup-requests/{id}/reject`. Viewing and requesting need `Billing.View`; approve/reject need `Billing.Manage`. See [WhatsApp prepaid wallet](/docs/modules/whatsapp-wallet/). +- **Not yet shipped (Phase 2): metering.** Nothing debits the wallet per WhatsApp message send yet — that needs the Meta Cloud API send integration. The ledger's `Debit` path and `MessageCharge` transaction kind exist so it's ready, but no send charges the wallet in Phase 1. + ## 2026-06-20 - **The `fsh` CLI and `dotnet new` template are now on NuGet as stable `10.0.0`.** The two distribution packages that 10.0.0 had been waiting on have shipped: `FullStackHero.CLI` (install with `dotnet tool install -g FullStackHero.CLI` — no more `--prerelease`) and `FullStackHero.NET.StarterKit` (`dotnet new install FullStackHero.NET.StarterKit`). Because `fsh new` scaffolds *from* that template, the one-command flow is now end-to-end: `dotnet tool install -g FullStackHero.CLI && fsh new MyApp` produces a fully renamed project — unique JWT signing key, generated Docker secrets, `npm install` run, initial commit on `main`. The [Install](/docs/getting-started/install/) and [CLI](/docs/cli/) pages now lead with the CLI as the recommended path; `git clone` and the GitHub template remain available for reading the source or zero-install runs. See the [10.0.0 release](https://github.com/fullstackhero/dotnet-starter-kit/releases/tag/10.0.0). diff --git a/src/content/docs/modules/billing.mdx b/src/content/docs/modules/billing.mdx index dab63746..3f0a4125 100644 --- a/src/content/docs/modules/billing.mdx +++ b/src/content/docs/modules/billing.mdx @@ -274,6 +274,7 @@ Unit tests live at `src/Tests/Billing.Tests/` (domain state machines, services, ## Related +- [WhatsApp prepaid wallet](/docs/modules/whatsapp-wallet/) — a prepaid money wallet and operator-approved top-up lifecycle built on top of this module's invoice flow. - [Quota building block](/docs/building-blocks/quota/) — the plan keys here must line up with what Quota enforces. - [Multitenancy deep-dive](/docs/architecture/multitenancy-deep-dive/#the-documented-exception-billing) — why Billing opts out of the default tenant isolation. - [Modules overview](/docs/modules/) — the other nine modules that ship in v10. diff --git a/src/content/docs/modules/whatsapp-wallet.mdx b/src/content/docs/modules/whatsapp-wallet.mdx new file mode 100644 index 00000000..7a22e10d --- /dev/null +++ b/src/content/docs/modules/whatsapp-wallet.mdx @@ -0,0 +1,89 @@ +--- +title: WhatsApp prepaid wallet +lastUpdated: 2026-06-26 +description: A prepaid money wallet and operator-approved top-up lifecycle for recovering WhatsApp message costs under the BSP / Meta Tech-Provider model. +sidebar: + label: WhatsApp wallet + order: 8.5 +pageType: reference +seo: + title: 'WhatsApp prepaid wallet & top-ups — fullstackhero Billing' + description: 'A prepaid money wallet with an operator-approved top-up lifecycle: tenant requests a top-up, operator approves and issues an invoice, marking it paid auto-credits the wallet ledger.' + keywords: 'whatsapp wallet .net, prepaid wallet saas, bsp meta tech provider billing, top-up request approval, wallet ledger dotnet' +--- + +The WhatsApp prepaid wallet extends the [Billing module](/docs/modules/billing/) with a **prepaid money wallet** and an **operator-approved top-up lifecycle**. It exists to recover the cost of WhatsApp message traffic when you operate as a **BSP (Business Solution Provider) / Meta Tech Provider**: tenants' WhatsApp numbers are onboarded under *your* Meta account, so **Meta bills you** for all of their message traffic, and you recover that cost from each tenant by having them keep a prepaid balance with you. + + +This is Phase 1. It ships the **money wallet** and the **top-up lifecycle**. It does **not** meter message sends: nothing debits the wallet per WhatsApp message yet — that needs the Meta Cloud API send integration, which is Phase 2. See [Out of scope](#out-of-scope-phase-2) below. The wallet holds and grows a balance today; it does not shrink. + + +## The BSP / Meta Tech-Provider model + +A clinic (tenant) does not hold its own Meta billing relationship. Its WhatsApp Business number is onboarded under your Meta Tech-Provider account, so **Meta charges you** for every conversation and template send across all tenants. You front that cost and recover it from each tenant through a prepaid wallet they top up in advance. + +This is why the wallet holds a **money balance** (Phase 1 currency is **USD**), not a message or "credits remaining" count: the balance is what the tenant has prepaid against their future message cost. Phase 2 will debit that balance per send. + +## The top-up lifecycle + +A top-up moves money into a tenant's wallet through an operator-approved, invoice-backed flow. Payment is **manual / offline** — the kit issues an invoice and records the credit; it does not integrate a payment gateway. + +1. **Tenant requests a top-up.** From the dashboard's **WhatsApp wallet** page a tenant user submits a top-up request for a money amount (arbitrary amount within a min/max validator). The request starts in `Pending`. +2. **Operator reviews.** In the admin console, **Billing → Top-ups** lists pending requests across tenants. The operator can **Approve** or **Reject**. +3. **Approve issues an invoice.** Approving **generates and issues an invoice** for the requested amount (an `Topup`-purpose invoice) and moves the request to `Invoiced`. Issuing the invoice emails it to the tenant (the existing Billing invoice-issued notification). +4. **Tenant pays offline.** The tenant pays the invoice manually — bank transfer, card over the phone, etc. The kit does not collect the payment. +5. **Operator marks the invoice Paid.** On the existing invoice-detail page the operator marks the invoice **Paid**. For a top-up invoice this **auto-credits the wallet** — appending a `Topup` `WalletTransaction` to the ledger — and moves the request to `Completed`, atomically in the same save. + +`Reject` moves a `Pending` request to `Rejected` (no invoice, no credit). The wallet balance changes **only when a top-up invoice is marked Paid** — there is no provisional balance for a pending or merely-invoiced request. + +### Request states + +| State | Meaning | +|---|---| +| `Pending` | Submitted by the tenant, awaiting operator action | +| `Invoiced` | Operator approved; an invoice has been issued for the amount | +| `Completed` | Invoice paid; wallet credited | +| `Rejected` | Operator rejected the pending request | +| `Cancelled` | Reserved for tenant-cancelled requests | + +## The wallet ledger + +The wallet's balance is the **sum of an append-only ledger** of `WalletTransaction` rows, not a single mutable number — so every balance change is auditable. Phase 1 writes only credit transactions: + +| Kind | Phase 1 use | +|---|---| +| `Topup` | Credit written when a top-up invoice is marked Paid | +| `MessageCharge` | **Phase 2** — debit per WhatsApp message send (not yet written) | +| `Adjustment` | Manual operator correction | + +`Wallet.Debit` and `WalletTransactionKind.MessageCharge` exist so the ledger is ready for metering, but **nothing calls `Debit` in Phase 1**. + +## Endpoints + +All routes mount under `/api/v1/billing` alongside the rest of the Billing module. Like the rest of Billing, the wallet handlers are **root-gated**: an operator (root tenant) may act across tenants, every other caller is pinned to its own tenant id. + +| Verb | Route | Permission | Caller | +|---|---|---|---| +| GET | `/wallet/me` | `Billing.View` | Tenant — own wallet balance | +| POST | `/wallet/topup-requests` | `Billing.View` | Tenant — submit a top-up request | +| GET | `/wallet/topup-requests/me` | `Billing.View` | Tenant — own request history | +| GET | `/wallet/topup-requests` | `Billing.View` | Operator — all pending/historic requests | +| POST | `/wallet/topup-requests/{id}/approve` | `Billing.Manage` | Operator — issue invoice, move to `Invoiced` | +| POST | `/wallet/topup-requests/{id}/reject` | `Billing.Manage` | Operator — move to `Rejected` | + +Viewing a wallet and **requesting** a top-up need only `Billing.View`; **approving** or **rejecting** needs `Billing.Manage`. There is no dedicated wallet-credit endpoint — the credit is a side effect of marking the top-up invoice paid via the existing `POST /invoices/{invoiceId}/pay`. + +## Out of scope (Phase 2) + +The following are intentionally **not** shipped in Phase 1 — readers should not expect message-level deduction yet: + +- **Metering / debit** — decrementing the wallet per WhatsApp template send (per Meta category pricing plus markup). Needs the Meta Cloud API send integration. +- **Low-balance notifications / auto-block** at a zero balance. +- **Fixed top-up packages** (UI presets) — Phase 1 uses an arbitrary amount with a min/max validator. +- **Per-category credit pricing display** ("messages remaining") — Phase 1 shows a money balance. +- **Embedded Signup / WABA onboarding** under your Meta Tech-Provider account. + +## Related + +- [Billing module](/docs/modules/billing/) — plans, the invoice state machine, and the `POST /invoices/{invoiceId}/pay` transition that triggers the wallet credit. +- [Modules overview](/docs/modules/) — the other modules that ship in v10. From 59a5105e6c4b0634834a1394fe4cc1e8c84e08f8 Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Fri, 26 Jun 2026 02:20:09 +0530 Subject: [PATCH 2/2] docs: fix article typo (a Topup-purpose invoice) --- src/content/docs/modules/whatsapp-wallet.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/modules/whatsapp-wallet.mdx b/src/content/docs/modules/whatsapp-wallet.mdx index 7a22e10d..3dfb5cf9 100644 --- a/src/content/docs/modules/whatsapp-wallet.mdx +++ b/src/content/docs/modules/whatsapp-wallet.mdx @@ -30,7 +30,7 @@ A top-up moves money into a tenant's wallet through an operator-approved, invoic 1. **Tenant requests a top-up.** From the dashboard's **WhatsApp wallet** page a tenant user submits a top-up request for a money amount (arbitrary amount within a min/max validator). The request starts in `Pending`. 2. **Operator reviews.** In the admin console, **Billing → Top-ups** lists pending requests across tenants. The operator can **Approve** or **Reject**. -3. **Approve issues an invoice.** Approving **generates and issues an invoice** for the requested amount (an `Topup`-purpose invoice) and moves the request to `Invoiced`. Issuing the invoice emails it to the tenant (the existing Billing invoice-issued notification). +3. **Approve issues an invoice.** Approving **generates and issues an invoice** for the requested amount (a `Topup`-purpose invoice) and moves the request to `Invoiced`. Issuing the invoice emails it to the tenant (the existing Billing invoice-issued notification). 4. **Tenant pays offline.** The tenant pays the invoice manually — bank transfer, card over the phone, etc. The kit does not collect the payment. 5. **Operator marks the invoice Paid.** On the existing invoice-detail page the operator marks the invoice **Paid**. For a top-up invoice this **auto-credits the wallet** — appending a `Topup` `WalletTransaction` to the ledger — and moves the request to `Completed`, atomically in the same save.