# 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](/agents/errors/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](/agents/steps/config-and-transport#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](/agents/steps/capture-refund-void#reading-the-rows), 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](/agents/verify/readiness-preflight).