# Transactions

Source: <https://docs.gjold.com/v1/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](https://docs.gjold.com/v1/transactions/capture) some or all of what is left of the authorization. |
| `POST /v1/transactions/{trnid}/refund` | [Refund](https://docs.gjold.com/v1/transactions/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](https://docs.gjold.com/v1/transactions#acts); API v2 adds a [void request](https://docs.gjold.com/v2/transactions/void).

A transaction comes into being in one of three ways, and seq returns it in each: a payer pays a [payment link](https://docs.gjold.com/v1/payment-links), which makes a `"transaction"`; a payer pays the first charge of a [subscriber link](https://docs.gjold.com/v1/subscribers/create#first-charge) as they save their card, which makes a `"charge"`; or you [charge a subscriber](https://docs.gjold.com/v1/subscribers/charge), 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](https://docs.gjold.com/v1/requests#compatibility) says.

| Field | Type | Description |
|---|---|---|
| `type` | string | `"transaction"`, or `"charge"` for a [charge](https://docs.gjold.com/v1/transactions#charges) 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](https://docs.gjold.com/learn/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](https://docs.gjold.com/v1/payment-links/create#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](https://docs.gjold.com/v1/transactions#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](https://docs.gjold.com/v1/transactions#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:

```json
{
    "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](https://docs.gjold.com/notes/retries#the-index).

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](https://docs.gjold.com/v1/transactions/capture#acquirer-errors). 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](https://docs.gjold.com/learn/authorizations).

## Charges

A charge of a subscriber is a transaction too, whether you made it with [Charge subscriber](https://docs.gjold.com/v1/subscribers/charge), or the payer paid it as the [first charge](https://docs.gjold.com/v1/subscribers/create#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](https://docs.gjold.com/notes/synchronization#what-seq-guarantees) explains, and you capture and refund it as any transaction.
