Visual Search update: `search-flow-visual.json` adds category chips, four featured choices and up to eight thumbnail results, with current backend prices and stock. The matching integration uses `searchFlowVisual: true` and starts through data exchange. Approved JPEG, PNG and WebP source images up to 5 MB are converted to JPEG thumbnails (240px, at most 80 KB); missing, inaccessible or unsupported images use a placeholder. This keeps native WhatsApp styling and returns selection to chat.

# Native shopping and payment-contact Flows

## Active hybrid journey (updated)

The bot now requires first-time/versioned terms and privacy acceptance before the welcome menu. The published Shopping Flow is used for consent, and the dedicated Search-first Flow (`1791484795407520`) is used for search: accepting closes the Flow with a verified `consent_flow` completion and sends the existing welcome menu; selecting a search result closes it with a verified `search_flow` completion and resumes existing product/pack/quantity handling in chat. Shop/category browsing, basket, checkout and delivery retain their existing chatbot handlers. Search does not add to the basket inside the Flow in this mode.

Order confirmation always offers Visa/Mastercard, EcoCash and InnBucks. Visa/Mastercard uses the existing contact Flow and hosted provider link when merchant card checkout is enabled. EcoCash retains the existing number collection/push handler. InnBucks uses the existing USD PesePay hosted link; the customer chooses InnBucks on that page if enabled for the account/order. There is no new seamless InnBucks implementation.

Search uses `search-flow-visual.json`, whose first screen is SEARCH. The older Terms-first Flow cannot be launched on SEARCH: Meta rejects it with error 131009. The endpoint completes consent/search using Meta’s reserved `SUCCESS` response. Existing basket-mode sessions remain compatible with the older full shopping screens described below. New chatbot launches explicitly select consent or search mode.

Legacy basket-mode journey: first-time terms acceptance → search/category → up to 20 product/pack-size options → product image, current database price and quantity → idempotent add to the existing basket → view basket and continue the existing chat checkout. A new Flow can add another product. Search uses WHC's existing product search and WhatsApp sellability rules. Category membership comes from synced `Categories:` description metadata, matching the existing bot; a category without this metadata cannot filter products yet.

Card entry is on the provider's hosted checkout. An enabled, live Paynow/PesePay merchant can offer Visa/Mastercard alongside EcoCash after order confirmation. Card acceptance must be activated by the provider for the merchant account and currency. No card number, CVV, PIN, or bank OTP is collected by WHC or the Flow.

## Local preparation

1. From `backend`, run `npx prisma generate` and `npm run build`.
2. Before running the new backend against a database, apply `backend/prisma/sql/20261102-shopping-flows.sql` followed by `backend/prisma/sql/20261102-payment-contact-flow.sql` to that intended PostgreSQL database using your deployment database tooling. The first file adds shopping session/terms tables. The second adds the payment-contact session table and nullable `Order.paymentContactPhone` column. This repo has no existing migration baseline; do not run `migrate deploy` expecting this standalone SQL file to be discovered automatically. Development deployments already using `prisma db push` can use that existing schema-sync workflow after reviewing its diff.
3. Set the server environment variables in `server.env.example`. Do not commit private keys or secrets. Mount the private key outside public uploads. Existing `.env` files have not been modified by this change.
4. Generate a 2048-bit RSA keypair in your deployment secrets directory, e.g. `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem`, then `openssl pkey -in private.pem -pubout -out public.pem`. Restrict private-key permissions. Register the public key for the merchant's WhatsApp phone-number ID using Meta's `/{PHONE_NUMBER_ID}/whatsapp_business_encryption` endpoint (`business_public_key`). Check the returned encryption-key signature status.
5. The configured app secret must belong to the Meta app delivering requests. This initial implementation uses one Meta app/keypair per server deployment. Separate Meta apps require separate endpoints/configuration. Never expose a plaintext test endpoint in production.

## Meta setup

