Transactions
A transaction is an authorized payment. You capture it to take the money, and refund what you captured. Seq returns it, as a whole object, whenever it changes.
Requests
| Request | What it does |
|---|---|
POST /v1/transactions/{trnid}/capture |
Capture some or all of what is left of the authorization. |
POST /v1/transactions/{trnid}/refund |
Refund some or all of what was captured. |
v1 has no request to void a transaction. A transaction can be voided in the dashboard, and the void then comes through seq as an act; API v2 adds a void request.
A transaction comes into being in one of three ways, and seq returns
it in each: a payer pays a payment link, which makes
a "transaction"; a payer pays the first charge of a
subscriber link as they save their
card, which makes a "charge"; or you
charge a subscriber, which makes a "charge"
and answers with its "id". Either way, its "id" is the {trnid}
of the requests above.
The transaction object
Every field below is always there, but where its row says otherwise.
A field without a value is an empty string, an empty array, or, for
"locked", null; a client must skip fields it doesn't know, as
Compatibility says.
| Field | Type | Description |
|---|---|---|
type |
string | "transaction", or "charge" for a charge of a subscriber. |
id |
integer | The transaction's ID, the {trnid} of its requests. A shop's transactions are numbered from 1. |
rev |
integer | The revision: 1 when the transaction is created, and one higher with each change. Of two copies of a transaction, the one with the higher "rev" is the newer. |
locked |
object or null | null, or {"uid": 7812} while we have locked the transaction, where "uid" is the ID of the user who locked it. A locked transaction refuses captures and refunds until it is unlocked. |
method.type |
string | How the payer paid: "card", "applepay", or "mobilepay". |
method.id |
string | An opaque ID of the card or device, the same for each payment with it in your shop. Use it to recognize a returning card, as for fraud checks; you can't charge it. |
method.{type} |
object | The card that paid, named for method.type: method.card for a card, method.applepay for Apple Pay, and method.mobilepay for MobilePay. Look it up by the name in method.type, not as "card", and check that it is there, as a later method may have none. |
method.{type}.brand |
string | The card's brand: "visa", "mastercard", "maestro", "dankort", "visadankort", "forbrugsforening", "amex", "diners", "discover", "jcb", or "unionpay", or empty when we don't know it. See the knowledge base's Payment methods. |
method.{type}.last4 |
string | The last four digits of the card number. |
services |
array of strings | What took part in the payment: "3dsecurev2", "applepay", and "mobilepay", or on older transactions "3dsecure" and "dsbn"; and the acquiring agreement, such as "clearhaus-3" or "nets-1", by the name the dashboard gives it. |
info.ip |
string | The IP address, v4 or v6, of the browser that paid. |
info.ndisplay |
integer | How many times the payment window was shown before the payment succeeded. Not in a charge. |
info.nfail |
integer | How many payments were declined before it. Not in a charge. |
time.created |
integer | When the payment link was created, in Unix time: seconds since 1970-01-01 UTC. |
time.authorized |
integer | When the payment was authorized, in Unix time. |
orderid |
string | Your order ID, from the payment link or the charge. |
billing |
object | The billing address, from the payment link or the charge: "name", "company", "vatin", "gln", "email", "phone", "address", an array of lines, "city", "zip", "state", and "country", each as it was given, or empty. See Addresses. |
shipping |
object | The shipping address, with the fields of "billing" but "vatin" and "gln". |
items |
array of objects | The items, from the payment link or the charge, each with "quantity", "total", "name", and "sku", in the order given. "quantity" is 1 where none was given, and the strings are empty where none was given. |
acts |
array of objects | The acts: its captures, refunds, and voids, oldest first. |
totals.attempted |
amount | The sum of the items' totals: what the payment asked for. |
totals.authorized |
amount | What the issuer authorized. It can be less than "attempted", as a bank may authorize part of a payment, and v1 accepts that: see Partial authorizations. |
totals.captured |
amount | The sum of the captures. |
totals.refunded |
amount | The sum of the refunds. |
totals.voided |
amount | The sum of the voids, or 0. |
totals.left |
amount | What can still be captured: authorized - captured - voided, or 0 if that is less than 0. |
A transaction that was paid, captured, and partly refunded:
{
"type": "transaction",
"id": 378,
"rev": 3,
"locked": null,
"method": {
"type": "card",
"id": "WkS3rJht/unginSCpOEwqVtH6SJWt4+StC8TWl+kP6w=",
"card": {
"brand": "visadankort",
"last4": "1234"
}
},
"services": [
"3dsecurev2",
"clearhaus-3"
],
"info": {
"ip": "198.51.100.74",
"ndisplay": 2,
"nfail": 0
},
"time": {
"created": 1727088810,
"authorized": 1727088919
},
"orderid": "00029384",
"billing": {
"name": "Peter Nielsen",
"company": "Nielsen A/S",
"vatin": "DK91738264",
"gln": "7953878173521",
"email": "pn@nielsenas.dk",
"phone": "+45 90014893",
"address": ["Pederstrupvej 14"],
"city": "Teststrup",
"zip": "8320",
"state": "",
"country": "DK"
},
"shipping": {
"name": "Peter Nielsen",
"company": "Nielsen A/S",
"email": "pn@nielsenas.dk",
"phone": "+45 90014893",
"address": ["C/O Peter Nielsen", "Pederstrupvej 14"],
"city": "Teststrup",
"zip": "8320",
"state": "",
"country": "DK"
},
"items": [
{
"quantity": 2,
"total": "199.90 DKK",
"name": "Danmarkskort plakat",
"sku": "182932000-MAP-DK"
},
{
"quantity": 1,
"total": "45 DKK",
"name": "Fragt - PostNord",
"sku": ""
}
],
"acts": [
{
"act": "capture",
"time": 1727109740,
"uid": null,
"apikey": "",
"total": "244.90 DKK",
"history": []
},
{
"act": "refund",
"time": 1727196369,
"uid": 7812,
"apikey": "",
"total": "99.95 DKK",
"history": []
}
],
"totals": {
"attempted": "244.90 DKK",
"authorized": "244.90 DKK",
"captured": "244.90 DKK",
"refunded": "99.95 DKK",
"voided": "0 DKK",
"left": "0 DKK"
}
}
Acts
A capture, a refund, or a void is an act. Each one that succeeds is
added to "acts", and raises "rev" by one:
| Field | Type | Description |
|---|---|---|
act |
string | "capture", "refund", or "void". A void is made in the dashboard, as v1 has no request for it. |
time |
integer | When it was done, in Unix time. |
uid |
integer or null | The ID of the user who made the act in the dashboard, or null for an act made with an API key, or by an "autocapture". |
apikey |
string | Always "". v1 doesn't record which key made an act. |
total |
amount | How much it captured, refunded, or voided. |
history |
array of objects | Which items the act covered, each as {"item": 0, "quantity": 1, "total": "99.95 DKK"}, where "item" indexes "items", or []. An "autocapture" records one; an act of yours has [], as v1's requests take no "history". Don't rely on it for amounts: "total" is the truth. |
Acts made in the dashboard come through seq like your own, with the
user's ID in "uid". So an act with a "uid" is one that you didn't
make, and your system should take it as it comes.
Index
The number of acts a transaction has is its index, the length of
"acts". A capture or a refund sends it as "index", which you know
from the transaction as seq last returned it. If an act has been made
since, the index is stale, and the request fails with
500 transaction database error: index mismatch without doing
anything. So two requests made from the same state, as by two of your
servers, or by you and someone in the dashboard, can't both go
through. See Retries and idempotency.
v1 lets you leave "index" out, and then takes it as 0, which is
right only for a transaction without acts. Always send it: without it,
a second capture fails, and a first one is not checked against what
someone else may have done since you last looked.
Rules
- Capture any amount up to
"left", as many times as you like."left"goes down by each capture. - Refund any amount up to
captured - refunded, as many times as you like. - Void is in the dashboard alone. Once a part is captured, the rest can't be voided, and is released when the authorization runs out.
- A transaction's amounts are all in its items' currency. Send every act in it.
- A locked transaction refuses every act until it is unlocked.
v1 doesn't check an act's "total" against "left", what was
captured, or the transaction's currency: it sends the act to the
acquirer, which refuses what it won't do, with an
acquirer error. Check the
amount yourself, against the transaction as seq last returned it,
before you send it.
Partial authorizations
A bank may authorize less than a payment asked for, as when the card's
balance is short. v1 accepts such an authorization, unless the link or
charge has "autocapture": the transaction is then for what the bank
authorized, "authorized" is less than "attempted", and you can
capture no more than "authorized". Compare the two before you deliver
an order.
How long an authorization can be captured is not the API's to decide, but the card scheme's and the acquirer's: see the knowledge base's Authorizations.
Charges
A charge of a subscriber is a transaction too, whether you made it with Charge subscriber, or the payer paid it as the first charge of a subscriber link. It differs from a payment link's transaction in this:
- Its
"type"is"charge". - Its
"info"has only"ip", without"ndisplay"and"nfail". - It has a
"subscriber": the subscriber it charged, as below. - Its
"orderid","items", and addresses come from the request that made it: the charge, or the subscriber link. An address that a charge left out is the subscriber's.
| Field | Type | Description |
|---|---|---|
subscriber.id |
integer | The subscriber's ID. |
subscriber.rev |
integer | The subscriber's revision when it was charged. |
subscriber.ref |
string | The subscriber's "ref", as you gave it. |
Seq returns a charge after its subscriber, as What seq guarantees explains, and you capture and refund it as any transaction.