# Manage Funding Sources

Funding sources supply the means for a user to link a real funding source with a virtual card. A user can supply their credit or debit card as a method to fund a transaction performed on the virtual card.

## Creating a Funding Source

A funding source is created by invoking a series of steps from the retrieval of funding source configuration to setup and completion of the creation process.

{% hint style="info" %}
Before creating a funding source, the identity of your user must first be verified via [Secure Id Verification](/guides/identity-verification.md).
{% endhint %}

To begin the process of creating a funding source, call the `setupFundingSource` method. The funding source providers that your client application supports will inform which provider names you provide to the `setupFundingSource` method call. The funding source provider supported by the Virtual Cards service is `stripe`. You must also provide the name of your application to the `setupFundingSource` method. This name forms part of a shared configuration between your application, the Virtual Cards service, and any third party funding source providers you have a relationship with. [Contact us](https://sudoplatform.com/#lets-chat) if you are not certain what application name to use.

### Card based Funding Source

{% hint style="warning" %}
You can create a credit card funding source using `stripe` as the funding source provider.&#x20;
{% endhint %}

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

```typescript
try {
  const provisionalFundingSource = await virtualCardsClient.setupFundingSource({
    currency: 'USD',
    type: FundingSourceType.CreditCard,
    applicationName: 'yourApplicationName',
    // include only the provider(s) you will support
    supportedProviders: ['stripe'],
  })
} catch (error) {
  // Handle/notify user of errors
}
```

The call to `setupFundingSource` will return a `ProvisionalFundingSource` containing provisioning data required to continue the funding source creation process. To continue, you must use the [Stripe API](#stripe-funding-source-preparation) to progress the funding source setup.

Retrieve the provider API key as well as other information pertaining to the funding source provider using the `getFundingSourceClientConfiguration` method:

```typescript
try {
  const configuration = 
      await virtualCardsClient.getFundingSourceClientConfiguration()
  // [configuration] contains the list of funding source client configuration.
} catch (error) {
  // Handle/notify user of errors
}
```

**Stripe Funding Source Preparation**

{% hint style="info" %}
For Stripe React integration, see the documentation at:

<https://stripe.com/docs/stripe-js/react>

For Stripe JavaScript integration, see the documentation at:

<https://stripe.com/docs/js>
{% endhint %}

To setup a payment intent in Stripe, call `StripeClient.confirmSetupIntent(confirmSetupIntentParams: ConfirmSetupIntentParams)` and ensure that the `ConfirmSetupIntentParams` input parameter is populated with card details and billing details that you wish to add as a funding source.

{% hint style="warning" %}
Card and Billing Details are required.
{% endhint %}

Once a Setup Intent has been setup, call `completeFundingSource` to finish provisioning the `FundingSource`.

**Completing Funding Source Setup with Provider Specific Data**

Once the provider-specific data has been obtained, construct a provider specific completion data object and call the `completeFundingSource` method to finish the process of provisioning a funding source:

The `completeFundingSource` call accepts an optional `updateCardFundingSource` parameter (default value is true). This parameter is a flag to indicate whether to automatically update unfunded cards' funding source when a new funding source is created:

```typescript
// For Stripe
const completionData: CompleteFundingSourceStripeCardCompletionDataInput = {
  provider: 'stripe',
  type: FundingSourceType.CreditCard,
  paymentMethod: setupIntentPaymentMethodAsString, // Retrieve from Stripe API
}

try {
  const fundingSource = await virtualCardsClient.completeFundingSource({
    id: provisionalFundingSource.id,
    completionData,
    // Optional flag to indicate whether to automatically update unfunded cards' funding source when a new funding source is created (defaults to true)
    updateCardFundingSource: true,
    
} catch (error) {
    // Handle error
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
try {
  let provisionalFundingSource = try await virtualCardsClient.setupFundingSource(
    withInput: SetupFundingSourceInput(
      type: .creditCard,
      currency: "USD",
      applicationData: ClientApplicationData(applicationName: "yourApplicationName")),
      // include only the provider(s) you will support
      supportedProviders: ["stripe"]
    )
  )
} catch {
  // Handle/notify user of errors
}
```

The call to `setupFundingSource` will return a `ProvisionalFundingSource` containing provisioning data required to continue the funding source creation process. To continue, you must use the [Stripe API](#stripe-funding-source-preparation-1) to progress the funding source setup; the chosen provider must match that returned in the `ProvisionalFundingSource.provisioningData` property.

Retrieve the provider API key as well as other information pertaining to the funding source provider using the `getFundingSourceClientConfiguration` method and finding the client configuration for your supported provider(s):

```swift
  try {
    let configuration = try await
      virtualCardsClient.getFundingSourceClientConfiguration()
    // [configuration] contains the list of funding source client configuration.
  } catch {
    // Handle/notify user of exception
  }
}
```

**Stripe Funding Source Preparation**

{% hint style="info" %}
For Stripe iOS-Swift, see the documentation at:

<https://stripe.dev/stripe-ios/docs/>
{% endhint %}

To create a Stripe setup intent, call `STPAPIClient.confirmSetupIntent(with:)`

and ensure that the `STPSetupIntentConfirmParams` is populated with the card that is to be added as a funding source.

{% hint style="warning" %}
Card and Billing Details are required.
{% endhint %}

**Completing Funding Source Setup with Provider Specific Data**

Once the provider-specific data has been obtained, construct a provider specific completion data object and call the `completeFundingSource` method to finish the process of provisioning a funding source:

```swift
/// Stripe
let completionData = StripeCompletionDataInput(
  paymentMethodId: paymentMethodId // This is the payment method identifier of the Stripe SetupIntent success response.
)

try {
  let fundingSource = try await virtualCardsClient.completeFundingSource(
    withInput: CompleteFundingSourceInput(
      id: id, // This is the identifier of the provisional Funding Source.
      completionData: completionData
    )
  )
} catch {
  // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        val setupInput = SetupFundingSourceInput(
            "USD", 
            FundingSourceType.CREDIT_CARD,
            ClientApplicationData("yourApplicationName"),
            // include only the provider(s) you will support
            listOf("stripe")
        val provisionalFundingSource = withContext(Dispatchers.IO) {
            virtualCardsClient.setupFundingSource(
                setupInput
            )
        }
        // The returned [provisionalFundingSource] has been created.
    } catch (e: FundingSourceException) {
        // Handle/notify user of exception
    }
}
```

The call to `setupFundingSource` will return a `ProvisionalFundingSource` containing provisioning data required to continue the funding source creation process. To continue, you must use the [Stripe API](#stripe-funding-source-preparation) to progress the funding source setup; the chosen provider must match that returned in the `ProvisionalFundingSource.provisioningData` property.

Retrieve the provider API key as well as other information pertaining to the funding source provider using the `getFundingSourceClientConfiguration` method:

```kotlin
launch {
    try {
        val configuration = withContext(Dispatchers.IO) {
            virtualCardsClient.getFundingSourceClientConfiguration()
        }
        // [configuration] contains the list of funding source client configuration.
    } catch (e: FundingSourceException) {
        // Handle/notify user of exception
    }
}
```

**Stripe Funding Source Preparation**

{% hint style="info" %}
For Stripe Android-Kotlin, see the documentation at:

<https://stripe.dev/stripe-android/>
{% endhint %}

To setup a setup intent in Stripe, call `StripeClient.confirmSetupIntent(confirmSetupIntentParams: ConfirmSetupIntentParams)` and ensure that the `ConfirmSetupIntentParams` input parameter is populated with card details and billing details that you wish to add as a funding source.

{% hint style="warning" %}
Card and Billing Details are required.
{% endhint %}

**Completing Funding Source Setup with Provider Specific Data**

Once the provider-specific data has been obtained, construct a provider specific completion data object and call the `completeFundingSource` method to finish the process of provisioning a funding source:

```kotlin
launch {
    try {
        val completionData = StripeCardProviderCompletionData(
            paymentMethodId // This is the payment method identifier of the Stripe SetupIntent success response.
        )
  
        val completeInput = CompleteFundingSourceInput(
            provisionalFundingSource.id,
            completionData,
            null
        )
        val fundingSource = withContext(Dispatchers.IO) {
            virtualCardsClient.completeFundingSource(
                completeInput
            )
        }
        // The returned [fundingSource] has been created.
    } catch (e: FundingSourceException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

### Failure during payment processor interaction

If there is any failure returned from the payment processor (Stripe) prior to calling `completeFundingSource`, then the provisional funding source returned by `setupFundingSource` method may be re-used for multiple attempts at `completeFundingSource`.

Examples of failures include the user entering incorrect expiry, billing address or security code information for a card.

## Cancelling a Provisional Funding Source

If a user would like to cancel a provisional funding source before it is completed, they can call the `cancelProvisionalFundingSource` method. This method cancels setting up the funding source at the funding source provider and places the provisional funding source in the `failed` state.

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

```typescript
// Collect the input provisionalFundingSourceId as appropriate 
// for your implementation.
try {
    const provisionalFundingSource = await virtualCardsClient.cancelProvisionalFundingSource(
        provisionalFundingSourceId
    )
    // The returned `provisionalFundingSource` is now cancelled and is in a Failed state.
} catch {
    // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  // Collect the input provisionalFundingSourceId as appropriate 
  // for your implementation.
  let provisionalFundingSource = try await self.virtualCardsClient.cancelProvisionalFundingSource(
    withId: provisionalFundingSourceId
  )
  // The returned funding source is now cancelled and is in a failed state.            
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
// Collect the input provisionalFundingSourceId as appropriate 
// for your implementation.
try {
    val provisionalFundingSource = withContext(Dispatchers.IO) {
        virtualCardsClient.cancelProvisionalFundingSource(
            provisionalFundingSourceId
        )
    }
    // The returned [provisionalFundingSource] is now cancelled and 
    // is in a FAILED state.
} catch (e: FundingSourceException) {
    // Handle/notify user of exception
}
```

{% endtab %}
{% endtabs %}

## Cancelling a Funding Source

If a user decides they want to remove a funding source from their account, the ability to provide them with the means to do so is applied with the `cancelFundingSource` method.

{% hint style="danger" %}
When a funding source is cancelled, any associated active virtual cards will no longer be able to process transactions. Any such transactions will be declined.
{% endhint %}

This API call is idempotent, so cancelling an already-cancelled funding source will yield the same result.

When a funding source is cancelled, the record of the funding source will not be deleted, but instead its state will be set to `INACTIVE` and will no longer be usable for provisioning virtual cards. This is because refunds are always returned to the same original funding source and so clients will need to be able to retain details about cancelled funding in case any such future refunds refer to them.

Any existing virtual cards funded by a cancelled funding source will become unfunded. These can be funded by creating a new funding source and setting the `updateCardFundingSource` parameter in the `completeFundingSource` method call to true.

{% hint style="warning" %}
Clients should check the result of the `cancelFundingSource` request to ensure that the funding source was able to be cancelled.&#x20;
{% endhint %}

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

```typescript
// Collect the input id however makes sense for your implementation.
// const fundingSourceId = fundingSource.id
try {
    const fundingSource = await virtualCardsClient.cancelFundingSource(
        fundingSourceId
    )
    // The returned `fundingSource` is now cancelled and in an INACTIVE state.
} catch {
    // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  // Retrieve the id for canceling the funding source.
  let fundingSource = try await self.virtualCardsClient.cancelFundingSource(withId: id)
  // The returned funding source is now cancelled.            
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
// Collect the input id however makes sense for your implementation.
// val fundingSourceId = fundingSource.id
try {
    val fundingSource = withContext(Dispatchers.IO) {
        virtualCardsClient.cancelFundingSource(fundingSourceId)
    }
    // The returned [fundingSource] is now cancelled.
} catch (e: FundingSourceException) {
    // Handle/notify user of exception
}
```

{% endtab %}
{% endtabs %}

## Retrieving Funding Sources

Previously created (or cancelled) funding sources can be accessed in two ways: via its identifier ([Single Funding Source by Id](#single-funding-source-by-id)), or via a list method ([List Funding Sources](#list-funding-sources)).

A fetch of single or multiple funding sources can be performed remotely or locally by specifying the appropriate [CachePolicy](/guides/email/caching-1.md) as part of the input.

### Single Funding Source by Id

To retrieve a single funding source given its unique `id`, use the `getFundingSource` method. This method will return the record in an active or inactive state if it exists.

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

```typescript
try {
  const fundingSource = await virtualCardsClient.getFundingSource({
    id,
    cachePolicy: CachePolicy.RemoteOnly,
  })
  // `fundingSource` contains the fundingSource object, else `undefined` if not found.
} catch {
  // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

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

{% endtab %}

{% tab title="Kotlin" %}

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

{% endtab %}
{% endtabs %}

### List Funding Sources

The ability to retrieve multiple or all funding sources available to the user is supported. Returned funding sources 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) and can contain funding sources which may be active whilst others are inactive. To return least recently updated first, provide the sortOrder parameter with a value of ascending.

A call to a list API will return a `ListOutput` object containing 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.

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

```typescript
try {
    const filter: FundingSourceFilterInput = {
        and: [
            { id: { eq: idToGet } },
            { state: { eq: FundingSourceState.Active } },
        ],
    }
    const listOutput = await virtualCardsClient.listFundingSources({
        filter,
        sortOrder: SortOrder.Asc,
        cachePolicy: CachePolicy.RemoteOnly,
        limit: 20,
        nextToken,
    })
    // `listOutput.items` contains the list of items matching the input.
    // Page through the results if `listOutput.nextToken` != undefined. 
} catch {
    // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
  let filter = FundingSourceFilterInput.and([
      FundingSourceFilterInput.id(.equals(idToGet)),
      FundingSourceFilterInput.state(.equals(FundingSourceState.active))
  ])
  let listOutput = try await self.virtualCardsClient.listFundingSources(
    withFilter: filter,
    sortOrder: .ascending,
    withLimit: 20,
    nextToken: nextToken
  )
  // `listOutput.items` contains the list of items matching the input.
  // Page through the results if `listOutput.nextToken` != nil.
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        val filter = FundingSourceFilterInput(
            id = null,
            state = null,
            and = arrayListOf(
                FundingSourceFilterInput(IdFilterInput(null, idToGet)),
                FundingSourceFilterInput(
                    null,
                    FundingSourceStateFilterInput(
                        eq = FundingSourceState.ACTIVE,
                    ),
                ),
            ),
            or = null,
            not = null
        )
        val listOutput = withContext(Dispatchers.IO) {
            virtualCardsClient.listFundingSources(
                filter,
                SortOrder.ASC,
                limit = 20, 
                nextToken = nextToken
            )
        }
        // [listOutput.items] contains the list of items matching the input.
        // Page through results if [listOutput.nextToken] != null.
    } catch (e: FundingSourceException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

## Listing Provisional Funding Sources

Provisional funding sources are created by calls to the `setupFundingSource` method. The ability to retrieve multiple or all provisional funding sources available to the user is supported. Returned provisional funding sources 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 `ListOutput` object containing 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.

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

```typescript
try {
  const filter: ProvisionalFundingSourceFilterInput = {
    and: [
      { id: { eq: idToGet } },
      { state: { eq: ProvisionalFundingSourceState.Provisioning } },
    ],
  }
  const listOutput = await virtualCardsClient.listProvisionalFundingSources(
    {
      filter,
      sortOrder: SortOrder.Asc,
      cachePolicy: CachePolicy.RemoteOnly,
      limit: 5,
      nextToken,
    },
  )
  // `listOutput.items` contains the list of items matching the input.
  // Page through the results if `listOutput.nextToken` != undefined. 
} catch {
  // Handle/notify user of errors
}
```

{% endtab %}

{% tab title="Swift" %}

```swift
do {
    let filter = ProvisionalFundingSourceFilterInput.and([
        ProvisionalFundingSourceFilterInput.id(.equals(idToGet)),
        ProvisionalFundingSourceFilterInput.state(.equals(ProvisionalFundingSourceState.provisioning))
    ])
    let listOutput = try await self.virtualCardsClient.listProvisionalFundingSources(
        withFilter: filter,
        sortOrder: .ascending,
        limit: 5,
        nextToken: nextToken
    )
    // `listOutput.items` contains the list of items matching the input.
    // Page through the results if `listOutput.nextToken` != nil.
} catch {
  // Handle/notify user of error
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        val filter = ProvisionalFundingSourceFilterInput(
            id = null,
            state = null,
            and = arrayListOf(
                ProvisionalFundingSourceFilterInput(IdFilterInput(null, idToGet)),
                ProvisionalFundingSourceFilterInput(
                    null,
                    ProvisionalFundingSourceStateFilterInput(
                        ProvisionalFundingSource.ProvisioningState.PROVISIONING,
                    ),
                ),
            ),
            or = null,
            not = null
        )
        val listOutput = withContext(Dispatchers.IO) {
            virtualCardsClient.listProvisionalFundingSources(
                filter,
                SortOrder.ASC,
                limit = 5, 
                nextToken
            )
        }
        // [listOutput.items] contains the list of items matching the input.
        // Page through results if [listOutput.nextToken] != null.
    } catch (e: FundingSourceException) {
        // Handle/notify user of exception
    }
}
```

{% endtab %}
{% endtabs %}

## Subscribing to Funding Source Changes

Consumers of the SDK may choose to subscribe to notifications of changes to Funding Sources from the Sudo Platform rather than polling. This is done via the `subscribeToFundingSourceChanges` method. Subscribing is done across all Funding Sources associated with the client, not on a per-funding-source basis.

This method accepts a unique subscription identifier and a subscriber object which must implement the `fundingSourceChanged(fundingSource: FundingSource): Promise<void>` method. This handler should contain application-specific implementation of behavior in the event of a funding source change. The subscription object should also implement the `connectionStatusChanged(state: ConnectionState): void` 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.

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

```typescript
await instanceUnderTest.subscribeToFundingSourceChanges(
  'subscription-id', 
  {
    fundingSourceChanged(fundingSource: FundingSource): Promise<void> {
      // Implement handling for funding source changed behaviour
    },
    connectionStatusChanged(state: ConnectionState): void {
      // Implement handling for subscription connection status changed
    },
  },
)
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
launch {
    try {
        withContext(Dispatchers.IO) {
            virtualCardsClient.subscribeToFundingSources('subscription-id', object : FundingSourceSubscriber {
                override fun fundingSourceChanged(fundingSource: FundingSource) {
                    // Implement handling for funding source changed behaviour
                }            
                override fun connectionStatusChanged(state: Subscriber.ConnectionState) {
                   // Implement handling for subscription connection status changed
                }
            })
        }
    } catch (e: FundingSourceException) {
        // Handle exception
    }
}
```

{% endtab %}
{% endtabs %}

If the `subscribeToFundingSourceChanges` method is called more than once with the same subscription id, subsequent invocations will replace the earlier subscriptions.

### Unsubscribing

Once no longer interested in funding source changes, clients may unsubscribe by calling the `unsubscribeFromFundingSourceChanges` method, passing the same subscription identifier.

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

```typescript
sudoVirtualCardClient.unsubscribeFromFundingSourceChanges(
  'subscription-id'
)
```

{% endtab %}

{% tab title="Kotlin" %}

<pre class="language-kotlin"><code class="lang-kotlin">withContext(Dispatchers.IO) {
    sudoVirtualCardClient.unsubscribeFromFundingSourceChanges(
        'subscription-id'
<strong>    )
</strong>}
</code></pre>

{% 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-funding-sources.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.
