# Manage Virtual Cards

Virtual cards are a core component of the **Virtual Cards SDK**. Virtual cards are used as a proxy between a user's personal [Funding Source](/guides/virtual-cards/manage-funding-sources.md) and online merchants.

{% hint style="info" %}
In order to provision and use a virtual card, a [Sudo](/guides/sudos.md) and [Funding Source](/guides/virtual-cards/manage-funding-sources.md) are required.
{% endhint %}

Virtual cards make an important distinction between two subtypes: `ProvisionalVirtualCard` and `VirtualCard`.

A `ProvisionalVirtualCard` is a card that is currently being provisioned. This will typically exist in 3 different states: `PROVISIONING`, `COMPLETED`, and `FAILED`. It also contains a property `card` which contains the provisioned card, when it has `COMPLETED`. This type is transitory and should only be used for determining whether a card is provisioning, or has failed provisioning. Accessing provisioned cards should be done using methods that return `Card` types.

A `VirtualCard` is a card that has successfully finished provisioning. This is the card type that represents virtual cards.

## Closed Cards

Cards will be closed and set to a `CLOSED` state under the following circumstances:

* The card has expired; this occurs when your card reaches its expiry date.
* The card has been closed via the cancel card API.
* The Sudo owning the card has been deleted.

While provisional card records are transient, a card record is not. Cards will always be available for access via access methods even when closed. It is important to maintain a record of closed cards so that users can still examine the transaction history and also see any refunds that may still occur against the closed card from merchants where the card was used before the card was closed.

#### Orphan Cards

When a Sudo is deleted, any virtual cards owned by that Sudo will be closed and the entry for the Sudo in the `owners` property of any virtual cards owned by that Sudo will be removed. Such cards are called orphan cards and can be identified by looking for cards with no Sudo owner in the `owners` property. Sudo owners are identified as owners with `issuer` of value `sudoplatform.sudoservice`.

## Provisioning a Virtual Card

A virtual card is provisioned by calling the `provisionVirtualCard` method.