1. In WhatsApp Manager, create a shopping Flow under the same WABA used by the merchant.
2. Import `shopping-flow.json` (Flow JSON 7.1 / data API 3.0).
3. Set the Flow endpoint URI to `https://YOUR_BACKEND/whatsapp/flows/data-exchange` and associate the Meta app.
4. Run Meta validation, encrypted endpoint health checks and interactive previews. The JSON is a local implementation artifact: it has not been uploaded, validated by Meta or published by this change. Correct any version/account-specific validation errors before publishing.
5. Test Android and iOS: first/new terms version, returning user, search and empty results, category and pack size, image fallback, stock/price changes, duplicate add, expired session, back navigation and completion to chat.
6. Publish the validated Flow and copy its ID. Published Flows should be versioned by creating/replacing the Flow when changing its screen schema.

Official protocol examples: https://github.com/WhatsApp/WhatsApp-Flows-Tools/tree/main/examples/endpoint/nodejs/basic

## Merchant setup

In Dashboard → Integrations → WhatsApp, set:

- Published Flow ID and Enable native shopping Flow.
- Approved shopping terms summary (max 3,500 characters), version, full HTTPS terms URL and privacy-policy URL.
- Save the integration. Existing WABA phone ID, token and catalogue ID are retained in the form.

Acceptance is stored per business/customer/version with the terms URL and timestamp, separately from expiring shopping sessions. Publish immutable terms for each version and increment the version whenever acceptance must be renewed. Shopping terms do not grant marketing permission.

Send `shop flow`, `shop`, `browse`, or `search` in the active WhatsApp chat. The main menu's shop/search choices also open the Flow when configured. When disabled/incomplete, ordinary chat shopping remains available. Interactive Flow launch is for an active customer-service window; business-initiated messages outside the window need an approved Flow template (not implemented here).

For PesePay, enable the Visa/Mastercard checkbox only after the provider activates card acceptance. For Paynow, save `cardPaymentsEnabled: true` on the existing Paynow integration credentials through the authenticated integrations API. Keep `isSandbox: false` and complete live credentials. A pending order must have a real HTTPS hosted link before the card choice appears. The existing gateway priority is Paynow, then PesePay.

## Endpoint and operational behavior

- Raw-body HMAC-SHA256 is mandatory; bad signatures return 432. RSA-OAEP SHA256 / AES-128-GCM implements Meta's request envelope; responses invert the request IV. Decryption failures return 421. Expired/revoked tokens return encrypted 427 responses.
- Opaque tokens expire after 30 minutes; SHA-256 token hashes are stored in the new session table. Business/customer identity is resolved server-side, never accepted from submitted fields.
- Terms acceptance is enforced on every shopping exchange. Selection is restricted to products offered in that session. Quantity is 1–10, bounded by live stock including existing basket quantity. Prices are read server-side; a changed displayed price requires searching again.
- Each Flow adds one product once. Serializable transactions and stored results protect retries and double clicks. Stock is checked but not reserved; final order/fulfilment remains subject to the existing checkout's stock handling.
- Image URLs must come from exact operator-approved public HTTPS CDN origins. JPEG/PNG/WebP sources up to 5 MB are converted into 240px JPEG thumbnails up to 80 KB, with a 2.5-second download timeout and no redirects; unavailable/unsupported images use a placeholder. Configure small public product thumbnails for reliable rendering. Do not allow origins that resolve to private/internal networks.
- Clean up expired `ShoppingFlowSession` and `PaymentContactFlowSession` rows through a scheduled database maintenance job. Retain acceptance records according to your published retention policy. No automatic cleanup schedule is installed here.
- Production payment factory rejects mock/sandbox/incomplete live configurations. The sandbox webhook cannot confirm live payment records and is disabled in production. PesePay HTTPS now verifies server certificates; repair an upstream certificate-chain problem rather than disabling verification.
- Payment initiation/confirmation reuse the existing gateway/webhook architecture. This change does not certify those providers' complete production integration or their merchant card availability. Test successful, declined, cancelled, delayed and duplicate callbacks before enabling live cards; do not treat the browser return as payment proof.

No Meta publication, server restart, database update, live payment or external customer message is performed by this source change.


## Second Flow: payment contact phone number

`payment-contact-flow.json` is a separate Flow, with endpoint `POST /whatsapp/flows/payment-data-exchange`. It uses the same server RSA key and app secret as shopping. It does not need a new payment gateway.

