- Get Started
- Guides
- Integrations
- References
- API Reference
- Basic Payment
- Forex
- Authentication
- Card Account
- Apple Pay
- Virtual Account
- Bank Account
- Token Account
- Customer
- Billing Address
- Merchant Billing Address
- Shipping Address
- Merchant Shipping Address
- Merchant
- Corporate
- Recipient
- Marketplace & Cart
- Airline
- Lodging
- Passenger
- Tokenization
- Recurring Migration
- 3D Secure
- Custom Parameters
- Async Payments
- Webhook notifications
- Job
- Risk
- Response Parameters
- Card On File
- Chargeback
- Result Codes
- Transaction Flows
- Regression Testing
- Data Retention Policy
- API Reference
- Support
NOTE: Since version 6.0.0 the asynchronous payments are handled by Mobile SDK. Skip this page if you are using version 6.0.0 or above.
Asynchronous Payments
In an asynchronous workflow a redirection takes place to allow the account holder to complete/verify the payment. After this the account holder is redirected back to the app and the status of the payment can be queried.
This article will guide you how to support communication between apps for the asynchronous payment methods.
NOTE: this article assumes you've already followed our First Integration tutorial.
iOS
Secure browsing
Issuer page will be opened in a secure web view using SFSafariViewController.
Register a custom URL scheme
- In Xcode, click on your project in the Project Navigator and navigate to App Target > Info > URL Types
- Click [+] to add a new URL type
- Under URL Schemes, enter your app switch return URL scheme. This scheme must start with your app's Bundle ID. For example, if the app bundle ID is
com.companyname.appname
, then your URL scheme could becom.companyname.appname.payments
. - Add scheme URL to a whitelist in your app's
Info.plist
:<key>LSApplicationQueriesSchemes</key> <array> <string>com.companyname.appname.payments</string> </array>
NOTE: If you have multiple app targets, be sure to add the return URL type for all of the targets.
Shopper result URL
As soon as asynchronous payment is processed, shopper result URL is opened. You should set it in OPPCheckoutSettings
class to handle payment result.
Sample URL: com.companyname.appname.payments://result
.
The scheme should be the same that you've registered in the previous step. The rest part of URL can be any valid string (but not empty).
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.shopperResultURL = @"com.companyname.appname.payments://result";
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
Do not set shopperResultURL
if it's already sent in the first step (prepare checkout request). It will cause an override error.
Handle URL Request
Handle specific callback in the AppDelegate
or SceneDelegate
depending on deployment target of your app, see the code snippets below. Make sure that URL scheme is identical to the registered one in previous step.
Important: You are responsible to close checkout by calling method dismissCheckoutAnimated:completion:
of OPPCheckoutProvider
class. If you have no access to the OPPCheckoutProvider
instance from the app delegate, you can use broadcast notifications to handle result in the view controller.
AppDelegate
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
if ([url.scheme caseInsensitiveCompare:@"com.companyname.appname.payments"] == NSOrderedSame) {
[checkoutProvider dismissCheckoutAnimated:YES completion:^{
// request payment status
}];
return YES;
} else {
return NO;
}
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
if url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame {
checkoutProvider.dismissCheckout(animated: true) {
// request payment status
}
return true
}
return false
}
iOS 13 and newer,
SceneDelegate
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
NSURL *url = [[URLContexts allObjects] firstObject].URL;
if ([url.scheme caseInsensitiveCompare:@"com.companyname.appname.payments"] == NSOrderedSame) {
[checkoutProvider dismissCheckoutAnimated:YES completion:^{
// request payment status
}];
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
if (url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame) {
checkoutProvider.dismissCheckout(animated: true) {
// request payment status
}
}
}
Handle aborted transactions
mSDK doesn't provide a built-in option to handle an aborted Asynchronus Payment. However there is a way to implement it inside your application.
Follow steps below:
Implement the checkoutProvider:checkoutProviderDidFinishSafariViewController
. This callback is called when the user taps Done button in the SafariViewController
.
- (void)checkoutProviderDidFinishSafariViewController:(nonnull OPPCheckoutProvider *)checkoutProvider {
// Save transaction aborted state here.
}}
func checkoutProviderDidFinishSafariViewController(_ checkoutProvider: OPPCheckoutProvider) {
// Save transaction aborted state here.
}
Follow Handle URL Request topic and check transaction state in dismissCheckoutAnimated:completion:
method.
Testing
You can test your custom URL scheme by opening up a URL that starts with it (e.g. com.companyname.appname.payments://result
) in Mobile Safari on your iOS Device or Simulator.
In addition, always test your app switching on a real device.
Android
Secure browsing
Issuer page will be opened in a secure web view using Chrome Custom Tabs.
Register a custom URL scheme
Define the name of your custom scheme (for example companyname
) and add intent filter to your target activity in the AndroidManifest.xml
<activity
android:name="YOUR_ACTIVITY"
android:launchMode="singleTask">
<intent-filter>
<data android:scheme="companyname"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
NOTE: it is important to use launchMode singleTask for target activity.
Shopper result URL
As soon as asynchronous payment is processed, shopper result URL is opened. You should set it in CheckoutSettings
class to handle payment result.
Sample URL: companyname://result
.
The scheme should be the same that you've registered in the previous step. The rest part of URL can be any valid string (but not empty).
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode)
.setShopperResultUrl("companyname://result");
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode)
.setShopperResultUrl("companyname://result")
Do not set shopperResultUrl
if it's already sent in the first step (prepare checkout request). It will cause an override error.
Handle the redirect intent in target activity
Override onNewIntent
method of your target activity to handle redirect intent and make payment status request.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getScheme().equals("companyname")) {
// request payment status if ActivityResultCallback is received
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.scheme == "companyname") {
// request payment status if ActivityResultCallback is received
}
}
NOTE: Prior Android 10 (API level 29) the
onNewIntent()
will be called before onStart()
and ActivityResultCallback
. Make sure that you handle this case.NOTE: The URL will not be opened for BOLETO payment brand. You can use your Activity callback to detect that the shopper returned to the app, for example
onStart()
.Handle the aborted transactions
mSDK doesn't provide a built-in option to handle an aborted Asynchronus Payment. However there is a way to implement it inside your application.
In order to do that, your application would require to know the transaction's state. Let's create TransactionState
enumeration for example.
enum TransactionState {
NEW,
PENDING,
COMPLETED
}
enum class TransactionState {
NEW,
PENDING,
COMPLETED
}
Mark a new transaction as NEW
and save the value till the end of checkout process.
Handle CheckoutActivityResult
, if the payment was Asynchronus change the transaction state to PENDING
private void handleCheckoutResult(@NonNull CheckoutActivityResult result) {
if (result.isCanceled()) {
// shopper cancelled the checkout process
return;
}
Transaction transaction = result.getTransaction();
if (transaction != null) {
if (transaction.getTransactionType() == TransactionType.SYNC) {
transactionState = TransactionState.COMPLETED;
} else {
// onNewIntent() might be already invoked if activity was destroyed in background,
// make sure you don't overwrite COMPLETED state
if (!transactionState.equals(TransactionState.COMPLETED)) {
transactionState = TransactionState.PENDING;
}
}
}
}
private fun handleCheckoutResult(result: CheckoutActivityResult) {
if (result.isCanceled) {
// shopper cancelled the checkout process
return
}
val transaction: Transaction? = result.transaction
if (transaction != null) {
if (transaction.transactionType === TransactionType.SYNC) {
transactionState = TransactionState.COMPLETED;
} else {
// onNewIntent() might be already invoked if activity was destroyed in background,
// make sure you don't overwrite COMPLETED state
if (!transactionState.equals(TransactionState.COMPLETED)) {
transactionState = TransactionState.PENDING;
}
}
}
}
Override onNewIntent()
method of your target activity to handle redirect intent and change the transaction's state to COMPLETED
.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getScheme().equals("companyname")) {
transactionState = TransactionState.COMPLETED;
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.scheme == "companyname") {
transactionState = TransactionState.COMPLETED;
}
}
Override onResume()
method of your target activity. If the transaction's state isn't COMPLETED
at this point then it was aborted.
@Override
protected void onResume() {
super.onResume();
if (transactionState.equals(TransactionState.PENDING)) {
requestPaymentStatus(resourcePath);
// handle aborted transaction
} else if (transactionState.equals(TransactionState.COMPLETED)) {
requestPaymentStatus(resourcePath);
}
}
override fun onResume(intent: Intent) {
super.onNewIntent(intent)
if (transactionState.equals(TransactionState.PENDING)) {
// handle aborted transaction
} else if (transactionState.equals(TransactionState.COMPLETED)) {
requestPaymentStatus(resourcePath);
}
}