An `ownershipProofToken` is required as part of the input. This ties together the Sudo and virtual card such that the Sudo becomes the owner of the virtual card. Use the `getOwnershipProof` method on the `SudoProfilesClient` in the **Sudo** **Profiles SDK** in order to obtain an `ownershipProofToken`. See the [Sudo](https://github.com/sudoplatform/developer-documentation/blob/master/guides/virtual-cards/broken-reference/README.md) section for more information.

The `fundingSourceId` is another required input parameter and is used to associate the virtual card with a funding source used to fund that particular virtual card.

Input properties `cardHolder`, `billingAddress`, and `metadata` are indicative of the user's information that they wish to be associated with the card.

`cardHolder` is the name that will appear on the card and is needed to perform transactions.

`billingAddress` is an optional property that the user can provide. If this is left `undefined/nil/null`, the `billingAddress` will be set by the platform itself.

`metadata` is a JSON object that contain application specific information about each virtual card. Examples of such information include an alias for the virtual card to help the user remember why they created it or perhaps a color for the virtual card to be rendered in by the application.

The `currency` is the three character ISO 4217 alphabetic currency code of the currency in which the virtual card is to be denominated.

{% hint style="warning" %}
Currently only USD is supported.
{% endhint %}

{% tabs %}
{% tab title="TypeScript" %}
A call to the `provisionVirtualCard` method initiates the process of provisioning a virtual card and will return a `ProvisionalCard` on completion. During this process the `ProvisionalCard` traverses between a `PROVISIONING` and `COMPLETED` state.

To check the provisioning state after calling this API, calls to `getProvisionalCard` is used to poll for state changes of a card during its provisioning cycle. Once the `ProvisionalCard` has reached a `COMPLETED` state, the fully provisioned card can be accessed via the `card` property on the card (or via get/list of virtual cards).

```typescript
try {
  const provisionalCard = await virtualCardsClient.provisionVirtualCard({
    // Refer to Sudo Profiles SDK for the information on how to obtain
    // a Sudo ownership proof. The audience parameter for the ownership
    // proof must be "sudoplatform.virtual-cards.virtual-card" to be
    // able to provision a virtual card for a Sudo.
    ownershipProofs: [ownershipProof],
    fundingSourceId: fundingSource.id,
    currency: 'USD',
    cardHolder: 'John Smith',
    billingAddress: {
      addressLine1: '123 Street Rd',
      addressLine2: undefined,
      city: 'Salt Lake City',
      state: 'UT',
      postalCode: '84044',
      country: 'US',
    },
    metadata: {
      alias: 'Shopping',
    }
  })
} catch (error) {
  // Handle/notify user of error.
}
```

{% endtab %}

{% tab title="Swift" %}
When provisioning a card for the first time, a key pair is generated on the [Apple Keychain](https://developer.apple.com/documentation/security/keychain_services) using the RSA algorithm. It is important to encourage a user to backup this key to ensure that they can access their data in the event that the key is lost. This key is linked to the `keyNamespace` property used when initializing a `DefaultSudoVirtualCardsClient` (for more info, see [here](/guides/virtual-cards/integrate-the-virtual-cards-sdk.md#client-initialization)).

To provision a virtual card:

```swift
class ProvisionVirtualCardViewController: UIViewController, ProvisionCardObserable {

    // ... Other view controller details

    // Ensure you retain a reference to a `DefaultSudoVirtualCardsClient` instance.
    let virtualCardsClient: SudoVirtualCardsClient

    func provisionVirtualCard() async {
        do {
            // Retrieve the input details.
            let provisionInput = ProvisionCardInput(
                sudoId: sudo.id,
                fundingSourceId: fundingSource.id,
                cardHolder: input.cardHolder,
                billingAddress: input.billingAddress,
                currency: input.currency,
                /// Proof of Sudo ownership for provisioning cards. The ownership proof must
                /// contain an audience of "sudoplatform.virtual-cards.virtual-card" and can be
                /// obtained from SudoProfiles SDK via getOwnershipProof API.
                ownershipProof: ownershipProof,
                metadata: .dictionary([
                    "alias": .string("Shopping")
                ])
            )
            let virtualCard = try await virtualCardsClient.provisionCard(
                withInput: provisionInput
            )
        } catch {
            // Handle error
        }
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}
A call to the `provisionVirtualCard` method initiates the process of provisioning a virtual card and will return a `ProvisionalVirtualCard` on completion. During this process the `ProvisionalVirtualCard` traverses between a `PROVISIONING` and `COMPLETED` state.

To check the provisioning state after calling this API, calls to `getProvisionalVirtualCard` is used to poll for state changes of a card during its provisioning cycle. Once the `ProvisionalVirtualCard` has reached a `COMPLETED` state, the fully provisioned card can be accessed.

```kotlin
// val ownershipProof: String
// val fundingSource: FundingSource
val billingAddress = BillingAddress(
    addressLine1 = "123 Street Rd",
    addressLine2 = null,
    city = "Salt Lake City",
    state = "UT",
    postalCode = "84044",
    country = "US"
)
val provisionInput = ProvisionVirtualCardInput(
    // Refer to Sudo Profiles SDK for the information on how to obtain
    // a Sudo ownership proof. The audience parameter for the ownership
    // proof must be "sudoplatform.virtual-cards.virtual-card" to be
    // able to provision a virtual card for a Sudo.
    ownershipProofs: [ownershipProof],
    fundingSourceId = fundingSource.id,
    cardHolder = "John Smith",
    alias = "Shopping",
    billingAddress = billingAddress,
    currency = "USD"
)
launch {
    try {
        val initialProvisionalCard = withContext(Dispatchers.IO) { 
            virtualCardsClient.provisionVirtualCard(
                provisionInput
            )
        }
        var state = initialProvisionalCard.state
        var virtualCard: VirtualCard? = null
        while (state == ProvisionalCard.ProvisioningState.PROVISIONING) {
            val provisionalCard = withContext(Dispatchers.IO) { 
                virtualCardsClient.getProvisionalCard(initialProvisionalCard.id)
            }
            if (provisionalCard?.state == ProvisionalCard.ProvisioningState.COMPLETED) {
                virtualCard = provisionalCard.card
                break
            }
            state = provisionalCard?.state ?: ProvisionalCard.ProvisioningState.PROVISIONING
        }
        // The returned [virtualCard] has been created.
    } catch (e: VirtualCardException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

## Updating a Virtual Card

The user may want to update details on their virtual card. The `updateVirtualCard` method is used to do this.

The modifiable fields of a virtual card are contained in the `UpdateVirtualCardInput`:

* `cardHolder`
* `metadata`
* `billingAddress`
* `alias`

These fields can all be updated independently.

{% hint style="warning" %}
The `alias` property is deprecated and replaced by the more general `metadata` property. To record an `alias` property for a card, set an `alias` property in the `metadata` JSON object.
{% endhint %}

A `SingleAPIResult` type is returned from this method call which contains the status of the update operation. Two possible statuses can be returned:

| Status  | Definition                                                                                                                                                                                       |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Success | The virtual card succeeded to update and the returned virtual card decrypted successfully.                                                                                                       |
| Partial | The virtual card updated successfully however the returned virtual card failed to decrypt. The return object will include the virtual card without the decrypted fields and an associated error. |

{% tabs %}
{% tab title="TypeScript" %}
If you only want to update the `alias` to '`Banking'` and the `cardHolder` to '`J Smith'` then the API should be called like so:

```typescript
try {
  const result = await virtualCardsClient.updateVirtualCard({
    id: card.id,
    expectedCardVersion: card.version,
    cardHolder: 'J Smith',
    metadata: {
      alias: 'Banking'
    }
  })
  if (result.status === APIResult.Success) {
    const card = result.result
  } else if (result.status === APIResult.Partial) {
    const cardMetadata = result.item
    const error = result.cause
  } 
} catch (error) {
  // Notify/handle errors
}
```

The virtual card `metadata` property is a single attribute and all metadata properties must be specified on each update.
{% endtab %}

{% tab title="Swift" %}
When updating a virtual card, you can supply either `null`, `defined`, or `undefined` for either updatable property. Supplying `undefined` will perform no update, supplying `null` will remove the value on the service (setting it to `null`), and supplying `defined` will update the content of the property. For example, if you have a card (`userCard`) that has the following properties:

```javascript
{
  "id": "card-id-1",
  "cardHolder": "John Smith",
  "alias": "Shopping",
  "billingAddress": { addressLine1: "42 Abbey Road" ... }
}
```

To update the `alias` to `"Banking"`, and `billingAddress` to `null`, call the update API:

```swift
do {
    let input = UpdateCardInput(
      id: userCard.id, 
      cardHolder: userCard.cardHolder,
      billingAddress: .null,
      metadata: .defined(
        .dictionary([
          "alias": .string("Shopping")
        ])
      )
    )
    
    let result = try await client.updateVirtualCard(withInput: input)
    switch result {
       case .success(let output):
         let updatedCard = output.result
         // `updatedCard` contains the updated card, with the previous details intact
         // and `alias` updated to "Banking".
       case .partial(let partial):
         let partialUpdatedCard = partial.result
         let error = partial.cause         
    }
} catch {
    // Handle error.
```

{% endtab %}

{% tab title="Kotlin" %}
When updating a virtual card, ensure that all properties that are remaining unchanged are populated. For example, if you have a virtual card (`userCard`) that has the following properties:

```javascript
{
  "id": "card-id-1",
  "cardHolder": "John Smith",
  "alias": "Shopping",
  "billingAddress": nil
}
```

If you only want to update the `alias` to `"Banking"`, then the API should be called like so:

```kotlin
val input = UpdateVirtualCardInput(
  id = userCard.id, // card-id-1
  cardHolder = userCard.cardHolder, // John Smith
  metadata = JsonValue.JsonString("Banking"),
  billingAddress = userCard.billingAddress // null
)
launch {
    try {
        val updateCard = withContext(Dispatchers.IO) {
            virtualCardsClient.updateVirtualCard(input)
        }
        when (updateCard) {
            is SingleAPIResult.Success -> {
                val card = updateCard.result
                // [card] contains the updated card, with the previous details intact
                // and [metadata] updated to "Banking".
            }
            is SingleAPIResult.Partial -> {
                val partialCard = updateCard.result.partial
                val error = updateCard.result.cause
            }
        }
    } catch (e: VirtualCardException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

## Canceling a Virtual Card

A virtual card can be cancelled using the `cancelVirtualCard` method by passing in the `id` of an existing virtual card object.

This API call is idempotent, so any subsequent call to cancel with the same `id` will always yield the same result.

When cancelling a virtual card, the card will not be deleted, but instead will be transitioned to the `CLOSED` state and remain accessible via retrieval methods. Closed cards remain available so that transaction history can be retained and so that refunds that happen after the card is closed can be shown.

{% hint style="warning" %}
Once a virtual card is cancelled and its state is set to `CLOSED`, it cannot be reactivated - a new virtual card will need to be provisioned.
{% endhint %}

A `SingleAPIResult` type is returned from this method call which contains the status of the cancellation operation. Two possible statuses can be returned:

| Status  | Definition                                                                                                                                                                                         |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Success | The virtual card succeeded to cancel and the returned virtual card decrypted successfully.                                                                                                         |
| Partial | The virtual card cancelled successfully however the returned virtual card failed to decrypt. The return object will include the virtual card without the decrypted fields and an associated error. |

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

```typescript
// Collect the input virtual card id however makes sense for your implementation.
try {
  const result = await virtualCardsClient.cancelVirtualCard({ id: card.id })
  if (result.status === APIResult.Success) {
    const card = result.result
  } else if (result.status === APIResult.Partial) {
    const cardMetadata = result.item
    const error = result.cause
  }
} catch (error) {
  // Handle/notify error
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
// Collect the input virtual card id however makes sense for your implementation.
do {
  let result = try await self.virtualCardsClient.cancelVirtualCard(withId: id)
  switch result {
  case .success(let output):
     let card = output.result
  case .partial(let partial):
     let partialCard = partial.result
     let error = partial.cause
} 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 cancelCard = withContext(Dispatchers.IO) {
            virtualCardsClient.cancelVirtualCard(virtualCardId)
        }
        when (cancelCard) {
            is SingleAPIResult.Success -> {
                val card = cancelCard.result
            }
            is SingleAPIResult.Partial -> {
                val partialCard = cancelCard.result.partial
                val error = cancelCard.result.cause
            }
        }
    } catch (e: VirtualCardException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

## Retrieving Virtual Cards

Previously provisioned (or closed and suspended) virtual cards can be accessed in two ways: via its identifier ([Single Virtual Card by Id](#single-virtual-card-by-id)), or via a list method ([Multiple Provisioned Virtual Cards](#accessing-multiple-virtual-cards)).

A fetch of single or multiple virtual cards 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 Virtual Card by Id

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

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

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

{% endtab %}

{% tab title="Swift" %}

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

{% endtab %}

{% tab title="Kotlin" %}

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

{% endtab %}
{% endtabs %}

### Multiple Provisioned Virtual Cards

The ability to retrieve multiple or all virtual cards available to the user is supported. Returned virtual cards are sorted based on time of last update, with the default ordering being most recent first. The results can also be filtered and/or [paginated](/guides/email/pagination.md). To return least recently updated first, provide the sortOrder parameter with a value of ascending.

A call to a list API will return a `ListVirtualCardsResult` 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. Note that an empty items list does not necessarily mean that all items have been retrieved. Always check the value of the returned `nextToken`, there are no more results to retrieve when `nextToken` is null. There can be three possible statuses returned:

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

{% hint style="warning" %}
A virtual card 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 Provisioned Virtual Cards

To retrieve multiple virtual cards that are owned by the signed in user, call the `listVirtualCards` method. The results can be filtered, sorted and/or [paginated](/guides/email/pagination.md).

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

```kotlin
try {
    const filter: VirtualCardFilterInput = {
        and: [
            { id: { eq: idToGet } },
            { state: { eq: 'ISSUED' } },
        ],
    }
  const result = await virtualCardsClient.listVirtualCards({
      filter,
      sortOrder: SortOrder.Asc,
      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.cause` contains the error which caused the failure.
  }
} catch (error) {
  // Handle/notify error
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  let filter = VirtualCardFilterInput.and([
      VirtualCardFilterInput.id(.equals(idToGet)),
      VirtualCardFilterInput.state(.equals("ISSUED"))
  ])
  let result = try await self.virtualCardsClient.listVirtualCards(
    withFilter: filter,
    sortOrder: .ascending,
    withLimit: 20,
    nextToken: nextToken
  )
  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 {
    val filter = VirtualCardFilterInput(
        id = null,
        state = null,
        and = arrayListOf(
            VirtualCardFilterInput(IdFilterInput(null, idToGet)),
            VirtualCardFilterInput(
                null,
                VirtualCardFilterInput(
                    eq = "ISSUED",
                ),
            ),
        ),
        or = null,
        not = null
    )

    try {
        val result = withContext(Dispatchers.IO) { 
            virtualCardsClient.listVirtualCards(
                filter,
                SortOrder.ASC,
                limit = 20, 
                nextToken = nextToken
            )
        }
        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: VirtualCardException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

### Provisional Virtual Cards

To retrieve multiple provisional cards (cards for which provisioning may not have been completed or successful) that are owned by the signed in user, call the `listProvisionalCards` method. The results can be [paginated](/guides/email/pagination.md).

Note that this list method should only be used for determining whether a card is provisioning, or has failed provisioning. The `listVirtualCards` interface is preferred for most operations due to its superior flexibility and functionality.

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

```kotlin
try {
  const result = await virtualCardsClient.listProvisionalCards({
      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 retrieval with associated error.
     //  Page through the results if `partial.nextToken` != undefined.
  } else {
     // `result.cause` contains the error which caused the failure.
  }
} catch (error) {
  // Handle/notify error
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  let result = try await self.virtualCardsClient.listProvisionalCards(
    withLimit: 20,
    nextToken: nextToken
  )
  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 retrieval 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.listProvisionalCards(
                limit = 20, 
                nextToken = nextToken
            )
        }
        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 retrieval with associated error.
                //  Page through the results if [partial.nextToken] != null.
            }
        }
    } catch (e: VirtualCardException) {
        // Handle/notify user of exception
    }
}
```

{% 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-virtual-cards.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.