## Active hybrid journey (updated)

The bot now requires first-time/versioned terms and privacy acceptance before the welcome menu. The published Shopping Flow is used for consent, and the dedicated Search-first Flow (`1791484795407520`) is used for search: accepting closes the Flow with a verified `consent_flow` completion and sends the existing welcome menu; selecting a search result closes it with a verified `search_flow` completion and resumes existing product/pack/quantity handling in chat. Shop/category browsing, basket, checkout and delivery retain their existing chatbot handlers. Search does not add to the basket inside the Flow in this mode.

Order confirmation always offers Visa/Mastercard, EcoCash and InnBucks. Visa/Mastercard uses the existing contact Flow and hosted provider link when merchant card checkout is enabled. EcoCash retains the existing number collection/push handler. InnBucks uses the existing USD PesePay hosted link; the customer chooses InnBucks on that page if enabled for the account/order. There is no new seamless InnBucks implementation.

Search uses `search-flow-visual.json`, whose first screen is SEARCH. The older Terms-first Flow cannot be launched on SEARCH: Meta rejects it with error 131009. The endpoint completes consent/search using Meta’s reserved `SUCCESS` response. Existing basket-mode sessions remain compatible with the older full shopping screens described below. New chatbot launches explicitly select consent or search mode.

Legacy basket-mode journey: existing chat checkout → confirmed pending order → Visa/Mastercard choice → payment-contact Flow → prefilled WhatsApp number (editable) → server validation and `Order.paymentContactPhone` update → Return to chat → existing provider payment link → provider verification and WhatsApp confirmation. The sender's original WhatsApp number stays the order owner and notification destination. Editing contact information cannot redirect order ownership or payment confirmation.

In Dashboard → Integrations → WhatsApp, enter the second published Flow ID and enable **card-payment contact Flow**, then Save Integration. These persist as `paymentContactFlowId` and `paymentContactFlowEnabled`. Live provider `cardPaymentsEnabled` is still required. If the second Flow is not configured, the prior direct hosted-link journey remains available.

This is a contact number, not a Visa/Mastercard account identifier or an OTP-verified number. It is prefilled from the sender already known to the backend; the Flow cannot import device contacts. Existing `PaymentGateway.createPaymentLink` accepts order, amount, currency, email and callback URL; it has no phone argument. Accordingly the number is saved on the WHC order, **not silently forwarded in an undocumented provider payload**. Card entry and any bank verification occur on the provider's page. A separate documented provider API would be needed to pass billing-phone metadata to that provider.

See `META-SETUP.md` for the complete two-Flow setup and backend route map.


## Type policy text instead of supplying external links

Dashboard → Integrations → WhatsApp now accepts **Full shopping terms text** and **Privacy notice text**. Leave external URL fields empty to use WHC-hosted pages. The existing published Flow does not need a JSON change: its existing Read full terms / Privacy policy links receive generated URLs at runtime. The summary is displayed directly in the Flow; when omitted, it uses the first 3,400 characters of the full terms text.

Hosted routes: `GET /whatsapp/policies/:businessId/:version/terms` and `/privacy`. They return escaped plain text only, never integration credentials. Set a new terms version when changing policies. Hosted pages serve the currently configured version; old versions are not archived by this feature. Keep a separate version archive according to your business requirements.

Payment setup still requires a separately published payment-contact Flow ID and provider-confirmed Visa/Mastercard acceptance. Presence of live PesePay credentials alone does not prove card acceptance. These settings are deliberately not inferred or enabled by policy configuration.


## Shared Visa/Mastercard checkout Flow

`card-payment-flow.json` is the redesigned shared card Flow (published ID `1650131740149133`). It replaces the old contact-only Flow in the merchant configuration, using the same payment-data-exchange endpoint and completion checks. The first screen displays the owned pending order number, total/currency and contact phone. Card number, expiry and CVV entry remain on the PesePay-hosted page, as required by its redirect-only card integration: https://developers.pesepay.com/payment-methods/card-payments/. No card transaction was created as part of publishing this Flow.
