Skip to content

feat(payments): multi-asset intents and patient-signed prepare - #160

Open
habeebti wants to merge 7 commits into
DocForum:mainfrom
habeebti:feat/payment-assets-and-prepare
Open

habeebti wants to merge 7 commits into
DocForum:mainfrom
habeebti:feat/payment-assets-and-prepare

Conversation

@habeebti

Copy link
Copy Markdown

What changed

Closes #79
Closes #80

Payment assets (issue #79)

  • PaymentIntent now stores assetCode + tokenContractId, and amountStroops is renamed to the token-agnostic amount. The migration is a plain column rename plus a nullable tokenContractId backfill — existing rows keep their exact value and resolve native XLM from ESCROW_TOKEN_ID (documented legacy fallback). No code still assumes “stroops of XLM”.
  • PAYMENT_ASSETS is a per-environment allowlist (JSON: { code, tokenContractId, decimals, issuer? }). Testnet defaults are native XLM + USDC SACs; production must set it explicitly. Entries opting into clawback/rebasing are rejected (escrow threat model T5). An intent in any other asset is 400 UNSUPPORTED_PAYMENT_ASSET.
  • Amounts are always in the asset's smallest unit and are never scaled/converted, so the server makes no decimal assumption. decimals is carried for clients only.
  • Before funding or preparing an issued asset, the payee's Horizon account is checked for an authorised trustline; failure is 409 PAYEE_TRUSTLINE_MISSING and happens before any SDK/on-chain call. Native XLM skips the check.
  • Testnet integration now has an always-on live-Horizon USDC trustline-rejection case and an opt-in (PAYMENT_TESTNET_USDC=true) USDC fund→release case.

Prepare endpoint (issue #80)

  • POST /payments/intents/:id/prepare: patient-only, verified email, and ownership is enforced in the service (someone else's intent is 404, not 403). Requires a verified PatientWalletLink (409 WALLET_NOT_LINKED) and a linked facility payee (409 FACILITY_WALLET_NOT_LINKED).
  • Builds + simulates the patient-as-payer create_escrow via the SDK seam, stores preparedTxHash/preparedXdr/preparedExpiryLedger/preparedExpiresAt, and moves created → awaiting_signature with a conditional UPDATE in the same transaction as its AuditEvent.
  • Only one live prepared transaction per intent: re-prepare while valid returns the exact same transaction; prepare after expiry rolls it back and builds a fresh one.
  • refundAfter = order expiry + configurable grace. Orders have no expiry field yet (Phase 4 placeholder), so the intent's createdAt is the documented anchor; it is never set in the past.
  • New awaiting_signature / submitting enum values; an interval job (src/jobs/payment-intents.ts) sweeps expired prepared transactions back to created as a normal case and records payment_intent.prepare_expired.

Docs

ROADMAP (items + changelog), ARCHITECTURE §3/§4.10/new §4.12, ARCHITECTURE_ESSENTIALS (payments + gaps), docs/api (payments routes, state machine, env knobs, export v2), ADR 0004 update, ADR 0005 implementation notes/status, .env.example, jobs README.

Verification

backend/: npm run typecheck ✅, npm run build ✅, npm test → 9 files / 93 tests ✅, npm run test:integration → 10 files ✅ (includes new payments-assets and payments-prepare suites covering allowlist, USDC vs XLM, trustline rejection, legacy fallback, prepare idempotency, expiry → re-prepare, wrong patient, missing wallet links).

Assumptions / limitations (explicit)

🤖 Generated with Codebuff

Add a per-environment asset allowlist (USDC + native XLM) with a
token-agnostic amount, payee trustline checks, and the prepare half of
the non-custodial flow: an unsigned create_escrow XDR, new
awaiting_signature state, and an expiry sweep back to created.

Closes DocForum#79
Closes DocForum#80

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@habeebti Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@habeebti Thanks for the PR. It can't be merged yet. To unblock it:

Merge conflicts: this branch conflicts with main. Please rebase onto the latest main (git fetch upstream && git rebase upstream/main), resolve the conflicts (often ROADMAP.md, ARCHITECTURE*.md or prisma/schema.prisma), and force-push.

Push to this same branch and CI will re-run. Once it's green and conflict-free, it'll be reviewed for merge.

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 20, 2026
@github-actions

Copy link
Copy Markdown

@habeebti This PR now has merge conflicts with main, most likely because #153 (feat(referrals): Phase 3 referral core — creation, accept/decline, cascade, loop guard, admin overrides) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 20, 2026
@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@habeebti CI is failing on this branch (after it was updated with the latest main), so it can't be merged yet. The errors from the latest run:

Error: Prisma schema validation - (get-dmmf wasm)
error: Error validating field `patientProfile` in model `PatientWalletLink`: The relation field `patientProfile` on model `PatientWalletLink` is missing an opposite relation field on the model `PatientProfile`. Either run `prisma format` or add it manually.

The new PatientWalletLink.patientProfile relation needs a back-relation field on PatientProfile. Run npx prisma format in backend/ and commit the result. CI will re-run on your next push.

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 20, 2026
@github-actions

Copy link
Copy Markdown

@habeebti This PR now has merge conflicts with main, most likely because #183 (feat(perf): k6 load tests for search, slot listing and booking contention) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 21, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants