A dev-only stand-in for a hosted mobile-money checkout. It renders a checkout, signs and posts the callback to the Pressify API, and — its reason to exist — sends the browser back to the SPA afterwards.
notifyUrl and returnUrl are query parameters that cause an outbound POST and a
redirect, so anyone who can reach this server can aim both. That is harmless on a developer machine and
unacceptable anywhere else. Adding authentication would not fix it; not deploying it does.
The secret must equal the API's Subscription:SimulatedPayment:CallbackSecret. Compare the
fingerprint above with echo -n "<secret>" | shasum -a 256 if callbacks
are coming back 401.
A real one is built by the API's SimulatedSubscriptionPaymentProvider.OpenCheckoutAsync.
| Method | Path | Purpose |
|---|---|---|
| GET | /checkout | Render the checkout. Requires reference, amount, notifyUrl, returnUrl. |
| POST | /checkout/settle | Settle: post the signed callback, answer { redirectUrl }. The page performs the redirect. |
| GET | /transactions | In-memory log of everything that happened. Dies with the process. |
| GET | /health | Container health check. |
Optional query parameters on /checkout. Each reproduces a provider behaviour
the backend claims to survive, and each is announced on the checkout page so a rigged payment never looks
like an ordinary one.
| Parameter | Effect | What it exercises |
|---|---|---|
| callbackDelayMs | Notify this long after the redirect | The browser returning before the grant lands — the SPA's polling |
| callbackTimes=2 | Send the identical callback twice | Idempotency: the shop must be granted exactly once |
| callbackNever=true | Redirect without notifying | A lost webhook: a paid shop that stays uncovered |
| amountOverride | Notify a different amount than quoted | The backend refusing a mismatched callback |
| unsigned=true | Omit the signature header | The callback route's rejection path |
POST {notifyUrl}
Content-Type: application/json
X-Simulated-Payment-Signature: <hmac-sha256 of the body, hex, lowercase>
{"reference":"…","outcome":"Paid","amount":20000,"occurredAt":"…Z"}
The signature covers the exact bytes of the body, which the API verifies before parsing. A 200 means received and queued — not that the shop was granted anything. Only the subscription status read proves that.