caon.io

Payment Workflow

https://soroush.me/downloadable/common-security-issues-in-financially-orientated-web-applications-_v1.1.pdf

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  →  fulfil

Write 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.000001

Negative 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.40

Also: 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/success accepted?
  • 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_id in the callback to another user’s order
  • Is status=paid taken 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 first

Stored 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.js

A 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.

Articles

↑↓ navigate↵ openesc close