Steps
Rules, traps and order for each part of the build
- Config and Transport: Build configuration and one PayMe client first. Choose the environment from config, keep credentials out of code, treat status_code 0 in a 2xx response as the only success, read a failure by its body (HTTP 500 with status_code 1 and an error code is a refusal), do not retry money calls with an unknown outcome, omit local callback URLs (error 21), keep two public URL settings, and use dd/mm/yyyy on seller and subscription dates but yyyy-mm-dd hh:mm:ss on query date filters.
- Money: PayMe amounts are integers in minor units. generate-sale rounds a decimal sale_price rather than refusing it, so 600.75 becomes 601 and charges 6.01, and 50.75 becomes 51 and fails with error 352. The default range is 500 to 4000000 per account. Convert only for display, and reject bad amounts before calling PayMe.
- Seller Onboarding: Partners onboard sellers through PayMe's white-label form or create-seller. In the sandbox seller_phone 0501234567 failed with error 25 and +972501234567 passed. Dates are dd/mm/yyyy. Store seller_payme_id and the public key, ignore seller_payme_secret, read state from get-sellers, and never block checkout on approval, which gates payouts only.
- Checkout: Every PayMe flow starts with generate-sale. Default to the hosted page, add Hosted Fields when the checkout must look native, use a saved token only when no buyer is present. Keep one open sale per order: a failed hosted sale stays payable, so reuse it, send your order ID as transaction_id, store payme_sale_id, fulfil only after a query confirms payment, and use sale_expiry_seconds to limit how long a payment page stays open.
- Callbacks (Partner): For partners, a sale callback's valid signature means only worth checking, because it covers credentials and two IDs, not status or amount. Confirm with get-transactions using the seller's MPL and match your own order. Subscription, seller and withdrawal-complete callbacks are unsigned, so re-read them. Delivered means below 400, 4xx is retried, and a failing domain can be skipped, so reconcile, including finished orders, and a failed sale can still complete.
- Callbacks (Direct Seller): A direct seller cannot check payme_signature, because it needs partner credentials. Treat every callback as a trigger: find your order by the payme_sale_id you stored, call get-transactions with your MPL and your order ID, check the rows, then apply PayMe's answer. Reply below 400 for recorded decisions, rate-limit lookups, and re-check on a schedule because callbacks can be skipped.
- Capture, Refund, Void: An authorization lasts 7 days and captures once; a second capture fails with error 305 and a late one with 516. A refund with no amount after a partial refund returns exactly the rest, and the sale stays partial-refund, so track the total. refund-sale on an uncaptured authorization voids it. Refund rows in get-transactions have the action Refund or Partial Refund; a void adds none. Partial capture's remainder is unconfirmed, so do not offer it yet.
- Saved Cards: Saved cards use a token. capture_buyer, Hosted Fields and capture-buyer-token give a buyer_key; POST /sellers/{mpl}/tokens gives a uuid sent as token, and it worked across a partner's sellers in the sandbox. Without buyer_is_permanent, a second charge failed with error 604. Encrypt tokens, never send them to a browser. A direct seller sends buyer_is_private: true and fetches the token with get-buyer-key after confirming the sale.
- Subscriptions: PayMe runs the subscription schedule, so never build a charging scheduler. Subscription callbacks are unsigned: re-read with get-subscriptions using the seller's MPL in the JSON body. Grant service for each increase in sub_iterations_completed, not per notification. Status 7 is failed-retry, so don't dun the buyer. Monthly start days must be 1 to 28, and a token used here must be permanent.
- Balances and Payouts: For partners, balances are in seller_wallets on get-sellers. wallet_releasable comes from release dates. A withdrawal can be refused because the seller is not validated (109) or is on an automatic plan (907). withdrawal-complete is unsigned and fires on commit, before the bank transfer, so re-read with get-withdrawals. market_fee reaches the platform separately, by default monthly.
- Implementation Traps: A checklist of traps found by independent review of a real PayMe integration: two payable sales for one order, concurrent capture or refund, unknown refund outcomes, reconcile clearing flags on empty answers, retried payouts or subscriptions, counting a void as a refund, and lenient number parsing. Check your code against each row before review.