Skip to content

Feat/fedapay error reasons - #6

Merged
jules-mahounou merged 4 commits into
mainfrom
feat/fedapay-error-reasons
Oct 5, 2026
Merged

jules-mahounou merged 4 commits into
mainfrom
feat/fedapay-error-reasons

Conversation

@jules-mahounou

Copy link
Copy Markdown
Collaborator

What & why

Checklist

  • Commits are signed off (git commit -s) — see DCO
  • Tests added or updated
  • If this touches routing/fallback: tests prove no double charge is possible
  • Docs and CHANGELOG.md (Unreleased) updated if behavior changed
  • No real secrets or personal data in code, tests, or fixtures

jules-mahounou and others added 4 commits October 5, 2026 15:24
To check that a real USSD prompt reaches a phone. Real money, so:
- explicit opt-in (DEMO_LIVE=yes) and a separate FEDAPAY_LIVE_SECRET_KEY;
  a sandbox key is refused in live mode and a live key in sandbox mode
- amounts capped by DEMO_LIVE_MAX_AMOUNT (default 200, at most 1000 XOF)
- warning in the terminal and on the page
- the server now only listens on 127.0.0.1

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
Found with the founder's first live payments (2026-10-05):
- a canceled MTN payment carried `last_error_code: INSUFFICIENT_FUND_ERROR`,
  but Payenv only reported a generic CUSTOMER_DECLINED;
- a Celtiis payment canceled on the phone stayed `pending` at FedaPay with
  `last_error_code: API_ERROR`, and Payenv showed no reason at all.

core: PayenvError gains `providerCode`, serialized with the error, so every
connector can keep the provider's own code.

connector-fedapay: map `last_error_code` (INSUFFICIENT_FUND_ERROR →
INSUFFICIENT_FUNDS, API_ERROR → PROVIDER_UNAVAILABLE, others kept as
providerCode), also on transactions that stay pending. Status is never
changed from what FedaPay reports. Tests reproduce both live transactions;
verified against them read-only.

docs: QUIRKS (verified live), README guidance for payments stuck in pending.
demo: shows the reason while pending and what to do after a minute.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
The full live transaction shows that `last_error_message` is the
operator's raw SOAP answer: the status query succeeded and the operator
still reported `TransactionStatus: "Initiated"`. API_ERROR therefore
means "no final status from the operator", not an outage: map it to
UNKNOWN_ERROR with an accurate message. The raw dump is kept in
`error.raw` (never serialized).

QUIRKS: FedaPay schedules an expiration for pending transactions
(`metadata.expire_schedule_jobid`, `expired_at`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
A live `transaction.canceled` webhook showed the shape
`{ name, object, object_id, account, entity }`, where `entity` is the
full transaction. `parseFedaPayWebhook` normalizes it with the same
mapping as status lookups, deriving the status from the transaction
rather than the event name, so unknown event names still work.

- INSUFFICIENT_FUND_ERROR message now reflects MTN's actual reason
  (low balance, or a limit reached, or not allowed)
- demo: a verified webhook triggers payenv.refresh (webhooks are hints);
  verified end to end with a signed and a forged webhook
- QUIRKS: webhook shape, and a warning that webhooks carry merchant and
  payer personal data

Test fixtures are scrubbed of account details, phone numbers and tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
@jules-mahounou
jules-mahounou merged commit 0ce7d55 into main Oct 5, 2026
4 checks passed
@jules-mahounou
jules-mahounou deleted the feat/fedapay-error-reasons branch October 5, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant