SDK and your own UI
Mobile SDK integration
This guide explains how to integrate a native payment experience into your mobile shop.
Accepting payments in your app involves four steps:
- Prepare checkout (configure amount, currency, and other details).
- Collect shopper payment details.
- Create and submit a transaction.
- Request the payment result.
The iOS and Android SDKs help with steps 2 and 3.
Steps 1 and 4 require communication with your backend API. These steps are not included in SDK API due to security reasons.
Demo app
Use Peach Payments' demo app to start making test transactions without setting up a server. Peach Payments provide a test integration server, configured for demo purposes.
The Mobile SDK from version 2.5.0 includes the demo app.
Before running the app, copy the SDK files to the demo app folder. See
readme.txtfor details.
iOS
Install the Mobile SDK
-
Drag
OPPWAMobile.xcframeworkinto your project'sFrameworksfolder, checking 'Copy items if needed'. -
In your app's target settings under 'Frameworks, Libraries, and Embedded Content', set the Embed drop-down to 'Embed and Sign'.
-
Import the framework:
#import <OPPWAMobile/OPPWAMobile.h>import OPPWAMobile
If you use Swift, see Apple's guide Importing Objective-C into Swift.
In your checkout controller or wherever you handle payments, create the OPPPaymentProvider variable and initialise it in test mode.
@property (nonatomic) OPPPaymentProvider *provider;
- (void)viewDidLoad {
[super viewDidLoad];
self.provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];
}override func viewDidLoad() {
super.viewDidLoad()
let provider = OPPPaymentProvider(mode: .test)
}Set up your server
Expose two APIs on your backend:
- Endpoint 1: Create a checkout ID.
- Endpoint 2: Get the payment result.
See the set up your server guide for details.
Request checkout ID
Your app requests a checkout ID from your server.
NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://YOUR_URL/?amount=100¤cy=EUR&paymentType=DB"]];
[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
self.checkoutID = JSON[@"checkoutId"];
}] resume];let merchantServerRequest = NSURLRequest(url: URL(string: "https://YOUR_URL/?amount=100¤cy=EUR&paymentType=DB")!)
URLSession.shared.dataTask(with: merchantServerRequest as URLRequest) { (data, response, error) in
// Ensure that you handle errors
if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let checkoutID = json?["checkoutId"] as? String
}
}.resume()Collect and validate shopper payment details
Create an OPPPaymentParams object and initialise it with collected shopper payment details.
Use fabric initialisers to create an appropriate subclass of OPPPaymentParams.
NSError *error = nil;
OPPCardPaymentParams *params = [OPPCardPaymentParams cardPaymentParamsWithCheckoutID:self.checkoutID
paymentBrand:@"VISA"
holder:holderName
number:cardNumber
expiryMonth:month
expiryYear:year
CVV:CVV
error:&error];
if (error) {
// See error.code (OPPErrorCode) and error.localizedDescription to identify the reason of failure
}
// Set shopper result URL
params.shopperResultURL = @"com.companyname.appname.payments://result";do {
let params = try OPPCardPaymentParams(checkoutID: checkoutID, paymentBrand: "VISA", holder: holderName, number: cardNumber, expiryMonth: month, expiryYear: year, cvv: CVV)
// Set shopper result URL
params.shopperResultURL = "com.companyname.appname.payments://result"
} catch let error as NSError {
// See error.code (OPPErrorCode) and error.localizedDescription to identify the reason of failure
}See the asynchronous payments guide for details on the shopper result URL.
You can validate each parameter before creating an OPPPaymentParams object. The iOS SDK provides convenience validation methods.
if (![OPPCardPaymentParams isNumberValid:@"4200 0000 0000 0000" luhnCheck:YES]) {
// Handle invalid card number
}if !OPPCardPaymentParams.isNumberValid("4200 0000 0000 0000", luhnCheck: true) {
// display error that card number is invalid
}Brand detection for card payments (optional)
The system can detect the brand from the card number.
From Mobile SDK version 2.25.0, you can create OPPCardPaymentParams and omit the payment brand. Use a special constructor for it or pass an empty string instead of the payment brand in the standard constructor.
[OPPCardPaymentParams cardPaymentParamsWithCheckoutID:self.checkoutID
holder:holderName
number:cardNumber
expiryMonth:month
expiryYear:year
CVV:CVV
error:&error];OPPCardPaymentParams(checkoutID: checkoutID, holder: holderName, number: cardNumber, expiryMonth: month, expiryYear: year, cvv: CVV)Submit a transaction
Create a transaction with the collected valid payment parameters. Then submit it using the OPPPaymentProvider method and handle the callback.
Check transaction type:
- If it is synchronous, request the payment result from your server.
- If it is asynchronous, the transaction includes a redirect URL. Redirect the shopper to this URL of verification (example: 3-D Secure check).
See the asynchronous payments guide for details on handling asynchronous payments.
OPPTransaction *transaction = [OPPTransaction transactionWithPaymentParams:params];
[self.provider submitTransaction:transaction completionHandler:^(OPPTransaction * _Nonnull transaction, NSError * _Nullable error) {
if (transaction.type == OPPTransactionTypeAsynchronous) {
// Open transaction.redirectURL in Safari browser to complete the transaction
} else if (transaction.type == OPPTransactionTypeSynchronous) {
// Send request to your server to obtain transaction status
// Error is no more an decisive factor for transaction termination
if (transaction.resourcePath) {
// Get the payment status using the resourcePath.
}
}
}];let transaction = OPPTransaction(paymentParams: params)
provider.submitTransaction(transaction) { (transaction, error) in
guard let transaction = transaction else {
// Handle invalid transaction, check error
return
}
if transaction.type == .asynchronous {
// Open transaction.redirectURL in Safari browser to complete the transaction
} else if transaction.type == .synchronous {
// Send request to your server to obtain transaction status
// Error is no more an decisive factor for transaction termination
if transaction.resourcePath {
// Get the payment status using the resourcePath.
}
}
}Get the payment status
Your app should request the payment status from your server.
NSString *URL = [NSString stringWithFormat:@"https://YOUR_URL/paymentStatus?resourcePath=%@", [checkoutInfo.resourcePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:URL]];
[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
BOOL status = [result[@"paymentResult"] boolValue];
}] resume];let url= String(format: "https://YOUR_URL/paymentStatus?resourcePath=%@", checkoutInfo.resourcePath!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let merchantServerRequest = NSURLRequest(url: URL(string: url)!)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let transactionStatus = json?["paymentResult"] as? Bool
}
}.resume()To get resourcePath, make another request to the server:
[self.provider requestCheckoutInfoWithCheckoutID:checkoutId completionHandler:^(OPPCheckoutInfo * _Nullable checkoutInfo, NSError * _Nullable error) {
if (error) {
// Handle error
} else {
// Use checkoutInfo.resourcePath for transaction status
}
}];self.provider.requestCheckoutInfo(withCheckoutID: checkoutId, completionHandler: { (checkoutInfo, error) in
guard let resourcePath = checkoutInfo?.resourcePath else {
// Handle error
return
}
// Use resourcePath for getting transaction status
})Testing
The sandbox environment accepts test payment parameters. Find test values in the testing guide.
Go to production
-
Contact support for live credentials.
-
Set the provider mode to live:
self.provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeLive];let provider = OPPPaymentProvider(mode: OPPProviderMode.live) -
Update your backend to use live API endpoints and credentials.
Android
Install the Mobile SDK
-
Import the Mobile SDK for Android. Download
oppwa.mobile.aarand import it into your project:File>New>New Module>Import .JAR/.AAR Package. -
Add the module and required dependencies to your
build.gradle:// Ensure this name matches the library name defined with an include: in your settings.gradle file implementation project(":oppwa.mobile") implementation "androidx.appcompat:appcompat:x.x.x" implementation "com.google.android.material:material:x.x.x" implementation "com.google.android.gms:play-services-base:x.x.x" -
Declare the "INTERNET" permission before the application tag in your
AndroidManifest.xml:<uses-permission android:name="android.permission.INTERNET"/>
Set up your server
To work with the SDK, expose two APIs on your backend for your app to communicate with:
- Endpoint 1: Create a checkout ID.
- Endpoint 2: Get the payment result.
See detailed instructions in the set up your server guide.
Request checkout ID
Your app should request a checkout ID from your server. Adapt this example to use your own backend API.
public String requestCheckoutId() {
URL url;
String urlString;
HttpURLConnection connection = null;
String checkoutId = null;
urlString = YOUR_URL + "?amount=48.99¤cy=EUR&paymentType=DB";
try {
url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
JsonReader reader = new JsonReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"));
reader.beginObject();
while (reader.hasNext()) {
if (reader.nextName().equals("checkoutId")) {
checkoutId = reader.nextString();
break;
}
}
reader.endObject();
reader.close();
} catch (Exception e) {
// Handle error
} finally {
if (connection != null) {
connection.disconnect();
}
}
return checkoutId;
}fun requestCheckoutId(): String? {
val url: URL
var connection: HttpURLConnection? = null
var checkoutId: String? = null
val urlString = YOUR_URL.toString() + "?amount=48.99¤cy=EUR&paymentType=DB"
try {
url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
val reader = JsonReader(InputStreamReader(connection.inputStream, "UTF-8"))
reader.beginObject()
while (reader.hasNext()) {
if (reader.nextName() == "checkoutId") {
checkoutId = reader.nextString()
break
}
}
reader.endObject()
reader.close()
} catch (e: Exception) {
/* error occurred */
} finally {
connection?.disconnect()
}
return checkoutId
}Collect and validate shopper payment details
First, create a PaymentParams object with the collected shopper payment details to process a transaction.
Use fabric initialisers to create an appropriate subclass of PaymentParams. For example, for credit card payment parameters:
PaymentParams paymentParams = new CardPaymentParams(
checkoutId,
brand,
number,
holder,
expiryMonth,
expiryYear,
cvv
);
// Set shopper result URL
paymentParams.setShopperResultUrl("companyname://result");val paymentParams: PaymentParams = CardPaymentParams(
checkoutId,
brand,
number,
holder,
expiryMonth,
expiryYear,
cvv
)
// Set shopper result URL
paymentParams.shopperResultUrl = "companyname://result"To learn more about the shopper result URL, refer to the asynchronous payments guide.
You can also validate each parameter before creating a PaymentParams object. The Android SDK provides convenience validation methods for each payment parameter. For example, to validate a card number:
if (!CardPaymentParams.isNumberValid("4200 0000 0000 0000")) {
// Display error that card number is invalid
}if (!CardPaymentParams.isNumberValid("4200 0000 0000 0000")) {
/* display error that card number is invalid */
}Brand detection for card payments (optional)
The system can automatically detect the brand by analysing the credit card number. Activate this feature in the Smart Payments Platform under: Administration > Processing > Processing Settings > Activate automatic brand detection for missing brands of credit card accounts
Since SDK version 2.25.0, you can create CardPaymentParams without specifying a payment brand. Use the special constructor:
new CardPaymentParams(checkoutId, number, holder, expiryMonth, expiryYear, cvv);CardPaymentParams(checkoutId, number, holder, expiryMonth, expiryYear, cvv)Submit a transaction
Create a transaction with the collected valid payment parameters. Then submit it using the PaymentProvider method and use the callback.
Check the transaction type returned in the callback:
- If it's a synchronous payment, you can request the payment result from your server.
- Otherwise, the transaction contains a redirect URL; redirect the shopper to this URL to pass further checks on the payment provider side (example: 3-D Secure check).
- To support asynchronous payments, you need to make further changes in your app. See the Asynchronous payments tutorial for more details.
- To avoid a
javax.net.ssl.SSLProtocolException: SSL handshake abortedon devices running Android 4.4 (API level 19) and below, call theinstallIfNeeded()method of theProviderInstaller. You can place this call in your first Activity. See more information on Android developer documentation.- The
ServiceConnectionservice is not in use; useOPPPaymentProviderinstead.
Transaction transaction = null;
try {
OppPaymentProvider paymentProvider = new OppPaymentProvider(context, Connnect.ProviderMode.TEST);
transaction = new Transaction(paymentParams);
paymentProvider.submitTransaction(transaction, transactionListener);
} catch (PaymentException ee) {
/* error occurred */
}try {
val paymentProvider = OppPaymentProvider(context, Connnect.ProviderMode.TEST)
val transaction = Transaction(paymentParams)
paymentProvider.submitTransaction(transaction, transactionListener)
} catch (ee: PaymentException) {
/* error occurred */
}Let the class use the ITransactionListener interface. Use the following ITransactionListener methods:
@Override
public void transactionFailed(Transaction transaction, PaymentError paymentError) {
// Add your implementation
}
@Override
public void transactionCompleted(Transaction transaction) {
// Add your implementation
}Use the
getErrorInfo()method of thePaymentErrorto get more details about the error.
Get the payment status
Lastly, your app should request the payment status from your server. Adapt this example to your own setup.
public String requestPaymentStatus() {
URL url;
String urlString;
HttpURLConnection connection = null;
String paymentStatus = null;
urlString = YOUR_URL + "/paymentStatus?resourcePath=" + URLEncoder.encode(RESOURCE_PATH, "UTF-8");
try {
url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
JsonReader jsonReader = new JsonReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"));
jsonReader.beginObject();
while (jsonReader.hasNext()) {
if (jsonReader.nextName().equals("paymentResult")) {
paymentStatus = jsonReader.nextString();
break;
}
}
jsonReader.endObject();
jsonReader.close();
} catch (Exception e) {
// Handle error
} finally {
if (connection != null) {
connection.disconnect();
}
}
return paymentStatus;
}fun requestPaymentStatus(): String? {
val url: URL
var connection: HttpURLConnection? = null
var paymentStatus: String? = null
val urlString = YOUR_URL.toString() + "/paymentStatus?resourcePath=" + URLEncoder.encode(RESOURCE_PATH, "UTF-8")
try {
url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
val jsonReader = JsonReader(InputStreamReader(connection.inputStream, "UTF-8"))
jsonReader.beginObject()
while (jsonReader.hasNext()) {
if (jsonReader.nextName() == "paymentResult") {
paymentStatus = jsonReader.nextString()
break
}
}
jsonReader.endObject()
jsonReader.close()
} catch (e: Exception) {
/* error occurred */
} finally {
connection?.disconnect()
}
return paymentStatus
}To get the resourcePath, make an extra request to the server.
try {
paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener);
} catch (PaymentException e) {
// Handle error
}
@Override
public void paymentConfigRequestSucceeded(CheckoutInfo checkoutInfo) {
// Get the resource path in this ITransactionListener callback
String resourcePath = checkoutInfo.getResourcePath();
}try {
paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener)
} catch (e: PaymentException) {
/* error occurred */
}
override fun paymentConfigRequestSucceeded(checkoutInfo: CheckoutInfo) {
/* get the resource path in this ITransactionListener callback */
val resourcePath = checkoutInfo.resourcePath
}Testing
The sandbox environment accepts test payment parameters. You can find test values for credit cards and bank accounts in the testing guide.
Go to production
-
Contact support to get live credentials.
-
Set the server type to
LIVEwhen initialising thePaymentProvider.paymentProvider.setProviderMode(Connect.ProviderMode.LIVE);paymentProvider.setProviderMode(Connect.ProviderMode.LIVE)Or initialise
OPPPaymentProviderwithProviderMode.LIVE:val paymentProvider = OppPaymentProvider(context, Connnect.ProviderMode.LIVE) -
Update your backend to use the correct API endpoints and credentials.
Asynchronous payments
In an asynchronous workflow, the system redirects the account holder to complete or verify the payment. After this, the system redirects the account holder back to the app, and you can query the payment status.
This section guides you on how to support communication between apps for asynchronous payment methods.
This section assumes you have already followed the first integration tutorial.
iOS
Register a custom URL scheme
- In Xcode, click on your project in the Project Navigator and go 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 the scheme URL to a allowlist in your app's
Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>com.companyname.appname.payments</string>
</array>If you have multiple app targets, add the return URL type for all targets.
Shopper result URL
As soon as the system processes an asynchronous payment, the shopper result URL opens. Set it in the OPPPaymentParams class to handle the payment result.
Sample URL: com.companyname.appname.payments://result
The scheme should match the one registered in the previous step. The rest of the URL can be any valid string (but not empty).
OPPPaymentParams *paymentParams = ...; // init payment params
paymentParams.shopperResultURL = @"com.companyname.appname.payments://result";let paymentParams = ... // init payment params
paymentParams.shopperResultURL = "com.companyname.appname.payments://result"Do not set
shopperResultURLif you already sent it in the first step (prepare checkout request). Doing so causes an override error.
Update your application delegate
To finish this step, handle a specific callback in AppDelegate or SceneDelegate, depending on your app's deployment target. Ensure the URL scheme is the same to the one registered earlier. Then, send a notification to request the payment status and display the result to the customer.
For iOS 13 and newer, use 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:^{
// send notification to get 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) {
// send notification to get payment status
}
}
}For iOS 10-12, AppDelegate:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
if ([url.scheme caseInsensitiveCompare:@"com.companyname.appname.payments"] == NSOrderedSame) {
[checkoutProvider dismissCheckoutAnimated:YES completion:^{
// send notification to get 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) {
// send notification to get payment status
}
return true
}
return false
}Handle aborted transactions
Mobile SDK does not provide a built-in option to handle an aborted asynchronous payment. However, you can use it inside your application.
Follow these steps:
-
Use
checkoutProvider:checkoutProviderDidFinishSafariViewController. This callback triggers when the user taps the Done button inSafariViewController.- (void)checkoutProviderDidFinishSafariViewController:(nonnull OPPCheckoutProvider *)checkoutProvider { // Save transaction aborted state here. }func checkoutProviderDidFinishSafariViewController(_ checkoutProvider: OPPCheckoutProvider) { // Save transaction aborted state here. } -
Follow the Handle URL request topic and check the transaction state in the
dismissCheckoutAnimated:completion:method.
Testing
You can test your custom URL scheme by opening a URL that starts with it (example for, com.companyname.appname.payments://test) in Mobile Safari on your iOS device or simulator.
In addition, always test app switching on a real device.
Android
Register a custom URL scheme
Define the name of your custom scheme (for example, companyname) and add an intent filter to your target activity in 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>Use
launchMode="singleTask"for the target activity.
Shopper result URL
When an asynchronous payment processes, the shopper result URL opens. Set it in PaymentParams to handle the payment result.
Sample URL: companyname://result
The scheme should match the one registered in the previous step (prepare checkout request). The rest of the URL can be any valid string (but not empty).
PaymentParams paymentParams = new PaymentParams(...) // init payment params
.setShopperResultUrl("companyname://result");val paymentParams = PaymentParams(...) // init payment params
.shopperResultUrl("companyname://result")Do not set shopperResultUrl if it is already sent in the first step (prepare checkout request). Doing so causes an override error.
Handle the redirect intent in target activity
Override onNewIntent in your target activity to handle the redirect intent and make a payment status request.
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.getScheme().equals("companyname")) {
/* request payment status */
}
}override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.scheme == "companyname") {
/* request payment status */
}
}The URL does not open for the BOLETO payment brand. Use the activity callback (for example,
onStart()) to detect when the shopper returns to the app.
Handle aborted transactions
Mobile SDK does not provide a built-in option to handle an aborted asynchronous payment. However, your application can handle it by tracking the transaction's state.
Create a TransactionState enumeration:
enum TransactionState {
NEW,
PENDING,
COMPLETED
}enum class TransactionState {
NEW,
PENDING,
COMPLETED
}Mark a new transaction as NEW and keep this value until checkout completes.
Override onActivityResult() to track when the checkout process finishes. If the payment was asynchronous, change the transaction state to PENDING.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CheckoutActivity.REQUEST_CODE_CHECKOUT) {
switch (resultCode) {
case CheckoutActivity.RESULT_OK:
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;
}
}
break;
case CheckoutActivity.RESULT_CANCELED:
/* handle aborted transaction */
break;
case CheckoutActivity.RESULT_ERROR:
/* handle error */
}
}
}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CheckoutActivity.REQUEST_CODE_CHECKOUT) {
when (resultCode) {
CheckoutActivity.RESULT_OK -> {
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
}
}
}
CheckoutActivity.RESULT_CANCELED -> /* handle aborted transaction */
CheckoutActivity.RESULT_ERROR -> /* handle error */
}
}
}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() in the target activity. If the transaction state is not COMPLETED at this point, the system aborted it.
@Override
protected void onResume() {
super.onResume();
if (transactionState.equals(TransactionState.PENDING)) {
/* 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);
}
}Tokenisation
Tokenisation replaces sensitive payment data, such as a credit card number, with a unique identifier or, token. After you get a token for card data or a bank account, you can charge transactions on these tokens without storing the payment data.
Supported payment methods for storage
Card details
You can tokenise the following card details:
- Cardholder
- Card number
- Expiry date
You cannot store the CVV with a token. If needed, prompt the shopper to re-enter the CVV code for each transaction.
Bank accounts
The parameter, DIRECTDEBIT_SEPA, supports tokenisation. You can store the following details:
- Bank account holder
- IBAN
Virtual accounts
PAYPAL tokens work for payments from version 2.66.0.
iOS
Storing payment data
The SDK provides two options for storing payment data:
- Store data during a payment: When a shopper checks out for the first time, they must enter their payment details. Use this option to store their data automatically during the payment for later transactions.
- Store data as stand-alone: If your site provides an area where shoppers can register payment details without checking out, use this option.
Store data during a payment
-
The two ways to do this are:
-
Merchant-determined tokenisation
During checkout, store data by adding parameters to the prepare checkout request. See the COPYandPAY merchant-determined tokenisation section for details.
-
Shopper-determined tokenisation
Extend the payment form to display an option allowing shoppers to store payment details.
Set the
storePaymentDetailsModeof theOPPCheckoutSettingsto one of the following:OPPCheckoutStorePaymentDetailsModeNever: The system does not save payment details by default.OPPCheckoutStorePaymentDetailsModePrompt: The shopper confirms whether to save payment information for future purchases; the payment form includes a switch.OPPCheckoutStorePaymentDetailsModeAlways: The system saves the shopper's payment details for each new payment.
OPPCheckoutSettings *settings = [[OPPCheckoutSettings alloc] init]; settings.storePaymentDetails = OPPCheckoutStorePaymentDetailsModePrompt; …let settings = OPPCheckoutSettings() settings.storePaymentDetails = .prompt …
-
-
Receive a token with payment status
The server generates a token for the payment details and returns it with the payment status.
- The
registrationIdis the generated token. - Your server handles and stores the token.
Example payment status response:
{ "registrationId":"8a82944a580a782101581f3a0b4b5ab9", "result":{ "code":"000.100.110", "description":"Request successfully processed in 'Merchant in Integrator Test Mode'" }, // ... } - The
Store data as stand-alone
Create a registration without linking it to a payment. A registration transaction follows the same workflow as a payment, but uses a different endpoint.
-
Prepare checkout
Change these parameters in step 1:
-
Set
createRegistration=true. -
Do not send
paymentType.curl https://sandbox-card.peachpayments.com/v1/checkouts \ -d "entityId=8a8294174e735d0c014e78cf26461790" \ -d "notificationUrl=http://www.example.com/notify" \ -d "createRegistration=true" \ -H "Authorization: Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="public Dictionary<string, dynamic> Request() { Dictionary<string, dynamic> responseData; string data="entityId=8a8294174e735d0c014e78cf26461790" + "¬ificationUrl=http://www.example.com/notify" + "&createRegistration=true"; string url = "https://sandbox-card.peachpayments.com/v1/checkouts"; byte[] buffer = Encoding.ASCII.GetBytes(data); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); request.Method = "POST"; request.Headers["Authorization"] = "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="; request.ContentType = "application/x-www-form-urlencoded"; Stream PostData = request.GetRequestStream(); PostData.Write(buffer, 0, buffer.Length); PostData.Close(); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream dataStream = response.GetResponseStream(); StreamReader reader = new StreamReader(dataStream); var s = new JavaScriptSerializer(); responseData = s.Deserialize<Dictionary<string, dynamic>>(reader.ReadToEnd()); reader.Close(); dataStream.Close(); } return responseData; } responseData = Request()["result"]["description"];import groovy.json.JsonSlurper public static String request() { def data = "entityId=8a8294174e735d0c014e78cf26461790" + "¬ificationUrl=http://www.example.com/notify" + "&createRegistration=true" def url = "https://sandbox-card.peachpayments.com/v1/checkouts".toURL() def connection = url.openConnection() connection.setRequestMethod("POST") connection.setRequestProperty("Authorization","Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") connection.doOutput = true connection.outputStream << data def json = new JsonSlurper().parseText(connection.inputStream.text) json } println request()private String request() throws IOException { URL url = new URL("https://sandbox-card.peachpayments.com/v1/checkouts"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="); conn.setDoInput(true); conn.setDoOutput(true); String data = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "¬ificationUrl=http://www.example.com/notify" + "&createRegistration=true"; DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); wr.writeBytes(data); wr.flush(); wr.close(); int responseCode = conn.getResponseCode(); InputStream is; if (responseCode >= 400) is = conn.getErrorStream(); else is = conn.getInputStream(); return IOUtils.toString(is); }const https = require('https'); const querystring = require('querystring'); const request = async () => { const path='/v1/checkouts'; const data = querystring.stringify({ 'entityId':'8a8294174e735d0c014e78cf26461790', 'notificationUrl':'http://www.example.com/notify', 'createRegistration':'true' }); const options = { port: 443, host: 'sandbox-card.peachpayments.com', path: path, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': data.length, 'Authorization':'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=' } }; return new Promise((resolve, reject) => { const postRequest = https.request(options, function(res) { const buf = []; res.on('data', chunk => { buf.push(Buffer.from(chunk)); }); res.on('end', () => { const jsonString = Buffer.concat(buf).toString('utf8'); try { resolve(JSON.parse(jsonString)); } catch (error) { reject(error); } }); }); postRequest.on('error', reject); postRequest.write(data); postRequest.end(); }); }; request().then(console.log).catch(console.error);function request() { $url = "https://sandbox-card.peachpayments.com/v1/checkouts"; $data = "entityId=8a8294174e735d0c014e78cf26461790" . "¬ificationUrl=http://www.example.com/notify" . "&createRegistration=true"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Authorization:Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=')); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// this should be set to true in production curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $responseData = curl_exec($ch); if(curl_errno($ch)) { return curl_error($ch); } curl_close($ch); return $responseData; } $responseData = request();try: from urllib.parse import urlencode from urllib.request import build_opener, Request, HTTPHandler from urllib.error import HTTPError, URLError except ImportError: from urllib import urlencode from urllib2 import build_opener, Request, HTTPHandler, HTTPError, URLError import json def request(): url = "https://sandbox-card.peachpayments.com/v1/checkouts" data = { 'entityId' : '8a8294174e735d0c014e78cf26461790', 'notificationUrl' : 'http://www.example.com/notify', 'createRegistration' : 'true' } try: opener = build_opener(HTTPHandler) request = Request(url, data=urlencode(data).encode('utf-8')) request.add_header('Authorization', 'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=') request.get_method = lambda: 'POST' response = opener.open(request) return json.loads(response.read()) except HTTPError as e: return json.loads(e.read()) except URLError as e: return e.reason responseData = request() print(responseData)require 'net/https' require 'uri' require 'json' def request() uri = URI('https://sandbox-card.peachpayments.com/v1/checkouts') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri.path) req.set_form_data({ 'entityId' => '8a8294174e735d0c014e78cf26461790', 'notificationUrl' => 'http://www.example.com/notify', 'createRegistration' => 'true' }) res = http.request(req) return JSON.parse(res.body) end puts request()def initialPayment : String = { val url = "https://sandbox-card.peachpayments.com/v1/checkouts" val data = ("" + "entityId=8a8294174e735d0c014e78cf26461790" + "¬ificationUrl=http://www.example.com/notify" + "&createRegistration=true" ) val conn = new URL(url).openConnection() conn match { case secureConn: HttpsURLConnection => secureConn.setRequestMethod("POST") case _ => throw new ClassCastException } conn.setDoInput(true) conn.setDoOutput(true) IOUtils.write(data, conn.getOutputStream()) conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") conn.connect() if (conn.getResponseCode() >= 400) { return IOUtils.toString(conn.getErrorStream()) } else { return IOUtils.toString(conn.getInputStream()) } }Public Function Request() As Dictionary(Of String, Object) Dim url As String = "https://sandbox-card.peachpayments.com/v1/checkouts" Dim data As String = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "¬ificationUrl=http://www.example.com/notify" + "&createRegistration=true" Dim req As WebRequest = WebRequest.Create(url) req.Method = "POST" req.Headers.Add("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") req.ContentType = "application/x-www-form-urlencoded" Dim byteArray As Byte() = Encoding.UTF8.GetBytes(data) req.ContentLength = byteArray.Length Dim dataStream As Stream = req.GetRequestStream() dataStream.Write(byteArray, 0, byteArray.Length) dataStream.Close() Dim res As WebResponse = req.GetResponse() Dim resStream = res.GetResponseStream() Dim reader As New StreamReader(resStream) Dim response As String = reader.ReadToEnd() reader.Close() resStream.Close() res.Close() Dim jss As New System.Web.Script.Serialization.JavaScriptSerializer() Dim dict As Dictionary(Of String, Object) = jss.Deserialize(Of Dictionary(Of String, Object))(response) Return dict End Function responseData = Request()("result")("description")The Mobile SDK adapts the workflow to handle registration.
-
-
Receive a token with registration status: After sending the transaction, you receive a
resourcePath=/v1/checkouts/{checkoutId}/registrationto query the registration result. Request the transaction status using this resource path.The resource path for registration-only transactions differs from the path for obtaining payment status.
Using payment data
-
Prepare checkout with tokens
Add the token parameter to the checkout request. Your server must send shopper tokens with other data such as amount, currency, and order type.
You must send tokens in the
registrations[n].idparameter, wherenis a sequence number starting from zero. -
Stored payment methods UI
Before presenting payment pages, the Mobile SDK automatically fetches tokens related to the
checkoutIdfrom the server and displays the pre-filled payment methods to the shopper.You cannot store the CVV with a token. Prompt the shopper to enter it for each transaction.
-
Skipping 3-D Secure for stored cards
To skip 3-D Secure for payments with stored cards, send one additional parameter during preparing the checkout:
recurringType=REGISTRATION_BASEDcurl https://sandbox-card.peachpayments.com/v1/checkouts \ -d "entityId=8a8294174e735d0c014e78cf26461790" \ -d "amount=92.00" \ -d "currency=EUR" \ -d "paymentType=DB" \ -d "notificationUrl=http://www.example.com/notify" \ -d "recurringType=REGISTRATION_BASED" \ -H "Authorization: Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="public Dictionary<string, dynamic> Request() { Dictionary<string, dynamic> responseData; string data="entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED"; string url = "https://sandbox-card.peachpayments.com/v1/checkouts"; byte[] buffer = Encoding.ASCII.GetBytes(data); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); request.Method = "POST"; request.Headers["Authorization"] = "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="; request.ContentType = "application/x-www-form-urlencoded"; Stream PostData = request.GetRequestStream(); PostData.Write(buffer, 0, buffer.Length); PostData.Close(); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream dataStream = response.GetResponseStream(); StreamReader reader = new StreamReader(dataStream); var s = new JavaScriptSerializer(); responseData = s.Deserialize<Dictionary<string, dynamic>>(reader.ReadToEnd()); reader.Close(); dataStream.Close(); } return responseData; } responseData = Request()["result"]["description"];import groovy.json.JsonSlurper public static String request() { def data = "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" def url = "https://sandbox-card.peachpayments.com/v1/checkouts".toURL() def connection = url.openConnection() connection.setRequestMethod("POST") connection.setRequestProperty("Authorization","Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") connection.doOutput = true connection.outputStream << data def json = new JsonSlurper().parseText(connection.inputStream.text) json } println request()private String request() throws IOException { URL url = new URL("https://sandbox-card.peachpayments.com/v1/checkouts"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="); conn.setDoInput(true); conn.setDoOutput(true); String data = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED"; DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); wr.writeBytes(data); wr.flush(); wr.close(); int responseCode = conn.getResponseCode(); InputStream is; if (responseCode >= 400) is = conn.getErrorStream(); else is = conn.getInputStream(); return IOUtils.toString(is); }const https = require('https'); const querystring = require('querystring'); const request = async () => { const path='/v1/checkouts'; const data = querystring.stringify({ 'entityId':'8a8294174e735d0c014e78cf26461790', 'amount':'92.00', 'currency':'EUR', 'paymentType':'DB', 'notificationUrl':'http://www.example.com/notify', 'recurringType':'REGISTRATION_BASED' }); const options = { port: 443, host: 'sandbox-card.peachpayments.com', path: path, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': data.length, 'Authorization':'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=' } }; return new Promise((resolve, reject) => { const postRequest = https.request(options, function(res) { const buf = []; res.on('data', chunk => { buf.push(Buffer.from(chunk)); }); res.on('end', () => { const jsonString = Buffer.concat(buf).toString('utf8'); try { resolve(JSON.parse(jsonString)); } catch (error) { reject(error); } }); }); postRequest.on('error', reject); postRequest.write(data); postRequest.end(); }); }; request().then(console.log).catch(console.error);function request() { $url = "https://sandbox-card.peachpayments.com/v1/checkouts"; $data = "entityId=8a8294174e735d0c014e78cf26461790" . "&amount=92.00" . "¤cy=EUR" . "&paymentType=DB" . "¬ificationUrl=http://www.example.com/notify" . "&recurringType=REGISTRATION_BASED"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Authorization:Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=')); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// this should be set to true in production curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $responseData = curl_exec($ch); if(curl_errno($ch)) { return curl_error($ch); } curl_close($ch); return $responseData; } $responseData = request();try: from urllib.parse import urlencode from urllib.request import build_opener, Request, HTTPHandler from urllib.error import HTTPError, URLError except ImportError: from urllib import urlencode from urllib2 import build_opener, Request, HTTPHandler, HTTPError, URLError import json def request(): url = "https://sandbox-card.peachpayments.com/v1/checkouts" data = { 'entityId' : '8a8294174e735d0c014e78cf26461790', 'amount' : '92.00', 'currency' : 'EUR', 'paymentType' : 'DB', 'notificationUrl' : 'http://www.example.com/notify', 'recurringType' : 'REGISTRATION_BASED' } try: opener = build_opener(HTTPHandler) request = Request(url, data=urlencode(data).encode('utf-8')) request.add_header('Authorization', 'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=') request.get_method = lambda: 'POST' response = opener.open(request) return json.loads(response.read()); except HTTPError as e: return json.loads(e.read()); except URLError as e: return e.reason; responseData = request(); print(responseData);require 'net/https' require 'uri' require 'json' def request() uri = URI('https://sandbox-card.peachpayments.com/v1/checkouts') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri.path) req.set_form_data({ 'entityId' => '8a8294174e735d0c014e78cf26461790', 'amount' => '92.00', 'currency' => 'EUR', 'paymentType' => 'DB', 'notificationUrl' => 'http://www.example.com/notify', 'recurringType' => 'REGISTRATION_BASED' }) res = http.request(req) return JSON.parse(res.body) end puts request()def initialPayment : String = { val url = "https://sandbox-card.peachpayments.com/v1/checkouts" val data = ("" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" ) val conn = new URL(url).openConnection() conn match { case secureConn: HttpsURLConnection => secureConn.setRequestMethod("POST") case _ => throw new ClassCastException } conn.setDoInput(true) conn.setDoOutput(true) IOUtils.write(data, conn.getOutputStream()) conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") conn.connect() if (conn.getResponseCode() >= 400) { return IOUtils.toString(conn.getErrorStream()) } else { return IOUtils.toString(conn.getInputStream()) } }Public Function Request() As Dictionary(Of String, Object) Dim url As String = "https://sandbox-card.peachpayments.com/v1/checkouts" Dim data As String = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" Dim req As WebRequest = WebRequest.Create(url) req.Method = "POST" req.Headers.Add("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") req.ContentType = "application/x-www-form-urlencoded" Dim byteArray As Byte() = Encoding.UTF8.GetBytes(data) req.ContentLength = byteArray.Length Dim dataStream As Stream = req.GetRequestStream() dataStream.Write(byteArray, 0, byteArray.Length) dataStream.Close() Dim res As WebResponse = req.GetResponse() Dim resStream = res.GetResponseStream() Dim reader As New StreamReader(resStream) Dim response As String = reader.ReadToEnd() reader.Close() resStream.Close() res.Close() Dim jss As New System.Web.Script.Serialization.JavaScriptSerializer() Dim dict As Dictionary(Of String, Object) = jss.Deserialize(Of Dictionary(Of String, Object))(response) Return dict End Function responseData = Request()("result")("description"){ "result":{ "code":"000.200.100", "description":"successfully created checkout" }, "buildNumber":"9092e7a6af8301accda2f9a3a38f743f907dadd5@2026-03-23 16:50:06 +0000", "timestamp":"2026-03-26 15:11:25+0000", "ndc":"B5B729CE793B8B0018D5329F70571EC7.uat01-vm-tx01", "id":"B5B729CE793B8B0018D5329F70571EC7.uat01-vm-tx01" }
Android
Storing payment data
SDK provides two options for storing payment data:
- Store data during a payment: When a shopper checks out for the first time, they must fill in complete payment data. Use this option to store data during payment for reuse in later transactions.
- Store data as stand-alone: If your site provides shoppers with an administrative area where they can register payment details independent of checkout, use this option.
Store data during a payment
-
The two options are:
-
Merchant-determined tokenisation
During checkout, store data by adding parameters to the prepare checkout request. See the COPYandPAY merchant-determined tokenisation section for details.
-
Shopper-determined tokenisation
Extend the payment form to display an option allowing shoppers to store payment details.
Set the
storePaymentDetailsof theCheckoutStorePaymentDetailsModeto one of the following:CheckoutStorePaymentDetailsMode.NEVER: The system does not save payment details by default.CheckoutStorePaymentDetailsMode.PROMPT: The shopper confirms whether to save payment information for future purchases; the payment form includes a switch.CheckoutStorePaymentDetailsMode.ALWAYS: The system saves the shopper's payment details for each new payment.
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, providerMode); checkoutSettings.setStorePaymentDetailsMode(CheckoutStorePaymentDetailsMode.PROMPT);val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode) checkoutSettings.storePaymentDetailsMode = CheckoutStorePaymentDetailsMode.PROMPT
-
-
Receive a token with payment status
The server generates a token for the payment details and returns it with the payment status.
The
registrationIdis the generated token. Your server must handle and store the token.Example payment status response:
{ "registrationId":"8a82944a580a782101581f3a0b4b5ab9", "result":{ "code":"000.100.110", "description":"Request successfully processed in 'Merchant in Integrator Test Mode'" }, // ... }
Store data as stand-alone
A registration transaction follows the same workflow and parameters as a payment. Update the checkout request and use another endpoint when submitting the transaction.
- Prepare checkout and change these parameters:
- The
createRegistration=trueparameter must be sent - Do not send
paymentType
-
Create and register a transaction
Create a transaction with valid payment parameters. Then register it using
OPPPaymentProviderand use the callback.Transaction transaction = null; try { transaction = new Transaction(paymentParams); paymentProvider.registerTransaction(transaction, transactionListener); } catch (PaymentException ee) { /* error occurred */ } @Override public void transactionCompleted(@NonNull Transaction transaction) { }try { val transaction = Transaction(paymentParams) paymentProvider.registerTransaction(transaction, transactionListener) } catch (ee: PaymentException) { /* error occurred */ } override fun transactionCompleted(transaction: Transaction) { }
Receive a token with registration status
After sending the transaction, you receive a resourcePath=/v1/checkouts/{checkoutId}/registration to query the registration result. Request the transaction status using this resource path.
The resource path for registration-only transactions differs from the path for obtaining payment status.
Parameter ID is your generated token. See an example of the registration status response below:
{
"id":"8a82944a580a782101581f3a0b4b5ab9",
"card":{
"bin":"420000",
"last4Digits":"0000",
"holder":"Jane Jones",
"expiryMonth":"05",
"expiryYear":"2018"
},
"result":{
"code":"000.100.110",
"description":"Request successfully processed in 'Merchant in Integrator Test Mode'"
},
// ...
}Using payment data
-
Prepare checkout with tokens
Add the token parameter to the prepare checkout request. Your server sends shoppers' tokens along with configuration data such as amount, currency, and order type. Send tokens in the
registrations[n].idparameter, wherenstarts from zero and increments for each token.Example:
registrations[0].id = {first tokenID}registrations[1].id = {second tokenID}
-
Submit a transaction with token
Charging a token is similar to working with cards. Create
TokenPaymentParamsinstead ofCardPaymentParams.TokenPaymentParams paymentParams = new TokenPaymentParams(checkoutID, tokenID, brand); /* create and submit a transaction */val paymentParams = TokenPaymentParams(checkoutID, tokenID, brand) /* create and submit a transaction */Then create and submit a transaction with token payment parameters as usual.
CVV cannot be stored with a token, you can prompt the shopper to re-enter the CVV code for subsequent transactions, if required. CVV should be provided to
TokenPaymentParamsbefore submitting the transaction. -
Getting the payment data by token ID
After checkout is created with customer tokens, you can request the stored payment data along with checkout information using the SDK API.
try { paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener); } catch (PaymentException e) { /* error occurred */ } @Override public void paymentConfigRequestSucceeded(CheckoutInfo checkoutInfo) { /* get the tokens */ Token[] tokens = checkoutInfo.getTokens(); }try { paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener) } catch (e: PaymentException) { /* error occurred */ } override fun paymentConfigRequestSucceeded(checkoutInfo: CheckoutInfo) { /* get the tokens */ val tokens = checkoutInfo.tokens } -
Skipping 3-D Secure for stored cards
To skip 3-D Secure for payments with stored cards, send one additional parameter during preparing the checkout:
recurringType=REGISTRATION_BASEDcurl https://sandbox-card.peachpayments.com/v1/checkouts \ -d "entityId=8a8294174e735d0c014e78cf26461790" \ -d "amount=92.00" \ -d "currency=EUR" \ -d "paymentType=DB" \ -d "notificationUrl=http://www.example.com/notify" \ -d "recurringType=REGISTRATION_BASED" \ -H "Authorization: Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="public Dictionary<string, dynamic> Request() { Dictionary<string, dynamic> responseData; string data="entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED"; string url = "https://sandbox-card.peachpayments.com/v1/checkouts"; byte[] buffer = Encoding.ASCII.GetBytes(data); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); request.Method = "POST"; request.Headers["Authorization"] = "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="; request.ContentType = "application/x-www-form-urlencoded"; Stream PostData = request.GetRequestStream(); PostData.Write(buffer, 0, buffer.Length); PostData.Close(); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream dataStream = response.GetResponseStream(); StreamReader reader = new StreamReader(dataStream); var s = new JavaScriptSerializer(); responseData = s.Deserialize<Dictionary<string, dynamic>>(reader.ReadToEnd()); reader.Close(); dataStream.Close(); } return responseData; } responseData = Request()["result"]["description"];import groovy.json.JsonSlurper public static String request() { def data = "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" def url = "https://sandbox-card.peachpayments.com/v1/checkouts".toURL() def connection = url.openConnection() connection.setRequestMethod("POST") connection.setRequestProperty("Authorization","Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") connection.doOutput = true connection.outputStream << data def json = new JsonSlurper().parseText(connection.inputStream.text) json } println request()private String request() throws IOException { URL url = new URL("https://sandbox-card.peachpayments.com/v1/checkouts"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ="); conn.setDoInput(true); conn.setDoOutput(true); String data = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED"; DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); wr.writeBytes(data); wr.flush(); wr.close(); int responseCode = conn.getResponseCode(); InputStream is; if (responseCode >= 400) is = conn.getErrorStream(); else is = conn.getInputStream(); return IOUtils.toString(is); }const https = require('https'); const querystring = require('querystring'); const request = async () => { const path='/v1/checkouts'; const data = querystring.stringify({ 'entityId':'8a8294174e735d0c014e78cf26461790', 'amount':'92.00', 'currency':'EUR', 'paymentType':'DB', 'notificationUrl':'http://www.example.com/notify', 'recurringType':'REGISTRATION_BASED' }); const options = { port: 443, host: 'sandbox-card.peachpayments.com', path: path, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': data.length, 'Authorization':'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=' } }; return new Promise((resolve, reject) => { const postRequest = https.request(options, function(res) { const buf = []; res.on('data', chunk => { buf.push(Buffer.from(chunk)); }); res.on('end', () => { const jsonString = Buffer.concat(buf).toString('utf8'); try { resolve(JSON.parse(jsonString)); } catch (error) { reject(error); } }); }); postRequest.on('error', reject); postRequest.write(data); postRequest.end(); }); }; request().then(console.log).catch(console.error);function request() { $url = "https://sandbox-card.peachpayments.com/v1/checkouts"; $data = "entityId=8a8294174e735d0c014e78cf26461790" . "&amount=92.00" . "¤cy=EUR" . "&paymentType=DB" . "¬ificationUrl=http://www.example.com/notify" . "&recurringType=REGISTRATION_BASED"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Authorization:Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=')); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// this should be set to true in production curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $responseData = curl_exec($ch); if(curl_errno($ch)) { return curl_error($ch); } curl_close($ch); return $responseData; } $responseData = request();try: from urllib.parse import urlencode from urllib.request import build_opener, Request, HTTPHandler from urllib.error import HTTPError, URLError except ImportError: from urllib import urlencode from urllib2 import build_opener, Request, HTTPHandler, HTTPError, URLError import json def request(): url = "https://sandbox-card.peachpayments.com/v1/checkouts" data = { 'entityId' : '8a8294174e735d0c014e78cf26461790', 'amount' : '92.00', 'currency' : 'EUR', 'paymentType' : 'DB', 'notificationUrl' : 'http://www.example.com/notify', 'recurringType' : 'REGISTRATION_BASED' } try: opener = build_opener(HTTPHandler) request = Request(url, data=urlencode(data).encode('utf-8')) request.add_header('Authorization', 'Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=') request.get_method = lambda: 'POST' response = opener.open(request) return json.loads(response.read()); except HTTPError as e: return json.loads(e.read()); except URLError as e: return e.reason; responseData = request(); print(responseData);require 'net/https' require 'uri' require 'json' def request() uri = URI('https://sandbox-card.peachpayments.com/v1/checkouts') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri.path) req.set_form_data({ 'entityId' => '8a8294174e735d0c014e78cf26461790', 'amount' => '92.00', 'currency' => 'EUR', 'paymentType' => 'DB', 'notificationUrl' => 'http://www.example.com/notify', 'recurringType' => 'REGISTRATION_BASED' }) res = http.request(req) return JSON.parse(res.body) end puts request()def initialPayment : String = { val url = "https://sandbox-card.peachpayments.com/v1/checkouts" val data = ("" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" ) val conn = new URL(url).openConnection() conn match { case secureConn: HttpsURLConnection => secureConn.setRequestMethod("POST") case _ => throw new ClassCastException } conn.setDoInput(true) conn.setDoOutput(true) IOUtils.write(data, conn.getOutputStream()) conn.setRequestProperty("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") conn.connect() if (conn.getResponseCode() >= 400) { return IOUtils.toString(conn.getErrorStream()) } else { return IOUtils.toString(conn.getInputStream()) } }Public Function Request() As Dictionary(Of String, Object) Dim url As String = "https://sandbox-card.peachpayments.com/v1/checkouts" Dim data As String = "" + "entityId=8a8294174e735d0c014e78cf26461790" + "&amount=92.00" + "¤cy=EUR" + "&paymentType=DB" + "¬ificationUrl=http://www.example.com/notify" + "&recurringType=REGISTRATION_BASED" Dim req As WebRequest = WebRequest.Create(url) req.Method = "POST" req.Headers.Add("Authorization", "Bearer OGE4Mjk0MTc0ZTczNWQwYzAxNGU3OGNmMjY2YjE3OTR8SFV3I3JGQTQ9bWpxaWYrPz9OWVQ=") req.ContentType = "application/x-www-form-urlencoded" Dim byteArray As Byte() = Encoding.UTF8.GetBytes(data) req.ContentLength = byteArray.Length Dim dataStream As Stream = req.GetRequestStream() dataStream.Write(byteArray, 0, byteArray.Length) dataStream.Close() Dim res As WebResponse = req.GetResponse() Dim resStream = res.GetResponseStream() Dim reader As New StreamReader(resStream) Dim response As String = reader.ReadToEnd() reader.Close() resStream.Close() res.Close() Dim jss As New System.Web.Script.Serialization.JavaScriptSerializer() Dim dict As Dictionary(Of String, Object) = jss.Deserialize(Of Dictionary(Of String, Object))(response) Return dict End Function responseData = Request()("result")("description"){ "result":{ "code":"000.200.100", "description":"successfully created checkout" }, "buildNumber":"9092e7a6af8301accda2f9a3a38f743f907dadd5@2026-03-23 16:50:06 +0000", "timestamp":"2026-03-26 15:11:25+0000", "ndc":"B5B729CE793B8B0018D5329F70571EC7.uat01-vm-tx01", "id":"B5B729CE793B8B0018D5329F70571EC7.uat01-vm-tx01" }
BIN detection
Starting from version 3.5.0, you can use the BIN detection feature for your own UI.
iOS
Follow these steps to get payment brands for a provided card BIN:
- Request a checkout ID.
- Request checkout information.
- Request brands for the provided BIN.
Request checkout ID
Your app must request a checkout ID from your server. This example uses a sample integration server. Adapt it to use your own backend API.
NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://YOUR_URL/?amount=100¤cy=EUR&paymentType=DB"]];
[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// Handle error
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
self.checkoutID = JSON[@"checkoutId"];
}] resume];
```swift
let merchantServerRequest = NSURLRequest(url: URL(string: "https://YOUR_URL/?amount=100¤cy=EUR&paymentType=DB")!)
URLSession.shared.dataTask(with: merchantServerRequest as URLRequest) { (data, response, error) in
// Handle error
if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let checkoutID = json?["checkoutId"] as? String
}
}.resume()Create an instance of OPPPaymentProvider:
self.provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];self.provider = OPPPaymentProvider(mode: .test)Request checkout information
Use the checkoutId from the previous step to make a checkout information request:
[self.provider requestCheckoutInfoWithCheckoutID:checkoutId completionHandler:^(OPPCheckoutInfo * _Nullable checkoutInfo, NSError * _Nullable error) {
if (error) {
// Handle error
} else {
// Make bin detection call
}
}];self.provider.requestCheckoutInfo(withCheckoutID: checkoutId, completionHandler: { (checkoutInfo, error) in
if error != nil {
// Handle error
return
}
// Make Bin detection call
})Request BIN detection service
[self.provider requestPaymentBrandsForBin:bin checkoutID:checkoutID completionHandler:^(NSArray * _Nullable paymentBrands, NSError * _Nullable error) {
if (error) {
// Handle error
} else {
// Handle detected brands list
}
}];self.provider.requestPaymentBrands(forBin: bin, checkoutID: checkoutID!, completionHandler: { (brands, error) in
if error != nil {
// Handle error
return
}
// Handle detected brands list
}) Final notes
- BIN detection requests are asynchronous.
- You can make multiple BIN detection calls after
checkoutIdandcheckoutInfocalls. - BIN length must be at least one digit.
- Do not create a BIN detection request for each entered BIN digit.
- See the advanced options section for an example of BIN detection service usage.
Android
Follow these steps for a successful request to get payment brands for a provided card Bin:
- Request a checkout ID.
- Request checkout information.
- Request brands for the provided BIN.
Request checkout ID
Your app should request a checkout ID from your server. This example uses the sample integration server; adapt it to use your own backend API.
public String requestCheckoutId() {
URL url;
String urlString;
HttpURLConnection connection = null;
String checkoutId = null;
urlString = YOUR_URL + "?amount=48.99¤cy=EUR&paymentType=DB";
try {
url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
JsonReader reader = new JsonReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"));
reader.beginObject();
while (reader.hasNext()) {
if (reader.nextName().equals("checkoutId")) {
checkoutId = reader.nextString();
break;
}
}
reader.endObject();
reader.close();
} catch (Exception e) {
/* error occurred */
} finally {
if (connection != null) {
connection.disconnect();
}
}
return checkoutId;
}fun requestCheckoutId(): String? {
val url: URL
var connection: HttpURLConnection? = null
var checkoutId: String? = null
val urlString = YOUR_URL.toString() + "?amount=48.99¤cy=EUR&paymentType=DB"
try {
url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
val reader = JsonReader(InputStreamReader(connection.inputStream, "UTF-8"))
reader.beginObject()
while (reader.hasNext()) {
if (reader.nextName() == "checkoutId") {
checkoutId = reader.nextString()
break
}
}
reader.endObject()
reader.close()
} catch (e: Exception) {
/* error occurred */
} finally {
connection?.disconnect()
}
return checkoutId
}Create an instance of OPPPaymentProvider
OPPPaymentProviderOppPaymentProvider paymentProvider = new OppPaymentProvider(context, Connnect.ProviderMode.TEST);val paymentProvider = OppPaymentProvider(context, Connnect.ProviderMode.TEST)Now, let the class use the ITransactionListener interface.
Request checkout information
Use the checkout ID from the step above to make a checkoutInfo request call.
try {
paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener);
} catch (PaymentException e) {
/* error occurred */
}
@Override
public void paymentConfigRequestSucceeded(CheckoutInfo checkoutInfo) {
// Make Bin detection call
}try {
paymentProvider.requestCheckoutInfo(CHECKOUT_ID, transactionListener)
} catch (e: PaymentException) {
/* error occurred */
}
override fun paymentConfigRequestSucceeded(checkoutInfo: CheckoutInfo) {
// Make Bin detection call
}Request BIN detection service
public void requestBinInfo() {
paymentProvider.requestBinInfo(checkoutId, bin, this::onBinInfo);
}
public void onBinInfo(@Nullable BinInfo binInfo,
@Nullable PaymentError error) {
if (binInfo != null) {
// Handle detected brands
String[] detectedBrands = binInfo.getBrands();
}
}open fun requestBinInfo() {
paymentProvider.requestBinInfo(checkoutId, bin) {
binInfo: BinInfo?, error: PaymentError? -> onBinInfo(binInfo, error)
}
}
open fun onBinInfo(binInfo: BinInfo?,
error: PaymentError?) {
if (binInfo != null) {
// handle detected brands
val detectedBrands = binInfo.brands
}
}Final notes
- BIN detection requests are asynchronous.
- You can make multiple BIN detection calls after
checkoutIdandcheckoutInfocalls. - BIN length must be at least one digit.
- Do not create a BIN detection request for each entered BIN digit.
- See the advanced options section for an example of BIN detection service usage.
Updated about 7 hours ago