Implementation Traps

An independent review of a first implementation found every one of these. Check your code against each row before writing the connecting code, and again before asking for a review.

Orders and concurrency

Trap Correct behaviour
Two payable sales for one order, from two tabs, switching flows or concurrent requests Take a per-order lock around every call that opens or pays a sale. Once a hosted-page sale is open, refuse other flows for that order, because the buyer can still pay it
Concurrent capture, refund or void on one sale The same order lock. PayMe also answers error 998 to a duplicate still running
A rejected duplicate pay-sale marks the sale failed from a stale row Re-read the row inside the lock before writing
A lock that exists only in one process In-process locks protect one instance only. Enforce a single instance, or use a shared lock

Unknown outcomes

Trap Correct behaviour
A refund with an unknown outcome (see Reading a failure) Re-read the rows. A refund row settles it, but no row doesn't prove there was none, so keep refunds blocked until a person records the total. Never clear this automatically
A plain refusal arriving as HTTP 500 treated as unknown It freezes the order for nothing. status_code 1 with an error code is a refusal, apart from the listed exceptions
A money call worked, then the read-back query failed Answer success with a "check this" flag, and let reconciliation clear it (TRN-07). An error invites a repeat
Reconciliation clears the "check this" flag when the query returned nothing Clear it only when PayMe returned the sale, under the order lock
A payout with an unknown outcome is retried Refuse a new payout in that currency until a person resolves the old one against get-withdrawals
generate-subscription with a saved card is retried after a timeout Insert a guard row, and refuse a new subscription for that seller and buyer until it's resolved
The pending check after a successful pay-sale never clears Clear it when a query confirms the sale

Callbacks

Trap Correct behaviour
Unsigned lifecycle callbacks deduplicated by a key with no transaction ID Pause, resume, pause sends the same body twice. Don't deduplicate re-reads
A void's refund callback counted as refunded money A void releases a hold. Skip it when the status is voided
A refund allowed because the status is partial-refund Use your own refunded total, because a sale refunded in parts never moves to refunded
Refund totals taken from callbacks Add from each refund-sale response and each refund row, one per payme_transaction_id. A callback only triggers the re-read (CB-01). Once a person has recorded a total, alert on later rows rather than adding them
Any row with "refund" in its action counted as a refund Chargeback Refund isn't one, and a failed attempt isn't money back. Match the exact values and statuses
A paid order stuck because failed was treated as final A declined hosted sale can still complete. Keep re-reading, and allow failed to completed or authorized
An error thrown in the reconcile timer stops the process Catch and log it

Values

Trap Correct behaviour
A saved card charged without a buyer reference Require your buyer reference, and it must match the card's
A number parser accepting 5e2, 0x1f4 or 600 as an amount Digits only for minor units
market_fee: null becoming 0 Treat null as "not given"
A market_fee configured as 1% A plain number, such as 1

Before calling it reviewed

Commission a reviewer that didn't write the code, against these docs. Fix or record every finding. Then go to Readiness Preflight.