# Manage Transactions

Transaction records represent a transaction performed against a provisioned virtual card (for more information on virtual cards, see [here](/guides/virtual-cards/manage-virtual-cards.md)). Transactions cannot be created or deleted by the consumer directly. Transactions are generated by using the virtual card to make purchases at merchants.

Each transaction record has a `sequenceId` property which can be used to cross reference related transactions. For example, a pending transaction may have already been partially transacted and have related debit transactions (for more information on transaction states, see [here](#transaction-states)).

Transactions may also contain a `detail` property which includes some supplementary information. For more information, see [here](#transaction-details).

## Transaction States

Transactions can exist in 4 different states:

* pending
* complete
* refund
* decline

When a transaction is `pending`, it has entered the first state of authorization and is currently awaiting to be debited, or further action. A transaction that is pending will typically be completely debited, thus deleting the existing pending transaction, and being replaced with a `debit` transaction. A transaction can be partially debited however, which means that a new `debit` transaction record will be created for each partial debit.

A `pending` transaction record will continue to persist until its value has been decreased to 0, either via debits or reversal charges. Reversals will not be recorded via the transaction records, except through the decreasing value of the `pending` transaction. This is not to be confused with a `refund` transaction, which will generate a new transaction record.

**Note** - Reversal transactions relate to a transaction that has had its value decreased before it has been fully transacted (completed). A refunded transaction occurs after a fully completed transaction.

When a transaction is represented as being `complete`, it has entered a state that represents a successful debit. A completed transaction can be associated with a previously pending transaction via the `sequenceId` if the completed transaction was a partial debit.

When a transaction is represented as a `refund`, it means that a previously transacted record (state is `complete`) has been refunded. This can be either for a partial amount or the full amount. If a transaction is refunded, it will not remove the `complete` transaction record.

When a transaction is represented as a `decline`, it means that an error has occurred while attempting to process a transaction. For more information on decline reasons, see [here](#decline-reasons).

## Transaction Details

Transactions can contain supplementary details in the form of the `detail` property. Supplementary details generally refer to the interactions with the user's actual funding source. Pending, complete, and refund transactions will always contain at least 1 detail. The information contained in details is recorded below:

| Detail                | Summary                                                                                                                                                       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `virtualCardAmount`   | Amount merchant charged virtual card                                                                                                                          |
| `markup`              | Markup formula applied to `billedAmount` to calculate                                                                                                         |
| `markupAmount`        | Markup amount added to transaction's `transactedAmount`                                                                                                       |
| `fundingSourceAmount` | Amount charged to the funding source                                                                                                                          |
| `transactedAt`        | Timestamp indicating when the transaction was sent to the funding source. This is optional because not all funding sources require interaction at each stage. |
| `settledAt`           | Timestamp indicating when the transaction was settled with the funding source. This is optional until the transaction is settled.                             |
| `fundingSourceId`     | ID of the funding source that funded this item                                                                                                                |
| `description`         | Description that will show on the real funding source statement                                                                                               |
| `state`               | State returned by the funding source as a result of this transaction. Valid states are: Cleared, Pending, Insufficient Funds, and Failed.                     |

Each interaction (authorization, charge, refund) with the funding source will result in a separate detail entry and should be able to be correlated with an entry on the user's account statement.

For `pending` and `complete` transactions, if there is no detail entry with state `Cleared`, then the transaction will continue to be counted against transaction velocity limits irrespective of the velocity limits' periods.

#### Decline Reasons

A transaction can be declined for a number of reasons. The table below shows a list of reasons and what they mean:

| Reason                     | Cause                                                                                                                                    |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `INSUFFICIENT_FUNDS`       | Funding source related to card has insufficent funds to perform the transaction                                                          |
| `FUNDING_ERROR`            | Funding source related to card declined to perform the transaction for a reason other than insufficient funds.                           |
| `CARD_STOPPED`             | Card associated with the transaction is inactive. This will occur implicitly if the funding source is also inactive                      |
| `CARD_EXPIRED`             | Card associated with the transaction has expired.                                                                                        |
| `MERCHANT_BLOCKED`         | Merchant that has been attempted to transact with is restricted                                                                          |
| `MERCHANT_CODE_BLOCKED`    | The merchant category code (MCC) of the merchant that has been attempted to transact with is restricted                                  |
| `MERCHANT_COUNTRY_BLOCKED` | The country of the merchant that has been attempted to transact with is restricted                                                       |
| `AVS_CHECK_FAILED`         | Address verification has failed                                                                                                          |
| `CSC_CHECK_FAILED`         | CSC verification has failed - occurs when the CSC provided to the merchant does not match the CSC of the virtual card                    |
| `EXPIRY_CHECK_FAILED`      | Expiry verification has failed - occurs when the expiry date provided to the merchant does not match the expiry date of the virtual card |
| `PROCESSING_ERROR`         | An error has occurred while attempting to process the transaction. Please contact support                                                |
| `DECLINED`                 | Transaction is declined                                                                                                                  |
| `VELOCITY_EXCEEDED`        | The transaction has exceeded its velocity policies                                                                                       |
| `CURRENCY_BLOCKED`         | The transaction that has been attempted is in an unsupported currency                                                                    |

## Retrieving Transactions

Transactions can be accessed in two ways: via its identifier ([Single Transaction by Id](#single-transaction-by-id)), or via a list method ([Multiple Transactions](#multiple-access)).

A fetch of single or multiple transactions can be performed remotely or locally by specifying the appropriate [CachePolicy](https://github.com/sudoplatform/developer-documentation/blob/master/guides/virtual-cards/broken-reference/README.md) as part of the input.

### Single Transaction by Id

To retrieve a single virtual card given its unique `id`, use the `getTransaction` method. This method will return the record if it exists.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
try {
  const transaction = await virtualCardsClient.getTransaction({
    id,
    cachePolicy: CachePolicy.RemoteOnly
  })
  // `transaction` contains the transaction object, else `undefined` if not found.
} catch (error) {
  // Handle/notify error
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  let transaction = try await self.virtualCardsClient.getTransaction(withId: id)
  // If the id matches a transaction, `transaction` will be returned, else nil.
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        val transaction = withContext(Dispatchers.IO) {
            virtualCardsClient.getTransaction(
                id
            )
        }
        // If the [id] matches a transaction, [transaction] will be returned, else [null]
    } catch (e: TransactionException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

### Multiple Transactions

The ability to retrieve multiple transactions available to the user is supported. These results can be [paginated](https://github.com/sudoplatform/developer-documentation/blob/master/guides/virtual-cards/broken-reference/README.md) and can contain transactions in various states.

A call to a list API will return a `ListTransactionsResult` with a `status` and depending on the status, a list of matching `items` and a `nextToken` to support pagination. If no results matching the input are found, the result will contain empty items. There can be three possible statuses returned:

| Status  | Definition                                                                                                                                                                                                                |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Success | A list of all requested transactions are returned.                                                                                                                                                                        |
| Partial | A list of all transactions that were successfully fetched and decrypted are returned as well as a list of all transactions that failed to decrypt successfully, including an error indicating the reason for the failure. |
| Failure | All transactions failed to be fetched or decrypted. Contains an error indicating the reason for the failure.                                                                                                              |

{% hint style="warning" %}
A transaction may fail to be unencrypted if the version of the client is not up-to-date or if the required cryptographic key is missing from the client device.
{% endhint %}

#### All Transactions for a Virtual Card

To retrieve multiple transactions that are associated with a certain virtual card, call the `listTransactionsByCardId` method by passing in the `id` of the virtual card to query. An optional `sortOrder` and `dateRange` can also be supplied to organise results.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
// Collect the input virtual card id however makes sense for your implementation.
const virtualCardId = virtualCard.id
try {
    const result = await virtualCardsClient.listTransactionsByCardId({
        virtualCardId,
        cachePolicy: CachePolicy.RemoteOnly,
        limit: 20,
        nextToken
    })
    if (result.status === ListOperationResultStatus.Success) {
        // `result.items` contains the list of items matching the input.
        // Page through the results if result.nextToken != undefined. 
    } else if (result.status === ListOperationResultStatus.Partial) {
        // `result.items` contains the list of items matching the input that decrypted successfully.
        // `result.failed` contains the list of items that failed decryption with associated error.
        //  Page through the results if `partial.nextToken` != undefined.
    } else {
        // `result.clause` contains the error which caused the failure.
    }
} catch {
    // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
// Collect the input virtual card id however makes sense for your implementation.
do {
  let result = try await self.virtualCardsClient.listTransactions(
    withCardId: "id-of-card",
    nextToken: nil, 
    dateRange: nil,
    sortOrder: nil
  )
  switch result {
  case .success(let output):
     // `output.items` contains the list of items matching the input.
     // Page through the results if `output.nextToken` != nil.
  case .partial(let partial):
     // `partial.items` contains the list of items matching the input that decrypted successfully.
     // `partial.failed` contains the list of items that failed decryption with associated error.
     //  Page through the results if `partial.nextToken` != nil.
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
// Collect the input virtual card id however makes sense for your implementation.
// val virtualCardId = virtualCard.id
launch {
    try {
        val result = withContext(Dispatchers.IO) { 
            virtualCardsClient.listTransactions(
                cardId = virtualCardId
                limit = 20, 
                nextToken = null,
                dateRange = null,
                sortOrder = SortOrder.DESC
            )
        }
        when (result) {
            is ListAPIResult.Success -> {
                // [result.items] contains the list of items matching the input.
                // Page through the results if [output.nextToken] != null.
            }
            is ListAPIResult.Partial -> {
                // [result.items] contains the list of items matching the input that decrypted successfully.
                // [result.failed] contains the list of items that failed decryption with associated error.
                //  Page through the results if [partial.nextToken] != null.
            }
        }
    } catch (e: TransactionException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

#### All Transactions for a User

To retrieve multiple transactions that are associated across all of a user's virtual cards, call the `listTransactions` method. An optional `sortOrder` and `dateRange` can also be supplied to organise results.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
try {
    const result = await virtualCardsClient.listTransactions({
        cachePolicy: CachePolicy.RemoteOnly,
        limit: 20,
        nextToken,
        dateRange,
        sortOrder: SortOrder.ascending
    })
    if (result.status === ListOperationResultStatus.Success) {
        // `result.items` contains the list of items matching the input.
        // Page through the results if result.nextToken != undefined. 
    } else if (result.status === ListOperationResultStatus.Partial) {
        // `result.items` contains the list of items matching the input that decrypted successfully.
        // `result.failed` contains the list of items that failed decryption with associated error.
        //  Page through the results if `partial.nextToken` != undefined.
    } else {
        // `result.clause` contains the error which caused the failure.
    }
} catch {
    // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  let result = try await self.virtualCardsClient.listTransactions(
    nextToken: nil, 
    dateRange: dateRange,
    sortOrder: SortOrder.ascending
  )
  switch result {
  case .success(let output):
     // `output.items` contains the list of items matching the input.
     // Page through the results if `output.nextToken` != nil.
  case .partial(let partial):
     // `partial.items` contains the list of items matching the input that decrypted successfully.
     // `partial.failed` contains the list of items that failed decryption with associated error.
     //  Page through the results if `partial.nextToken` != nil.
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        val result = withContext(Dispatchers.IO) { 
            virtualCardsClient.listTransactions(
                limit = 20, 
                nextToken = null,
                dateRange = null,
                sortOrder = SortOrder.DESC
            )
        }
        when (result) {
            is ListAPIResult.Success -> {
                // [result.items] contains the list of items matching the input.
                // Page through the results if [output.nextToken] != null.
            }
            is ListAPIResult.Partial -> {
                // [result.items] contains the list of items matching the input that decrypted successfully.
                // [result.failed] contains the list of items that failed decryption with associated error.
                //  Page through the results if [partial.nextToken] != null.
            }
        }
    } catch (e: TransactionException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

### Subscribing to Transactions

{% hint style="warning" %}
Typescript Libraries currently do not support subscribing to transactions.
{% endhint %}

You can subscribe to receive updates as transactions are created, updated and deleted. The `subscribeToTransactions`, `unsubscribeFromTransactions` and `unsubscribeAll` APIs allow you to start and stop receiving updates.

You may receive multiple updates for a single transaction over time. Each time the transaction changes you will receive a new update. You should receive an update when the transaction is first created, the `type` field of the transaction will be `PENDING`. Some time later you will receive another update with the `type` set to `COMPLETE`, `REFUND` or `DECLINE`.

{% tabs %}
{% tab title="Swift" %}
To subscribe to transaction changes, use the `subscribe` method. This method accepts a unique subscription identifier and a subscriber object which must implement the `notify(notification: SubscriptionNotification)` method.  This handler should contain application-specific implementation of behavior in the event of a transaction change. The subscription object should also implement the `connectionStatusChanged(state: SubscriptionConnectionState)` method which is invoked in the event of subscription connection state changes between `connected` and `disconnected`. This handler allows the consumer to detect when the subscription connection has been lost and take appropriate corrective action.

**Setting Up A Subscription**

To setup the subscription for update events, create a `Subscriber` conforming instance and provide it to the subscription method:

```swift
class TransactionUpdatedSubscriber: Subscriber {

    func notify(notification: SubscriptionNotification) {
        if case .transactionUpdated(let transaction) = notification {
            // Handle the updated transaction.
        }
    }

    func connectionStatusChanged(state: SubscriptionConnectionState) {
        switch state {
        case .connected:
            // The subscription has become active.
        case .disconnected:
            // The subscription has become inactive and the subscriber
            // has been automatically unsubscribed.
        }
    }
}    

let subscriber = TransactionUpdatedSubscriber()
let id = UUID().uuidString
try await emailClient.subscribe(id: id, notificationType: .transactionUpdated, subscriber: subscriber)
```

To setup the subscription for deleted events, call the subscription method as so:

```swift
class TransactionDeletedSubscriber: Subscriber {

    func notify(notification: SubscriptionNotification) {
        if case .transactionDeleted(let transaction) = notification {
            // Handle the deleted transaction.
        }
    }

    func connectionStatusChanged(state: SubscriptionConnectionState) {
        switch state {
        case .connected:
            // The subscription has become active.
        case .disconnected:
            // The subscription has become inactive and the subscriber
            // has been automatically unsubscribed.
        }
    }
}
    
let subscriber = TransactionDeletedSubscriber()
let id = UUID().uuidString
try await virtualCardsClient.subscribe(id: id, notificationType: .transactionDeleted, subscriber: subscriber)
```

**Cancelling A Subscription**

To cancel a subscription, pass the same `id` value  used to subscribe to the `unsubscribe` method:

```swift
await virtualCardsClient.unsubscribe(id: id)
```

This will ensure that the subscription is cancelled and system resources are freed.  Alternatively you can cancel all subscriptions at once:

```swift
await virtualCardsClient.unsubscribeAll()
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
val subscriptionId = UUID.randomUUID().toString()
launch {
    try {
        withContext(Dispatchers.IO) {
            virtualCardsClient.subscribeToTransactions(subscriptionId) { txn ->
                // Handle updated transaction
            }
        }
    } catch (e: TransactionException) {
        // Handle exception
    }
    // Some time later ...
    withContext(Dispatchers.IO) {
        virtualCardsClient.unsubscribeFromTransactions(subscriptionId)
    }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sudoplatform.com/guides/virtual-cards/manage-transactions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
