Payment Workflow
Money is the one impact nobody argues with. Test the flow, not the payment page. The gateway is rarely the weak part.
Map it first
cart → quote/price → create order → redirect to PSP → callback → fulfilWrite down which side computes the price at each step, and which step actually flips the order to paid.
Price and quantity
Anywhere the client sends an amount, change it.
price=99.99 → price=0.01 price=0 price=-1
quantity=1 → quantity=-1 quantity=0
currency=USD → currency=IDR (same number, weaker currency)
amount=1000 → amount=1e2 amount=0x10 amount=1000.000001Negative quantity on one line item against a positive on another can net a refund.
Rounding
The reliable one. Buy many cheap items and let per-item rounding fall in your favour.
100 × 0.014 → charged 0.01 each → 1.00 instead of 1.40Also: currency conversion applied twice, or applied before discount on one path and after on another.
Coupons
- Apply the same code twice: sequentially, then in parallel (see Race Conditions)
- Stack two “cannot be combined” codes
- Apply the coupon, then remove the qualifying item
- Apply to a cart, then change the cart without re-validating
- Percentage discount on a negative line
- Expired or draft codes: is the check server-side?
The callback
Where most real bugs are.
- Is the PSP callback signature verified, or is any POST to
/payment/successaccepted? - Can you call the success URL directly, without paying?
- Replay a genuine callback from a 0.01 order onto a 1000.00 order
- Change
order_idin the callback to another user’s order - Is
status=paidtaken from the request or looked up against the PSP?
POST /payment/callback
{"order_id":"1337","status":"paid","amount":"0.01"}State machine
confirm twice
cancel → then confirm
confirm → then cancel → is it still fulfilled?
refund → refund again
ship → then refund
pay with a card that will fail, but fulfil firstStored value
- Gift cards: race the redeem, brute the code space, check the entropy
- Store credit: refund an item bought with credit and receive cash
- Loyalty points: convert points → credit → cash, look for a rounding gain each hop
- Subscriptions: downgrade mid-cycle and keep the annual credit
Test-mode keys
pk_test_ / sk_test_ in production means real orders can be paid with test cards.
grep -rEo '(pk|sk)_(test|live)_[A-Za-z0-9]+' bundle.jsA live secret key in a JS bundle is a critical on its own. Do not use it, just report it.
Rules of engagement
Use the sandbox and test cards if the program provides them. If you must transact for real, use the smallest amount, do not take delivery, refund immediately, and put the transaction IDs in the report.