Notes

Moving from v1 to v2

v2 launches in early 2027, and v1 is supported for years after it, so move when it suits you. The objects stay what they were: payment links, transactions, subscribers, seq, and pings. What changes is how you name them, how strictly we read a request, and how clearly an error says what went wrong.

Until v2 launches, its pages are its spec, and may change.

Requests

v2 names the shop in every URL, and an object by its kind and ID, where v1 takes the shop from the key:

v1 v2
POST /v1/new POST /v2/{shopid}:pay/new
POST /v1/new with "subscriber" POST /v2/{shopid}:sub/new
POST /v1/transactions/{trnid}/capture POST /v2/{shopid}:trn:{trnid}/capture
POST /v1/transactions/{trnid}/refund POST /v2/{shopid}:trn:{trnid}/refund
None: a void is made in the dashboard POST /v2/{shopid}:trn:{trnid}/void
POST /v1/subscribers/{subid}/charge POST /v2/{shopid}:sub:{subid}/charge
POST /v1/subscribers/{subid}/renew POST /v2/{shopid}:sub:{subid}/renew
None DELETE /v2/{shopid}:pay:{payid}
None DELETE /v2/{shopid}:sub:{subid}
GET /v1/seq/{seq} GET /v2/{shopid}:seq:{seq}

See v2's URLs.

Keys

A v1 key is {shopid}:{secret}. A v2 key starts with whose it is, and the key's own number: s{shopid}#{keyid}:{secret} for a shop's key, and there are keys for users and partners too. Each v2 key has its own permissions and its own ping URL, where a v1 shop has one ping URL for all its keys. See v2's Authentication.

Stricter requests

v1 reads a request leniently, and makes the best of what it can't use. v2 refuses it, so that a mistake shows at once and not in the payments:

  • A field of the wrong type, or null, is ignored in v1, and an error in v2: see v2's Request bodies.
  • "orderid" is optional in v1, and required in v2.
  • An amount with more decimals than the currency has is cut down in v1, as "10.999 DKK" to 10.99 DKK, and an error in v2: see v2's Amounts.
  • A "lifetime" that is too short, too long, or unreadable becomes the nearest one v1 allows, or an hour, and is an error in v2, which takes "5m" to "30d".
  • "index" may be left out of a capture or a refund in v1, which reads it as 0, and is required in v2.
  • v1 accepts a payment that the bank authorizes only in part, for what it authorized; v2 declines it, so a transaction is always for the whole amount. In v1, check totals.authorized, as What was authorized says.

Answers

  • Create payment link answers with the link's "id" beside its "url" in v2, so that you can delete the link, and match it to its transaction.
  • v1 answers most failures with 500, and its message says what failed. v2 answers each kind with its own status: 404 for an object that doesn't exist, 409 for a conflict, such as 409 index mismatch, 402 for a declined charge, and 410 for a deleted subscriber. See v2's Errors.
  • A declined charge in v2 says whether to try again: its message starts with transient or permanent. See When a charge fails.
  • Idempotency-Status: OK is on every answer to a request with a key in v1, and marks only a replay in v2.
  • In seq, an act says who made it with "who", a key's or a user's ID, in v2, where v1 has "uid" and "apikey". A transaction's card is always under method.card in v2, where v1 names the object for the method, as method.applepay. See v2's transaction object.

How to move

  1. Get v2 keys, and give each a ping URL.
  2. Change your requests' URLs, and send "orderid" and "index" always, and fields of the types that v2's reference gives.
  3. Branch on v2's status codes where you parsed v1's messages, and on transient and permanent for a failed charge.
  4. Read seq's acts by "who", and the card from method.card.
  5. Test it all in the test environment, as for a first integration: Before going live.