Pressify fake payment server

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.

Localhost only. Never deploy this. 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.

Configuration in effect

  • PORT3010
  • SETTLE_DELAY_MS1500
  • CALLBACK_SECRET set · sha256 7f4f88022fea…

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.

Endpoints

MethodPathPurpose
GET/checkoutRender the checkout. Requires reference, amount, notifyUrl, returnUrl.
POST/checkout/settleSettle: post the signed callback, answer { redirectUrl }. The page performs the redirect.
GET/transactionsIn-memory log of everything that happened. Dies with the process.
GET/healthContainer health check.

Dev controls

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.

ParameterEffectWhat it exercises
callbackDelayMsNotify this long after the redirectThe browser returning before the grant lands — the SPA's polling
callbackTimes=2Send the identical callback twiceIdempotency: the shop must be granted exactly once
callbackNever=trueRedirect without notifyingA lost webhook: a paid shop that stays uncovered
amountOverrideNotify a different amount than quotedThe backend refusing a mismatched callback
unsigned=trueOmit the signature headerThe callback route's rejection path

The callback it posts

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.