Card Deposits
Introduction
The Card Deposit Service offers a native card deposit flow for those who prefer not to build their own user interface or for applications that are not PCI DSS compliant. This service provides a secure, fully managed deposit experience, allowing users to add funds to their accounts using either a new or existing card. By leveraging Paysafe's PCI DSS-compliant infrastructure, sensitive card information is handled securely, enabling you to focus on your core application functionality.
Key Features:
- Offers a native, customizable UI flow that you can adapt to match your app's design, minimizing the need to build one from scratch.
- Supports 3D Secure (3DS) authentication to ensure a secure card deposit process.
- You can choose between a full native experience, covering the entire deposit flow, or a partial integration focused on collecting card details, making it adaptable to your specific needs.
Prerequisites
Before integrating the Deposit Service, ensure you have:
- A valid Paysafe account with appropriate permissions.
- Set up instructions for Paysafe SDK Android or iOS.
- Ensure that the user has the necessary permissions to make card deposits by accessing the
DepositService
and calling thegetOptions
method and verifying that theCARD
option is included in the list of available deposit methods.
To get started, initialize the CardDepositService
instance in your app as shown below:
- Kotlin
- Swift
import com.paysafe.wallet.android.core.wallet.Wallet
val cardDeposits = Wallet.getInstance().getCardDepositService()
import PaysafeWallet
let cardDeposits = Wallet.instance.cardDepositService
In order to integrate the card deposit native flow a parameter scope pci-dss-1
is required when issuing SDK JWT token. More information about PCI DSS can be found here.
Card Deposit User Experience Overview
In the card deposit native flow, users can choose to deposit funds using either a new card or an existing saved card. The type of flow Еmbedded
or Outsourced
, is determined when the preview
method is called. Based on the card type and flow chosen, the following steps outline the user experience:
- Android
- iOS
1. Input Card Information:
- When depositing with a new card, users are shown a screen to input their card details. This includes entering the card number, expiry date, and CVV. Additionally, they have the option to save the card for future use and to assign a personalized nickname to the card.
- For existing cards, users only need to enter the CVV, as the card number and other details are already stored and pre-filled in the form.
2. Embedded Flow:
- After users enter the required details for a new or existing card, they are shown a screen to review the deposit details, including the deposit amount and any associated fees.
- After confirming, if required, the user is prompted to complete 3DS authentication for enhanced security.
- Once all steps are completed, control is returned to the client application, indicating the result of the deposit.
3. Outsourced Flow:
- Once users have entered the required details (for new or existing card), control is returned to the client application.
- The client application has the flexibility to display its own confirmation screen before completing the deposit and should handle any required actions to finalize the process.
Card Deposit Workflow Overview
1. Get Payment Instrument (optional):
When initiating a card deposit with an existing card, you will need to pass paymentInstrumentReference
containing the information of the
saved card. Then, use the PaymentInstrumentService
and call the getAll
method and retrieve the user's saved cards. After selecting a card, save its details to be used when initiating the deposit. If the user is depositing with a new card, simply proceed.
2. Card Deposit Steps:
To create a deposit, you will follow a three-step process: preview, start and confirm. Each method plays a specific role in moving the deposit through its lifecycle. There are two types of the native card deposit flow: an Embedded
flow, which handles both card information collection and deposit processing, and an Outsourced
flow, which only covers the card information collection. By default, the system uses the Embedded
flow.
1. Preview Deposit
The preview
method is the first step in the card deposit process. It allows you to validate the card deposit parameters before actually creating the deposit. This step creates a deposit in PREVIEW
state, which does not perform any transaction in the Paysafe Wallet system. Instead, it checks the deposit's configuration.
Purpose: To validate the deposit parameters (e.g., amount, instrument, etc.).
Output: A CustomerDeposit
object is returned, which is needed for the next steps.
2. Start Deposit
Once the deposit has been previewed, the start
method is called to initiate the native card deposit flow. This method launches
the user interface, allowing users to enter their card details or confirm an existing card, while performing any necessary
validations such as 3D Secure (3DS) authentication when using the Embedded
flow. Once the deposit flow is completed, the control is returned to the client application.
Purpose: To guide the user through the card deposit process, performing necessary security and validation checks, and moving the deposit toward completion.
Output: After the native card deposit flow has been completed, the result is returned to the client application in the form of a
CardDepositResult
object. This result indicates the outcome of the card deposit process, which can be a successful
completion, a failure, or a cancellation. Based on the returned result, appropriate actions must be taken to finalize the deposit process or handle any errors.
Result Types:
Completed
: The card deposit flow has been completed, but this doesn't necessarily mean that the deposit is successful. You may need to check the status or perform additional actions.Terminal Failure
: The deposit process has failed and cannot be retried. Appropriate error handling must be implemented.Non-Terminal Failure
: The deposit process encountered an issue but can be retried. The user can call thestart
method with the same deposit ID.Cancelled
: The deposit flow was cancelled by the user.
3. Confirm Deposit (optional)
The confirm
method should only be called when using the Outsourced
flow. This method transitions the deposit into PROCESSING
state, indicating that the payment is being processed by the payment provider and is actively moving through the system.
Purpose: To confirm the deposit and initiate the actual processing of the payment.
Outcome: The deposit is moved to PROCESSING
state, and you may need to handle redirection.
To initiate the Outsourced
flow, include the returnUrl
when creating the CardDepositRequest
. If the returnUrl
is not provided, the Embedded
card deposit flow will be triggered by default.
In the Outsourced
flow, after the card details are collected and the user completes the initial steps, the client application must call the confirm
method to move the deposit to PROCESSING
state. At this stage, the client application is responsible for handling any required actions to finalize the deposit, such as completing 3D Secure (3DS) authentication.
Card Deposit Types
The Paysafe Wallet SDK supports two primary methods for card deposits: using an existing card or a new card. These methods cater to different user scenarios, allowing seamless integration of card deposits into your application. Below are the key steps and differences for each type of card deposit:
Deposit With New Card
When a user opts to deposit with a new card, the card details will need to be entered and validated during the deposit process. This method is ideal for first-time users or when the user wants to add a new card to their account.
Embedded Deposit With New Card
Steps for creating an Embedded
deposit with a new card:
Step 1: Begin by calling the preview method and passing null/nil for the paymentInstrumentReference
, as no stored card will be used. This step generates a depositId
that will be required for the next steps. For the Embedded
flow you should not pass returnUrl
and method
.
Step 2: Call the start method with the saved depositId
from the CustomerDeposit
object to initiate the native card deposit flow. During this step, the user will be prompted to enter their new card details, which may include completing a 3D Secure (3DS) authentication challenge.
- Kotlin
- Swift
Details about the CustomerDeposit
object are available here.
// Step 1 - Preview
val cardDepositRequest = CardDepositRequest(
amount = 100,
currencyCode = "USD",
paymentInstrumentReference = null,
merchantRefNum = "2b01127a-4929-4d0e-b9cb-a29a8d1c499c"
)
val depositId: String
try {
val cardDepositPreview = cardDeposits.preview(cardDepositRequest)
depositId = cardDepositPreview.depositId
} catch (exception: Exception) {
// handle exception
return
}
// Step 2 - Start card deposit screen for result with launcher
val launcher = registerForActivityResult(OpenStartCardDeposit()) { result ->
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
launcher.launch(depositId)
Alternatively for Step 2 you can use startActivityForResult
method:
// Step 2 - Start card deposit screen for result
cardDepositService.start(this@MainActivity , EXTRA_CARD_DEPOSIT_RESULT, depositId)
// Then, obtain the card deposit result in onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && data != null) {
val result = IntentCompat.getParcelableExtra(
data,
EXTRA_CARD_DEPOSIT_RESULT,
CardDepositResult::class.java
)
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
}
Details about the CustomerDeposit
object are available here.
// Step 1 - Preview
let cardDepositRequest = Wallet.CardDepositRequest(
amount: 100,
currencyCode: "USD",
paymentInstrumentReference: nil,
merchantRefNum: "2b01127a-4929-4d0e-b9cb-a29a8d1c499c"
)
cardDeposits.preview(cardDepositRequest: cardDepositRequest, completion: { result in
switch result {
case .success(let cardDepositPreview):
// Handle cardDepositPreview
case .failure(let error):
// Handle error
}
// Step 2 - Start
A 'UIViewController' instance should be provided as the starting point for presenting the deposit flow.
Wallet.instance.cardDepositService.start(
with: depositID,
viewController: someViewController, // Provide UIViewController instance here
completion: { cardDepositResult in
// Handle cardDepositResult
})
Outsourced Deposit With New Card
Steps for creating an Outsourced
deposit with a new card:
Step 1: Begin by calling the preview
method and passing null/nil for the paymentInstrumentReference
, as no stored card will be used. This step generates a depositId
that will be required for the next steps. For the Outsourced
flow, you should provide a returnUrl
and can optionally specify the method
, which defaults to GET
.
Step 2: Call the start
method with the saved depositId
from the CustomerDeposit
object to initiate the native card deposit flow. During this step, the user will be prompted to enter their new card details.
Step 3: Call the confirm
method with the depositId
. After confirming the deposit, the response may include a redirect action to complete a 3DS challenge. To finalize the deposit, use the redirectUrl
to direct the user to the appropriate 3DS page.
- Kotlin
- Swift
Details about the CustomerDeposit
object are available here.
// Step 1 - Preview
val cardDepositRequest = CardDepositRequest(
amount = 100,
currencyCode = "USD",
paymentInstrumentReference = null,
merchantRefNum = "2b01127a-4929-4d0e-b9cb-a29a8d1c499c",
returnUrl = "https://www.example.com",
method = HttpRequestMethod.GET
)
val depositId: String
try {
val cardDepositPreview = cardDeposits.preview(cardDepositRequest)
depositId = cardDepositPreview.depositId
} catch (exception: Exception) {
// handle exception
return
}
// Step 2 - Start card deposit screen for result with launcher
val launcher = registerForActivityResult(OpenStartCardDeposit()) { result ->
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
launcher.launch(depositId)
// Step 3 - Confirm
val cardDepositConfirm = CardDepositConfirm(depositId = depositId)
try {
val customerDeposit = cardDeposits.confirm(cardDepositConfirm)
// Handle successful deposit confirmation
} catch (exception: WalletException) {
// handle exception
return
}
Alternatively for Step 2 you can use startActivityForResult
method:
// Step 2 - Start card deposit screen for result
cardDepositService.start(this@MainActivity , EXTRA_CARD_DEPOSIT_RESULT, depositId)
// Then, obtain the card deposit result in onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && data != null) {
val result = IntentCompat.getParcelableExtra(
data,
EXTRA_CARD_DEPOSIT_RESULT,
CardDepositResult::class.java
)
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
}
Details about the CustomerDeposit
object are available here.
// Step 1 - Preview
let cardDepositRequest = Wallet.CardDepositRequest(
amount: 100,
currencyCode: "USD",
paymentInstrumentReference: nil,
merchantRefNum: "2b01127a-4929-4d0e-b9cb-a29a8d1c499c",
returnUrl: "https://www.example.com",
method: .get
)
cardDeposits.preview(cardDepositRequest: cardDepositRequest, completion: { result in
switch result {
case .success(let cardDepositPreview):
// Handle cardDepositPreview
case .failure(let error):
// Handle error
}
// Step 2 - Start
A 'UIViewController' instance should be provided as the starting point for presenting the deposit flow.
Wallet.instance.cardDepositService.start(
with: depositID,
viewController: someViewController, // Provide UIViewController instance here
completion: { cardDepositResult in
// Handle cardDepositResult
})
// Step 3 - Confirm.
let cardDepositConfirm = Wallet.CardDepositConfirm(id: depositID)
cardDeposits.confirm(depositConfirm: depositConfirm) { result in
switch result {
case .success(let cardDeposit):
// Handle successful card deposit confirmation
case .failure(let error):
// Handle error
}
}
Deposit With Existing Card
When a user opts to deposit with an existing card, the saved card details are used to streamline the process without requiring the user to re-enter their information. This method is ideal for returning users who have previously stored their card on the system, providing a faster and more convenient deposit experience. Additionally, it reduces friction in the deposit flow, making it a preferred option for users who frequently make deposits using the same card.
Embedded Deposit With Existing Card
Steps for creating a Embedded
deposit with an existing card:
Step 1: Begin by calling the preview
method and passing the paymentInstrumentReference
with the data of the chosen card. This step generates a depositId
that will be required for the next steps. For the Embedded
flow you should not pass returnUrl
and method
.
Step 2: Call the start
method with the saved depositId
from the CustomerDeposit
object to initiate the native card deposit flow. During this step, the user will be prompted to enter their cvv details, which may include completing a 3D Secure (3DS) authentication challenge.
- Kotlin
- Swift
Details about the CustomerDeposit
object are available here.
val existingCardId = "11123"
// Step 1 - Preview
val cardDepositRequest = CardDepositRequest(
amount = 100,
currencyCode = "USD",
paymentInstrumentReference = PaymentInstrumentReference(
id = existingCardId,
instrumentType = InstrumentType.CARD
),
merchantRefNum = "2b01127a-4929-4d0e-b9cb-a29a8d1c499c"
)
try {
val cardDepositPreview = cardDeposits.preview(cardDepositRequest)
} catch (exception: Exception) {
// handle exception
}
// Step 2 - Start card deposit screen for result with launcher
val launcher = registerForActivityResult(OpenStartCardDeposit()) { result ->
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
launcher.launch(depositId)
Alternatively for Step 2 you can use startActivityForResult
method:
// Step 2 - Start card deposit screen for result
cardDepositService.start(this@MainActivity , EXTRA_CARD_DEPOSIT_RESULT, depositId)
// Then, obtain the card deposit result in onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && data != null) {
val result = IntentCompat.getParcelableExtra(
data,
EXTRA_CARD_DEPOSIT_RESULT,
CardDepositResult::class.java
)
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
}
Details about the CustomerDeposit
object are available here.
let existingCardID = "11123"
// Step 1 - Preview
let cardDepositRequest = Wallet.CardDepositRequest(
amount: 100,
currencyCode: "USD",
paymentInstrumentReference: .init(
id: existingCardID,
instrumentType: .card
),
merchantRefNum: "2b01127a-4929-4d0e-b9cb-a29a8d1c499c"
)
cardDeposits.preview(cardDepositRequest: cardDepositRequest, completion: { result in
switch result {
case .success(let cardDepositPreview):
// Handle cardDepositPreview
case .failure(let error):
// Handle error
}
// Step 2 - Start
A 'UIViewController' instance should be provided as the starting point for presenting the deposit flow.
Wallet.instance.cardDepositService.start(
with: depositID,
viewController: someViewController, // Provide UIViewController instance here
completion: { cardDepositResult in
// Handle cardDepositResult
})
Outsourced Deposit With Existing Card
Steps for creating an Outsourced
deposit with a new card:
Step 1: Begin by calling the preview
method and passing the paymentInstrumentReference
with the data of the chosen card. This step generates a depositId that will be required for the next steps. For the Outsourced
flow, you should provide a returnUrl
and can optionally specify the method
, which defaults to GET
.
Step 2: Call the start
method with the saved depositId
from the CustomerDeposit
object to initiate the native card deposit flow. During this step, the user will be prompted to enter their cvv details.
Step 3: Call the confirm
method with the depositId
. After confirming the deposit, the response may include a redirect action to complete a 3DS challenge. To finalize the deposit, use the redirectUrl
to direct the user to the appropriate 3DS page.
- Kotlin
- Swift
Details about the CustomerDeposit
object are available here.
// Step 1 - Preview
val existingCardId = "11123"
val cardDepositRequest = CardDepositRequest(
amount = 100,
currencyCode = "USD",
paymentInstrumentReference = PaymentInstrumentReference(
id = existingCardId,
instrumentType = InstrumentType.CARD
),
merchantRefNum = "2b01127a-4929-4d0e-b9cb-a29a8d1c499c",
returnUrl = "https://www.example.com",
method = HttpRequestMethod.GET
)
try {
val cardDepositPreview = cardDeposits.preview(cardDepositRequest)
} catch (exception: Exception) {
// handle exception
}
// Step 2 - Start card deposit screen for result with launcher
val launcher = registerForActivityResult(OpenStartCardDeposit()) { result ->
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
launcher.launch(depositId)
// Step 3 - Confirm
val cardDepositConfirm = CardDepositConfirm(depositId = depositId)
try {
val customerDeposit = cardDeposits.confirm(cardDepositConfirm)
// Handle successful deposit confirmation
} catch (exception: WalletException) {
// handle exception
return
}
Alternatively for Step 2 you can use startActivityForResult
method:
// Step 2 - Start card deposit screen for result
cardDepositService.start(this@MainActivity , EXTRA_CARD_DEPOSIT_RESULT, depositId)
// Then, obtain the card deposit result in onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && data != null) {
val result = IntentCompat.getParcelableExtra(
data,
EXTRA_CARD_DEPOSIT_RESULT,
CardDepositResult::class.java
)
when (result) {
CardDepositResult.Cancelled -> handleCancelled()
is CardDepositResult.TerminalFailure -> handleTerminalFailure()
is CardDepositResult.NonTerminalFailure -> handleNonTerminalFailure()
CardDepositResult.Completed -> handleCompleted()
else -> handleOtherResult()
}
}
}
Details about the CustomerDeposit
object are available here.
let existingCardID = "11123"
// Step 1 - Preview
let cardDepositRequest = Wallet.CardDepositRequest(
amount: 100,
currencyCode: "USD",
paymentInstrumentReference: .init(
id: existingCardID,
instrumentType: .card
),
merchantRefNum: "2b01127a-4929-4d0e-b9cb-a29a8d1c499c",
returnUrl: "https://www.example.com",
method: .get
)
cardDeposits.preview(cardDepositRequest: cardDepositRequest, completion: { result in
switch result {
case .success(let cardDepositPreview):
// Handle cardDepositPreview
case .failure(let error):
// Handle error
}
// Step 2 - Start
A 'UIViewController' instance should be provided as the starting point for presenting the deposit flow.
Wallet.instance.cardDepositService.start(
with: depositID,
viewController: someViewController, // Provide UIViewController instance here
completion: { cardDepositResult in
// Handle cardDepositResult
})
// Step 3 - Confirm.
let cardDepositConfirm = Wallet.CardDepositConfirm(id: depositID)
cardDeposits.confirm(depositConfirm: depositConfirm) { result in
switch result {
case .success(let cardDeposit):
// Handle successful card deposit confirmation
case .failure(let error):
// Handle error
}
}