Prepaid Cards
Introduction
The Card service exposes features for managing cards related operations and eligible programs operations:
- Get customer prepaid cards.
- Create a prepaid card.
- Activate a prepaid physical card.
- Update а card.
- Get prepaid card details.
- Retrieve customer eligible prepaid cards programs.
- Tokenize a prepaid card.
- Update card tokenization status.
Use the following code to obtain an instance of CardService
:
- Kotlin
- Swift
val cardService = Wallet.getInstance().getCardService()
import PaysafeWallet
let cardService = Wallet.instance.cardService
Get customer prepaid cards
Retrieve a list of all prepaid cards for the current customer.
CardParameters
are the parameters for filtering the customer's cards.
Use include parameter to add additional information into the response.
TOKENIZATIONS
- include information about the mobile wallets' tokenization.
AVAILABLE_ACTIONS
- include information about available actions on the card.
CardParameters
Parameter | Type | Description | Example |
---|---|---|---|
cardType | CardType | Optional parameter to filter cards by card type. | CardType.VIRTUAL / .virtual |
limit | Int | Optional parameter to limit the number of results. | 20 |
offset | Int | Optional parameter to specify the starting position, where 1 is the first page. | 0 |
status | List<CardStatus> / [Card.CardStatus] | Optional parameter. List of card statuses which will be returned. Optional parameter, if not set all statuses will be returned. | listOf(CardStatus.ACTIVE, CardStatus.CANCELLED) / [.active] |
include | List<CardIncludesParam> / [CardIncludesParam] | Optional parameter. Include listed properties in the response. | listOf(CardIncludesParam.TOKENIZATIONS) / [.tokenizations] |
CardIncludesParam
Android | iOS | Description |
---|---|---|
TOKENIZATIONS | .tokenizations | Include additional information in card. |
AVAILABLE_ACTIONS | .availableActions | Include information about available actions on the card. |
CardType
Enum class representing the type of card.
Android | iOS | Description |
---|---|---|
PHYSICAL | .physical | The card is physical. |
VIRTUAL | .virtual | The card is virtual. |
UNKNOWN | .unknown | The type of the card is unknown or cannot be determined. |
CardStatus
Enum class representing the status of a card.
Android | iOS | Description |
---|---|---|
ACTIVE | .active | The card is currently active and can be used for transactions. |
APPLIED | .applied | The card application is in progress or awaiting approval. |
EXPIRED | .expired | The card has expired and is no longer valid for use. |
DIGITAL | .digital | The card is in a digital format, such as a virtual or electronic card. |
UNKNOWN | .unknown | The status of the card is unknown or cannot be determined. |
CardScheme
Enum class representing the scheme of a card.
Android | iOS | Description |
---|---|---|
MC | .mastercard | The card is Mastercard. |
VISA | .visa | The card is Visa. |
UNKNOWN | .unknown | The scheme of the card is unknown or cannot be determined. |
The response is a CardList
object which contains List<Card>
/ [Card]
and PagingResultMeta
objects.
PagingResultMeta
contains information about:numberOfRecords
- The total number of records available.limit
- The maximum number of records per page.page
- The current page number.
Card
contains information about the card such ascardId
,status
,expiry
,lastFour
,bin
,customerId
,cardType
,programName
,currency
,mobile
,deliveryAddress
,createdDate
,accountId
,scheme
,activationDate
,isPinSet
,externalId
,tokenizations
.tokenizations
contains objectMobileWalletTokenizationList
which contains list ofMobileWalletTokenization
with information such asdpanRef
,walletId
,walletType
,status
.- '.availableActions' contains an array of
CardAvailableActions
which has an available action field.
MobileWalletType
Enum class representing the type of mobile wallet.
Android | iOS | Description |
---|---|---|
APPLE_PAY | .applePay | The wallet type is Apple Pay. |
GOOGLE_PAY | .googlePay | The wallet type is Google Pay. |
SAMSUNG_PAY | .samsungPay | The wallet type is Samsung Pay. |
UNKNOWN | .unknown | The wallet type is unknown or cannot be determined. |
MobileWalletStatus
Enum class representing the tokenization status of a mobile wallet.
Android | iOS | Description |
---|---|---|
COMPLETED | .completed | The tokenization request is completed. |
INITIATED | .initiated | The tokenization request is initiated. |
UNKNOWN | .unknown | The type of the mobile wallet is unknown or cannot be determined. |
CardAction
Enum class representing an available action that could be performed on a card.
Android | iOS | Description |
---|---|---|
ACTIVATE | .activate | Activates newly received card. Can be used only for PHYSICAL card |
CANCEL | .cancel | Cancel card for all the operations. The status is IRREVERSIBLE. |
UNLOCK | .unlock | Unlocks locked card. |
LOCK | .lock | Locks the card for all operations. It is reversible by UNLOCK action. |
SHOW_DETAILS | .showDetails | Shows sensitive card details (PAN, CVV, expiry date). |
SHOW_PIN | .showPin | Shows pin of the card. |
SET_PIN | .setPin | Updates pin of the card. |
- Kotlin
- Swift
val cardParameters = CardParameters(
cardType = CardType.ACTIVE,
limit = null,
offset = null,
status = listOf(CardStatus.ACTIVE, CardStatus.EXPIRED),
include = listof(CardIncludesParam.TOKENIZATIONS, CardIncludesParam.AVAILABLE_ACTIONS)
)
try {
val cards = cardService.getAll(cardParameters)
Log.d(TAG, cards.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let parameters = Wallet.CardParameters(cardType: .virtual,
include: [.tokenizations, .availableActions],
limit: nil,
offset: nil,
status: [.active, .expired])
cardService.getAll(parameters: parameters, completion: { result in
switch result {
case .success(let cards):
// Display cards
case .failure(let error):
// Handle error
}
})
You can achieve pagination by combining the limit
and offset
filters. For instance, implementing
pagination with a page size of 2 results per page would involve configuring:
- Page 1: limit=2, offset=0
- Page 2: limit=2, offset=2
- Page 3: limit=2, offset=4
Retrieve single prepaid card
Provides detailed information about specific card by cardId. The response is a single card object.
- Kotlin
- Swift
val cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
val include = listOf(CardIncludesParam.TOKENIZATIONS)
try {
val card = cardService.get(cardId, include)
Log.d(TAG, card.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
cardService.get(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24", include: [.tokenizations], completion: { result in
switch result {
case .success(let card):
// Display card
case .failure(let error):
// Handle error
}
})
Create a prepaid card
Create a new Physical or Virtual prepaid card for a customer, based on the provided programName
.
CardRequest
Parameter | Type | Description | Example |
---|---|---|---|
programName | String | Required parameter. Program name retrieved from CardService. | "SKRILL-VIRTUAL-MC-EEA" |
currency | String | Required parameter. Currency code in three-letter format ("Alpha-3"). | "EUR" |
mobile | String | Optional parameter. If not provided, we will try to use the mobile phone provided during the customer onboarding. Mobile phone number must be in format +1 123456789 . | "+1 123456789" |
cardPin | String | Optional parameter. For EU Customers: The Card pin should be 4 digits. US customers must set their card PIN via a separate REST HTTP call. | "1234" |
externalId | String | Optional external identifier in merchant system. | "a2322550-af91-417f-867e-681efad44b9d" |
deliveryAddress | DeliveryAddress | Optional parameter. The DeliveryAddress object will be used for the PHYSICAL card delivery. It can be null in case of VIRTUAL card. | |
termsAndConditionsAccepted | Boolean / Bool | The field must be present for US customers. For other countries it is not required. | True |
eDisclosureAccepted | Boolean / Bool | The field must be present for US customers. For other countries it is not required. | True |
DeliveryAddress
Parameter | Type | Description | Example |
---|---|---|---|
address1 | String | Required parameter mandatory for address. | "Tsarigradsko Shose 73" |
address2 | String | Optional parameter for address line 2 | "Apartment 2C" |
address3 | String | Optional parameter for address line 3. | "Cityville, State 78901" |
city | String | Required parameter for specifying the city. Max 30 characters: letters, spaces, hyphen and period. | "Sofia" |
countryCode | String | Required parameter in ISO-3166 Alpha 2, representing a country code | "BG" |
state | String | Optional parameter. For US Customers: 2,3-character state or province abbreviation. | "NL" |
postalCode | String | Required parameter. For EU customers: maximum length 16. For US customers: Pattern: ^[a-zA-Z0-9-\ ]*$ | "1000" |
- Kotlin
- Swift
val cardRequest = CardRequest(
programName = "SKRILL-VIRTUAL-MC-EEA",
currency = "BGN",
mobile = "+1 123456789",
cardPin = null,
externalId = null,
deliveryAddress = null
)
try {
val card = cardService.create(cardRequest)
Log.d(TAG, card.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let request = Wallet.CardRequest(programName: "SKRILL-VIRTUAL-MC-EEA",
currency: "EUR",
mobile: "+1 123456789",
cardPin: nil,
externalID: nil,
deliveryAddress: nil,
termsAndConditionsAccepted: nil,
eDisclosureAccepted: nil)
cardService.create(request: request, completion: { result in
switch result {
case .success(let card):
// Display created card
case .failure(let error):
// Handle error
}
})
The parameters for PHYSICAL
/ .physical
card application are the same, but you need to specify the deliveryAddress
parameter.
- Kotlin
- Swift
val cardRequest = CardRequest(
programName = "SKRILL-PHYSICAL-MC-EEA",
currency = "BGN",
mobile = "+1 123456789",
cardPin = null,
externalId = null,
deliveryAddress = DeliveryAddress(
address1 = "Tsarigradsko Shose 73",
address2 = null,
address3 = null,
city = "Sofia",
countryCode = "BG",
state = null,
postalCode = "1000"
)
)
try {
val card = cardService.create(cardRequest)
Log.d(TAG, card.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let request = Wallet.CreateCardRequest(programName: "SKRILL-PHYSICAL-MC-EEA",
currency: "EUR",
mobile: "+1 123456789",
cardPin: nil,
externalId: nil,
deliveryAddress: .init(address1: "Tsarigradsko Shose 73",
address2: nil,
address3: nil,
city: "Sofia",
countryCode: "BG",
state: nil,
postalCode: "1000"))
cardService.create(request: request, completion: { result in
switch result {
case .success(let card):
// Display created card
case .failure(let error):
// Handle error
}
})
Activate a prepaid physical card
This endpoint facilitates the activation of a specific prepaid physical card, identified by its unique cardId. The card can be activated only once and only while it is with status ISSUED or DIGITAL. Once activated, the card's status transitions to ACTIVE, enabling users to conveniently utilize their physical card.
CardActivationRequest
Parameter | Type | Description | Example |
---|---|---|---|
lastFourDigits | String | Required parameter containing the last four-digit of the PAN. | "1234" |
cvv | String | Required parameter containing the CVV of the card. | "567" |
- Kotlin
- Swift
val cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
val cardActivationRequest = CardActivationRequest(
lastFourDigits = "1234",
cvv = "567"
)
try {
val card = cardService.activate(cardId, cardActivationRequest)
Log.d(TAG, card.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let request = Wallet.CardActivationRequest(lastFourDigits: "1234",
cvv: "567")
cardService.activate(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24", request: request, completion: { result in
switch result {
case .success(let card):
// Display card
case .failure(let error):
// Handle error
}
})
Update prepaid card
The customer can change/update:
- The status of their prepaid cards, and as a result, lock/unlock or cancel their prepaid card.
- The card's pin.
In the table below, you can find information about the statuses that the user can change.
From \ To | ACTIVE | CANCELLED | LOCKED | DIGITAL |
---|---|---|---|---|
ACTIVE | - | ✅ | ✅ | ❌ |
CANCELLED | ❌ | - | ❌ | ❌ |
LOCKED | ✅ | ✅ | - | ❌ |
DIGITAL | ✅ (By PIN & CHIP) | ✅ | ✅ | - |
- Kotlin
- Swift
To update card use CardUpdateRequest
.
// Status update request
val cardUpdateRequest = CardUpdateRequest(
status = Status.ACTIVE,
statusReason = "User Activate Card from LOCKED status"
)
// Pin change request
val cardUpdateRequest = CardUpdateRequest(
pin = "1243"
)
val cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
try {
val card = cardService.update(cardId, cardUpdateRequest)
Log.d(TAG, card.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
To update card use CardUpdateRequest
.
// Status update request
let request = Wallet.CardUpdateRequest(status: .active,
statusReason: "User Activate Card from LOCKED status")
// Pin change request
let request = Wallet.CardUpdateRequest(pin: "1234")
let cardID = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
cardService.update(cardID: cardID, request: request, completion: { result in
switch result {
case .success(let card):
// Display card
case .failure(let error):
// Handle error
}
})
Get prepaid card details
Card sensitive information can be viewed by the customers via specialized custom views. This can be either one view containing the whole card with the information on it, or separate views for each sensitive information: card PAN, CVV, expiry date and cardholder's name.
This operation might require Strong Customer Authentication (SCA). Please read Strong Customer Authentication for more information on the process.
Get card details views
Call getSecureDetailsViews
method of the CardService
interface with the card ID to obtain a
CardSecureDetailsViews
object containing specialized secure views for the card PAN, CVV, expiry date and cardholder's name.
For security reasons these details cannot be obtained in plain text.
This operation requires the SDK to be authenticated with a customer token with pci-dss-1
scope. The scope can be
requested during the token exchange process as described in
SDK User Authentication.
- Android
- iOS
The getSecureDetailsViews
method has an optional parameter viewsConfiguration
that can be used to
set a custom PAN separator and which view should have a copy button.
By default the PAN separator is an empty space ("1234 5678 9012 3456") and the copy button is disabled for all views.
The copy button copies the text from the respective view to the clipboard with the ClipDescription.EXTRA_IS_SENSITIVE extra set to true.
val viewsConfiguration = SecureDetailsViewsConfiguration(
shouldShowPanCopyButton = true,
shouldShowCvvCopyButton = true,
shouldShowExpiryDateCopyButton = true,
shouldShowCardholderNameCopyButton = true,
panSeparator = "-"
)
val request = CardSecureDetailsViewRequest(
cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24",
viewsConfiguration = viewsConfiguration
)
val cardSecureDetailsViews: CardSecureDetailsViews = try {
// call getSecureDetailsViews with the card ID and the viewsConfiguration
cardService.getSecureDetailsViews(request )
} catch (e: ScaRequiredException) {
// solve SCA challenge using e.authenticationEvent
// as described in https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication
// repeat getSecurePinView with the SCA details
cardService.getSecureDetailsViews(
CardSecureDetailsViewRequest(
cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24",
viewsConfiguration = viewsConfiguration,
scaDetails = ScaAuthenticationEventRequest(
eventId = e.authenticationEvent.eventId,
walletOperationId = e.authenticationEvent.walletOperationId
)
)
)
}
// Classic Views: Add the cardSecureDetailsViews to your view hierarchy
container.addView(cardSecureDetailsViews.panView)
container.addView(cardSecureDetailsViews.cvvView)
container.addView(cardSecureDetailsViews.expiryDateView)
container.addView(cardSecureDetailsViews.cardHolderNameView)
// Jetpack Compose: Create a composable function to display the cardSecureDetailsViews
@Composable
fun SecureDetailsComposable(request: CardSecureDetailsViewRequest) {
val cardSecureDetailsViews = remember { mutableStateOf<CardSecureDetailsViews?>(null) }
LaunchedEffect(true) {
cardSecureDetailsViews.value =
Wallet.getInstance().getCardService().getSecureDetailsViews(request)
}
cardSecureDetailsViews.value?.let { detailViews ->
val panView = detailViews.panView
val cvvView = detailViews.cvvView
val expiryDateView = detailViews.expiryDateView
val cardHolderNameView = detailViews.cardHolderNameView
Column {
AndroidView(factory = { panView })
AndroidView(factory = { cvvView })
AndroidView(factory = { expiryDateView })
AndroidView(factory = { cardHolderNameView })
}
}
}
Customize card details views
The copy button icon color is based on the text color of the respective view.
A custom icon for the copy button can be used by setting the pswIconCopyButton
attribute as explained in
UI Theme Configuration.
By default the card detail views use font sans-serif
with text size 24sp for the PAN and 20sp for CVV, Expiry and Cardholder's name.
The text style for each view can be changed by setting the pswTextAppearanceCardPan
, pswTextAppearanceCardCvv
,
pswTextAppearanceCardExpiry
and pswTextAppearanceCardCardholderName
attributes as explained in
UI Theme Configuration.
When the views are placed over card image, please make sure that the card design aligns with the approved guidelines from the Card Scheme.
The getSecureDetailsViews
method has an optional parameter viewsConfiguration
that can be used to configure the views.
If not set, a default configuration is applied - Title 1
text style and Text Primary
color, PAN(card number) separator is an empty space ("1234 5678 9012 3456") and the copy button is disabled for all views.
let request = Wallet.CardSecureDetailsViewsRequest(
cardSecureViewRequest: Wallet.CardSecureViewRequest(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24")
)
Customize card details views
Using SecureDetailsViewsConfiguration
each view can be customized.
Customization options include pan separator (for PAN/card number), font, text color, whether to have a copy button or not and action for tapping on the copy button.
The copy button's color and size are based on the text's respective properties.
Its default icon is a system icon doc.on.doc
. It can be changed by configuring iconCopy
as explained in UI Appearance configuration.
When a copy button is tapped, the text is copied in UIPasteboard.general
.
To trigger an additional action, set didTapCopyButton
for the respective view.
When the views are placed over card image, please make sure that the card design aligns with the approved guidelines from the Card Scheme.
let viewsConfiguration = Wallet.SecureDetailsViewsConfiguration(
panViewConfiguration: .init(
font: UIFont.systemFont(ofSize: 30),
textColor: .white,
hasCopy: true,
didTapCopyButton: { view in
// Trigger some event
}
),
panSeparator: "-",
cvvViewConfiguration: .init(
font: UIFont.systemFont(ofSize: 25),
textColor: .white,
hasCopy: true,
didTapCopyButton: { view in
// Trigger some event
}
),
expiryViewConfiguration: .init(
font: UIFont.systemFont(ofSize: 20),
textColor: .white
),
cardHolderViewConfiguration: .init(
font: UIFont.systemFont(ofSize: 20),
textColor: .white
)
)
let request = Wallet.CardSecureDetailsViewsRequest(
cardSecureViewRequest: Wallet.CardSecureViewRequest(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24"),
viewsConfiguration: viewsConfiguration
)
Wallet.instance.cardService.getSecureDetailsViews(
request,
completion: { result in
switch result {
case .success(let views):
// Display the views
break
case .failure(let error):
if case .scaRequired(let authenticationEvent, _) = error as? Wallet.WalletError {
// solve SCA challenge using e.authenticationEvent
// as described in https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication
// repeat getSecureDetailsViews with the SCA details
Wallet.instance.cardService.getSecureDetailsViews(
.init(
cardSecureViewRequest: .init(
cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24",
scaDetails: .init(
eventID: authenticationEvent.eventID,
walletOperationID: authenticationEvent.walletOperationID
)
),
viewsConfiguration: viewsConfiguration
),
completion: { _ in
// Handle the result
}
)
} else {
// Handle other errors
}
}
}
)
// SwiftUI: Create a UIViewRepresentable to wrap the UIView
struct ViewWrapper: UIViewRepresentable {
let wrapped: UIView
func makeUIView(context: Context) -> UIView {
wrapped
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct ViewWrapperPresenter: View {
let uiView: UIView
var body: some View {
let viewSize = uiView.systemLayoutSizeFitting(.zero)
ViewWrapper(wrapped: uiView)
.frame(width: viewSize.width,
height: viewSize.height)
}
}
...
@State var views: Wallet.CardSecureDetailsViews?
var body: some View {
if let views {
VStack {
ViewWrapperPresenter(uiView: views.panView)
ViewWrapperPresenter(uiView: views.cvvView)
ViewWrapperPresenter(uiView: views.expiryView)
ViewWrapperPresenter(uiView: views.cardHolderNameView)
}
}
}
...
Get card fragment (Legacy)
- Android
- iOS
On Android sensitive card information is displayed in a custom Fragment
. Call getSecureDetailsFragment
method
of the CardService
interface with the card ID to obtain a new instance of the fragment and use
supportFragmentManager
to display it. The sensitive data loads automatically when the fragment is visualized.
class ExampleActivity : AppCompatActivity(R.layout.example_activity) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val fragment = cardService.getSecureDetailsFragment(
CardSecureDetailsRequest(cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24")
)
supportFragmentManager.commit {
replace(R.id.fragment_container_view, fragment)
}
}
}
}
The fragment transaction is only created when savedInstanceState
is null
. This is to ensure that
the fragment is added only once, when the activity is first created. When a configuration change occurs and the
activity is recreated, savedInstanceState
is no longer null
, and the fragment does not need to be added a second
time, as it is automatically restored from the savedInstanceState
.
Handle Strong Customer Authentication
A ScaAuthenticationEventListener
should be attached to this fragment using the listenScaAuthenticationEvent
method.
In the case when a Strong Customer Authentication (SCA) is required, this listener will be triggered.
Follow the steps outlined in Submit the SCA Authentication
to confirm the event. After the SCA event is accepted, call authenticateScaEvent
with the scaDetails obtained from the listener.
// use the fragment created with cardService.getSecureDetailsFragment()
cardService.listenScaAuthenticationEvent(fragment) { scaDetails ->
// solve SCA challenge using scaDetails
// as described in https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication
// call authenticateScaEvent to obtain the card details
cardService.authenticateScaEvent(fragment, ScaAuthenticationEventRequest(...))
}
Implement the CardSecureDetailsViewControllerDelegate
and assign it
to delegate
property to the CardSecureDetailsViewController
returned from getSecureDetailsViewController
.
The delegate's cardSecureDetails(_:didReceiveSCAEvent:completionHandler:)
function will be invoked when Strong Customer Authentication is required.
Follow the steps outlined in Submit the SCA Authentication
to confirm the event. After the SCA event is accepted, invoke the completionHandler
.
let cardDetailsViewController = cardService.getSecureDetailsViewController(.init(
cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24"
))
cardDetailsViewController.delegate = ExampleViewController(...)
viewController.present(cardDetailsViewController, animated: true)
func cardSecureDetails(_ viewController: any CardSecureDetailsViewController,
didReceiveSCAEvent scaResponse: Wallet.SCAAuthenticationEventResponse,
completionHandler: @escaping @MainActor @Sendable (Wallet.SCAAuthenticationEventRequest) -> Void) {
// Present SCA authentication to the user (e.g. OTP)
// and record the attempt
// https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication#submit-the-sca-authentication
// Then call completionHandler
}
Get prepaid card PIN
A card PIN can be viewed by the customers via specialized secure view. This secure view is obtained asynchronously
with the getSecurePinView
method of the CardService
interface and then attached to the screen. For security reasons,
the PIN cannot be obtained in plain text.
This operation requires the SDK to be authenticated with a customer token with pci-dss-1
scope. The scope can be
requested during the token exchange process as described in
SDK User Authentication.
This operation might require Strong Customer Authentication (SCA). Please read Strong Customer Authentication for more information on the process.
Customize PIN font and color
By default the PIN is displayed on a transparent background using Title 1
text style and Text Primary
color.
These values can be customized as explained in UI Theme Configuration
for Android and UI Appearance configuration
for iOS.
- Android
- iOS
val pinView: View = try {
// call getSecurePinView with the card ID
cardService.getSecurePinView(CardSecureViewRequest(cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"))
} catch (e: ScaRequiredException) {
// solve SCA challenge using e.authenticationEvent
// as described in https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication
// repeat getSecurePinView with the SCA details
cardService.getSecurePinView(
CardSecureViewRequest(
cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24",
scaDetails = ScaAuthenticationEventRequest(
eventId = e.authenticationEvent.eventId,
walletOperationId = e.authenticationEvent.walletOperationId
)
)
)
}
// Jetpack Compose: Create a composable function to display the pinView
@Composable
fun CardPin(cardId: String) {
val pinView = remember { mutableStateOf<View?>(null) }
LaunchedEffect(true) {
pinView.value = cardService.getSecurePinView(CardSecureViewRequest(cardId))
}
pinView.value?.let { view ->
AndroidView(factory = { view })
}
}
// Classic Views: Add the pinView to your view hierarchy
container.addView(pinView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
Wallet.instance.cardService.getSecurePINView(
.init(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24"),
completion: { result in
switch result {
case .success(let pinView):
// Show PIN view
break
case .failure(let error):
if case .scaRequired(let authenticationEvent, _) = error as? Wallet.WalletError {
// solve SCA challenge using e.authenticationEvent
// as described in https://docs.paysafe.com/docs/embedded-wallets/strong-customer-authentication
// repeat getSecurePINView with the SCA details
Wallet.instance.cardService.getSecurePINView(.init(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24",
scaDetails: .init(eventID: authenticationEvent.eventID,
walletOperationID: authenticationEvent.walletOperationID)),
completion: { _ in
// Handle the result
})
} else {
// Handle other errors
}
}
}
)
// SwiftUI: Create a UIViewRepresentable to wrap the UIView
struct ViewWrapper: UIViewRepresentable {
let wrapped: UIView
func makeUIView(context: Context) -> UIView {
wrapped
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct ViewWrapperPresenter: View {
let uiView: UIView
var body: some View {
let viewSize = uiView.systemLayoutSizeFitting(.zero)
ViewWrapper(wrapped: uiView)
.frame(width: viewSize.width,
height: viewSize.height)
}
}
...
@State var pinView: UIView?
var body: some View {
if let pinView {
ViewWrapperPresenter(uiView: pinView)
}
}
...
Retrieve customer eligible prepaid cards programs
Retrieve eligible programs for a customer. The result list can be filtered by program type.
If the customer is eligible for a Prepaid card, the response will contain a non-empty programs array.
The number of cards that can be issued of a given type can be seen in the allowableCards
field.
- Kotlin
- Swift
try {
val eligiblePrograms = cardService.getPrograms()
Log.d(TAG, eligiblePrograms.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
cardService.getPrograms(completion: { result in
switch result {
case .success(let programs):
// Display prepaid programs
case .failure(let error):
// Handle error
}
})
Tokenize a prepaid card
Tokenize given card for specific mobile wallet.
CardTokenizationRequest
Card Tokenization by which the client mobile application requests the needed info for card tokenization for a specific mobile wallet. Depending on the mobile wallet, device type, and card schema, some of the request fields are optional.
Parameter | Type | Description | Example |
---|---|---|---|
walletType | MobileWalletType | Required parameter containing supported wallet types. | MobileWalletType.GOOGLE_PAY / .applePay |
clientDeviceId / clientDeviceID | String | Required for cards with VISA schema + Samsung/Google Pay. Not required for VISA + Apple Wallet. | "WNkcsfZPiKfa5PrH3jilkQYT" |
clientWalletAccountId / clientWalletAccountID | String | Required for cards with VISA schema + Samsung/Google Pay. | "40exmLiWV1iV55ZVstOAiMf7" |
leafCertificate | String | Required for iOS. The leaf certificate returned by Apple Wallet, that was signed by using the subordinateCertificate. Should be Base-64 encoded binary data of the certificate. | |
subordinateCertificate | String | Required for iOS. The subordinate certificate returned by Apple Wallet that was signed using the Wallet Provider’s Certificate Authority (CA) certificate. Should be Base-64 encoded binary data of the certificate. | |
nonce | String | Required for iOS. Base-64 encoded nonce value returned by Apple Wallet. | "w1koQg==" |
nonceSignature | String | Required for iOS. Base-64 encoded nonce signature value returned by Apple Wallet. | "QF7lLU3rlO...iuscoJE9PdX" |
The response is a CardTokenization
object which contains the needed information in order specific
card to be added to APPLE/GOOGLE Pay. Depending on the mobile device, some of the fields are
optional.
opaquePaymentCard
- It will be returned only for Google Pay.encryptedPassData
- Will be returned only for Apple Pay.ephemeralPublicKey
- Will be returned only for Apple Pay. Some other information that this object contains is:activationData
,cardNetwork
,tokenProvider
,customer
,cardLastDigits
.customer
- An object containing information about specific cardholder such as:address1
,address2
,address3
,city
,countryCode
,state
,postalCode
,name
,mobile
CardNetworkType
Enum class for representing cardNetwork
.
Android | iOS | Description |
---|---|---|
CARD_NETWORK_VISA | .cardNetworkVisa | Visa card network. |
CARD_NETWORK_MASTERCARD | .cardNetworkMastercard | Mastercard card network. |
UNKNOWN | .unknown | The network of the card is unknown or cannot be determined. |
CardTokenProvider
Enum class for representing tokenProvider
.
Android | iOS | Description |
---|---|---|
TOKEN_PROVIDER_VISA | .tokenProviderVisa | Visa token provider. |
TOKEN_PROVIDER_MASTERCARD | .tokenProviderMastercard | Mastercard token provider. |
UNKNOWN | .unknown | The token provider is unknown or cannot be determined. |
- Kotlin
- Swift
val cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
val cardTokenizationRequest = CardTokenizationRequest(
walletType = MobileWalletType.GOOGLE_PAY,
clientDeviceId = "WNkcsfZPiKfa5PrH3jilkQYT",
clientWalletAccountId = "40exmLiWV1iV55ZVstOAiMf7"
)
try {
val cardTokenizationResponse = cardService.tokenize(cardId, cardTokenizationRequest)
Log.d(TAG, cardTokenizationResponse.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let leafCertificate = "<base64 encoded certificate>"
let subordinateCertificate = "<base64 encoded certificate>"
let nonce = "<encoded value>"
let nonceSignature = "<encoded value>"
let request = Wallet.CardTokenizationRequest(leafCertificate: leafCertificate,
subordinateCertificate: subordinateCertificate,
nonce: nonce,
nonceSignature: nonceSignature)
cardService.tokenize(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24", request: request, completion: { result in
switch result {
case .success(let card):
// Display card
case .failure(let error):
// Handle error
}
})
Update card tokenization status
Updates card tokenization status by which tokenization can be marked as COMPLETED
. Tokenization
marked as COMPLETED
means that the given card will be not able to be tokenized for the specific
device and profile (in case of Google Pay).
MobileWalletTokenization
Contains information about card enrolment for specific Mobile Wallet.
Parameter | Type | Description | Example |
---|---|---|---|
dpanRef | String | Required parameter. dpan (Device Pan) reference. | "DAPLMC00002125433c0c34a2821f4f86866e7576963baf8b" |
walletId | String | Optional parameter. Wallet Id used only with Google Pay. | "NMZlGi8-DezZZKaU06orvl0f" |
walletType | MobileWalletType | Required parameter. The card pin should be 4 digits. | MobileWalletType.GOOGLE_PAY / .applePay |
status | MobileWalletStatus | Required parameter. The user agent of the device used for card creation. | MobileWalletStatus.COMPLETED / .completed |
The response is the updated MobileWallet
object.
- Kotlin
- Swift
val mobileWalletRequest = MobileWallet(
dpanRef = "DAPLMC00002125433c0c34a2821f4f86866e7576963baf8b",
walletId = walletId,
walletType = MobileWalletType.GOOGLE_PAY,
status = MobileWalletStatus.COMPLETED
)
val cardId = "f16ba382-eb42-481a-b08f-c57bdc9aae24"
try {
val mobileWalletResponse = cardService.updateTokenizationStatus(cardId, mobileWalletRequest)
Log.d(TAG, mobileWalletResponse.toString())
} catch (exception: Exception) {
Log.d(TAG, exception.toString())
}
let request = Wallet.MobileWalletTokenization(dpanRef: "DAPLMC00002125433c0c34a2821f4f86866e7572963baf8b",
status: .completed)
cardService.updateTokenizationStatus(cardID: "f16ba382-eb42-481a-b08f-c57bdc9aae24", request: request, completion: { result in
switch result {
case .success(let card):
// Display card
case .failure(let error):
// Handle error
}
})
Apple card tokenization
This section includes both sequence diagrams and a technical guide outlining the process of adding a card to Apple Wallet through the In-App Provisioning flow.
Prior to initiating the card addition implementation for Apple Wallet, it is essential to submit a request for access to Apple Pay in accordance with the latest guidelines provided in the Apple Developer documentation. For information on the prerequisites and required steps, please refer to Setting up Apple Pay documentation page.
Apple Pay button
"Add to Apple Wallet" Button should only be displayed if a card does not exist in Apple Wallet on either the iPhone or Apple Watch.
- Verify device eligibility using
canAddPaymentPass()
. - Check PKPassLibrary that card does not exist in
passes()
andremoteSecureElementPasses
. - Display "Add to Apple Wallet" button using PKAddPassButton if above conditions are met.
- Tapping the button triggers In-App provisioning.
In-App provisioning
Once the user taps the 'Add to Apple Wallet' button, In-App Provisioning starts by initializing and presenting PKAddPaymentPassViewController by providing PKAddPaymentPassRequestConfiguration.
func createPaymentPassRequestConfiguration(customerInfo: Wallet.CustomerInfo, card: Wallet.Card) -> PKAddPaymentPassRequestConfiguration? {
guard let requestConfiguration = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
return nil
}
requestConfiguration.cardholderName = "\(customerInfo.firstName) \(customerInfo.lastName)"
requestConfiguration.primaryAccountSuffix = card.lastFour
// Filter the devices that already have this card provisioned, by setting requestConfiguration.primaryAccountIdentifier
requestConfiguration.paymentNetwork = card.scheme.paymentNetwork
return requestConfiguration
}
Subsequently, Apple Wallet requests creation of PKAddPaymentPassRequest through PKAddPaymentPassViewControllerDelegate. The received Apple Public Certificates, along with nonce and nonceSignature, are used to make cardService.tokenize()
request to the SDK.
Below code block demonstrates how CardTokenizationRequest is created from data returned by the delegate.
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
generateRequestWithCertificateChain certificates: [Data],
nonce: Data,
nonceSignature: Data,
completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
let certificatesBase64EncodedString = certificates.map { $0.base64EncodedString() }
let nonceBase64EncodedString = nonce.base64EncodedString()
let nonceSignatureBase64EncodedString = nonceSignature.base64EncodedString()
guard let leafCertificate = certificatesBase64EncodedString.first,
let subordinateCertificate = certificatesBase64EncodedString.last else {
// Handle error
return
}
let tokenizationRequest = Wallet.CardTokenizationRequest(leafCertificate: leafCertificate,
subordinateCertificate: subordinateCertificate,
nonce: nonceBase64EncodedString,
nonceSignature: nonceSignatureBase64EncodedString)
Wallet.instance.cardService.tokenize(cardID: card.cardID,
request: tokenizationRequest,
completion: { result in
switch result {
case .success(let cardTokenization):
let addPaymentPassRequest = PKAddPaymentPassRequest()
addPaymentPassRequest.activationData = cardTokenization.activationData.flatMap { Data(base64Encoded: $0) }
addPaymentPassRequest.encryptedPassData = cardTokenization.encryptedPassData.flatMap { Data(base64Encoded: $0) }
addPaymentPassRequest.ephemeralPublicKey = cardTokenization.ephemeralPublicKey.flatMap { Data(base64Encoded: $0) }
handler(addPaymentPassRequest)
case .failure(let error):
// Handle error
}
})
}
Apple Wallet further forwards the encryptedPassData and ephemeralPublicKey to the PNO (Payment Network Operator) or service provider, where validation checks are conducted. The PNO validates the activationData and performs final approval checks, subsequently sending the successful activation status to Apple Wallet. PKAddPaymentPassViewControllerDelegate notifies success/failure.
func addPaymentPassViewController(
_ controller: PKAddPaymentPassViewController,
didFinishAdding pass: PKPaymentPass?,
error: Error?
)
Successful activation is communicated to the SDK with cardService.updateTokenizationStatus()
which finalizes the process.
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
didFinishAdding pass: PKPaymentPass?,
error: (any Error)?) {
if let pass = pass {
let request = Wallet.MobileWalletTokenization(dpanRef: pass.deviceAccountIdentifier,
status: .completed)
Wallet.instance.cardService.updateTokenizationStatus(cardID: card.cardID,
request: request,
completion: { result in
// Handle result and refresh UI
})
} else if let error = error {
// Handle error
}
}
Google card tokenization
This section offers sequence diagrams and a detailed technical step-by-step guide for integrating card tokenization into Google Wallet using Android Push Provisioning API.
Card tokenization involves generating a secure digital replica of an existing physical or virtual card. This replica is subsequently integrated into a token network, such as Google Pay. For Google Pay, token provisioning can occur either manually, where a tokenization request is initiated from Google Wallet, or through push provisioning, where the request originates from your application.
Before initiating the card tokenization process on Google Wallet, the first step is creating a Google account if one has not already been created. After setting up your Google account, you can initiate the access request through this form, as gaining access involves submitting a request to Google Android Push Provisioning API.
After obtaining access, you can download and import the latest version of play-services-tapandpay.aar enabling you to use the TapAndPay SDK. TapAndPay SDK is a library developed by Google which streamlines the necessary calls to integrate with Google Wallet.
Google button display
The initial step is to display the Google button with the appropriate logo and dimensions. Google Pay button should be only displayed if a card does not exist in Google Pay/Wallet. Further information about this can be found in the official documentation provided by Google once access to the page is granted.
The sequence diagram below illustrates the essential checks required to initialize and display the Google button, in conjunction with the Paysafe mobile SDK, Android OS, and TapAndPay SDK.
Google button click
After completing the aforementioned steps to display and initialize the button, a subsequent click on the button will initiate one of the three specified flows below.
Push Tokenization
This flow will be initiated under two conditions: firstly, if the attempt to list tokens from TapAndPay SDK results in a failure with exception code TapAndPayStatusCode.TAP_AND_PAY_NO_ACTIVE_WALLET and secondly, if there is no match between the tokens retrieved from TapAndPay SDK and the tokens retrieved from Paysafe Mobile SDK.
The following sequence diagram outlines the necessary steps for Push Tokenization.
Manual Tokenization
This flow is initiated when listing the tokens affiliated with a particular wallet using tapAndPayClient.listTokens()
.
Upon discovering a token in the TapAndPay.TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION state related to the selected card, manual verification becomes necessary. This additional step is required to authenticate a card for a specific user.
To proceed with manual tokenization, the issuerTokenId
from the token that requires identity verification is used as tokenRefId
for the CardManualTokenizationConfiguration
object.
The following sequence diagram outlines the necessary steps for Manual Tokenization.
Create wallet
This flow is initiated when no wallet has been previously created on the device. It can be triggered either through the Push Tokenization or Manual Tokenization flows, where a call to activeWalletId and stableHardwareId may throw an exception in the absence of a wallet.
The following sequence diagram outlines the necessary steps to add a wallet to a device.