Apple Pay/Google Pay
BBMSL Online Payment Service supports Apple Pay in iOS apps and Google Pay in Android apps. You can process wallet payments in a native way without calling the Hosted Checkout Page. Before starting the integration, you need to register your app in Apple Developer Portal and Google Play Console.
Apple Pay
Prerequisite
- You need to subscribe to the Apple Developer Programme before you start integrating Apple Pay with the direct model.
- Follow here to create a merchant identifier.
- Pass the
Merchant Identifier
to your relationship manager, BBMSL will return a.csr
file for uploading to the Apple Pay Payment Processing Certificate section.
By integrating with Apple PayKit, Apple Pay gathers the tokenized payment card data for you by requesting the customer's authorization. Merchants will then pass the data to the PayAPI for processing the payment. The diagram below gives an idea of how the system works on Apple Pay.
Before implementation, you have to first enable the Apple Pay Capability in your Xcode project. The available merchant identifiers will be shown by clicking the refresh button, and you should check the one you passed to BBMSL.
A sample app is provided by Apple here for referencing the implementation of Apple Pay. In this section, we will focus on the details for integrating to BBMSL Online Payment Service.
Before requesting for payment, the app should check if the customers' iOS device supported Apple Pay or have Apple Pay set up. If yes, you may show the button to start the payment, or ask the customers to set up Apple Pay otherwise. Note that you need to handle the button layout constraint by yourselves.
static let supportedNetworks: [PKPaymentNetwork] = [
.masterCard,
.visa
]
class func applePayStatus() -> (canMakePayments: Bool, canSetupCards: Bool) {
return (PKPaymentAuthorizationController.canMakePayments(),
PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks))
}
let result = PaymentHandler.applePayStatus()
var button: UIButton?
if result.canMakePayments {
button = PKPaymentButton(paymentButtonType: .book, paymentButtonStyle: .black)
button?.addTarget(self, action: #selector(ViewController.payPressed), for: .touchUpInside)
} else if result.canSetupCards {
button = PKPaymentButton(paymentButtonType: .setUp, paymentButtonStyle: .black)
button?.addTarget(self, action: #selector(ViewController.setupPressed), for: .touchUpInside)
}
After that, you can follow the demo above to construct a payment request containing all the information about your payment, and use the following code to present the payment view controller once the button is pressed,
caution
You must register the delegate by paymentController?.delegate = self
to receive the payment result callback later.
paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
paymentController?.delegate = self
paymentController?.present(completion: { (presented: Bool) in
if presented {
debugPrint("Presented payment controller")
} else {
debugPrint("Failed to present payment controller")
self.completionHandler(false)
}
})
When the user authorizes the payment, the system calls the following handler to return the payment card data, and you need to pass the payment data to BBMSL through the PayAPI Auth.
If the PayAPI response with responseCode == "0000"
, meaning your payment is succeeded and completed until this step.
A sample code for parsing the Apple Pay payment data and requesting PayAPI is attached below for reference.
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
var status = PKPaymentAuthorizationStatus.success
// create payapi request
var json = JSON()
json["currency"] = "USD"
json["amount"] = 50
json["merchantId"] = 3
json["merchantReference"] = "UNIQUE_MERCHANT_REF"
json["notifyUrl"] = "https://www.bbmsl.com/notify"
var priceData = JSON()
priceData["name"] = "Book"
priceData["unitAmount"] = 50
var lineItem = JSON()
lineItem["priceData"] = priceData
lineItem["quantity"] = 1
json["lineItems"] = [lineItem]
let jsonString = json.rawString()?.components(separatedBy: .whitespacesAndNewlines).joined()
// sign the request json string
let signature = jsonString?.signWithKey(key: privateKey)
// parse data from apple pay paymentData
let cardType = payment.token.paymentMethod.network?.rawValue.toCardType()
let paymentData = try! JSON(data: payment.token.paymentData)
let applePay: Parameters = ["cardType": cardType,
"data": paymentData["data"].string,
"ephemeralPublicKey": paymentData["header"]["ephemeralPublicKey"].string,
"publicKeyHash": paymentData["header"]["publicKeyHash"].string,
"signature": paymentData["signature"].string,
"transactionId": paymentData["header"]["transactionId"].string,
"version": paymentData["version"].string]
let params: Parameters = ["request": jsonString,
"signature": signature,
"applePay": applePay]
let headers: HTTPHeaders = ["Content-Type": "application/json"]
AF.request("\(BBMSL_PAYAPI_BASE_URL)direct/auth", method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers) { urlRequest in
urlRequest.timeoutInterval = 60
urlRequest.allowsConstrainedNetworkAccess = false
}.responseJSON { response in
switch response.result {
case .success(let value):
let rspJson = JSON(value)
if (rspJson["responseCode"].string == "0000") {
status = .success
}else{
status = .failure
}
case let .failure(error):
status = .failure
}
self.paymentStatus = status
completion(PKPaymentAuthorizationResult(status: status, errors: errors))
}
}
note
caution
For Apple Pay, you must request the PayAPI before you call the completion block, as the result will be reflected on the UI to customers to indicate the payment result.
Once the completion block is called in the previous step, the system calls another handler to let the app know that the view controller can be dismissed,
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
Result Handling
The merchant should also handle the result by navigating to another view in the app, otherwise, the app will stay on the same page and customers can proceed with the same payment again.
Google Pay
Prerequisite
Please contact your relationship manager for a gatewayMerchantId
.
Similarly to Apple Pay, the Google Pay API collects customers' payment data securely for you, by submitting the payment data to BBMSL, the payment can proceed immediately. Below is a flow diagram of using Google Pay in an Android app.
Google has provided a demo app here to guide you through the whole implementation. Before you start, you have to configure your app by following instruction here.
To request the payment, a payment token for encrypting the customer's card data needs to be defined by using the provided gatewayMerchantId
with the following code,
- Kotlin
- Java
private fun gatewayTokenizationSpecification(): JSONObject {
return JSONObject().apply {
put("type", "PAYMENT_GATEWAY")
put("parameters", JSONObject(mapOf(
"gateway" to "worldpay",
"gatewayMerchantId" to "BBMSL_GATEWAY_MERCHANT_ID")))
}
}
private static JSONObject getGatewayTokenizationSpecification() throws JSONException {
return new JSONObject() {{
put("type", "PAYMENT_GATEWAY");
put("parameters", new JSONObject() {{
put("gateway", "worldpay");
put("gatewayMerchantId", "BBMSL_GATEWAY_MERCHANT_ID");
}});
}};
}
Prerequisite
BBMSL Online Payment Service uses Worldpay as the payment service provider for Google Pay. You must fill in worldpay
for the value of gateway
key.
Also, you have to define the supported card network and auth methods,
- Kotlin
- Java
private val allowedCardNetworks = JSONArray(listOf(
"MASTERCARD",
"VISA"
))
private val allowedCardAuthMethods = JSONArray(listOf(
"PAN_ONLY",
"CRYPTOGRAM_3DS"
))
private static JSONArray getAllowedCardNetworks() {
return new JSONArray()
.put("MASTERCARD")
.put("VISA");
}
private static JSONArray getAllowedCardAuthMethods() {
return new JSONArray()
.put("PAN_ONLY")
.put("CRYPTOGRAM_3DS");
}
To request payment, a request object containing all the information is needed to present the payment activity. The payment process is an Async process, the LOAD_PAYMENT_DATA_REQUEST_CODE
can be used to get the result in the callback later.
Below is some reference code quoted from the sample app for requesting payment,
- Kotlin
- Java
val paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(priceCents)
if (paymentDataRequestJson == null) {
Log.e("RequestPayment", "Can't fetch payment data request")
return
}
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
if (request != null) {
AutoResolveHelper.resolveTask(
paymentsClient.loadPaymentData(request), this, LOAD_PAYMENT_DATA_REQUEST_CODE)
}
Optional<JSONObject> paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(priceCents);
if (!paymentDataRequestJson.isPresent()) {
return;
}
PaymentDataRequest request =
PaymentDataRequest.fromJson(paymentDataRequestJson.get().toString());
if (request != null) {
AutoResolveHelper.resolveTask(
paymentsClient.loadPaymentData(request),
this, LOAD_PAYMENT_DATA_REQUEST_CODE);
}
The payment data and result can be found in the onActivityResult
callback,
- Kotlin
- Java
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
// Value passed in AutoResolveHelper
LOAD_PAYMENT_DATA_REQUEST_CODE -> {
when (resultCode) {
RESULT_OK ->
data?.let { intent ->
PaymentData.getFromIntent(intent)?.let(::handlePaymentSuccess)
}
RESULT_CANCELED -> {
// The customer cancelled the payment attempt
}
AutoResolveHelper.RESULT_ERROR -> {
AutoResolveHelper.getStatusFromIntent(data)?.let {
handleError(it.statusCode)
}
}
}
// Re-enables the Google Pay payment button.
googlePayButton.isClickable = true
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// value passed in AutoResolveHelper
case LOAD_PAYMENT_DATA_REQUEST_CODE:
switch (resultCode) {
case Activity.RESULT_OK:
PaymentData paymentData = PaymentData.getFromIntent(data);
handlePaymentSuccess(paymentData);
break;
case Activity.RESULT_CANCELED:
// The user cancelled the payment attempt
break;
case AutoResolveHelper.RESULT_ERROR:
Status status = AutoResolveHelper.getStatusFromIntent(data);
handleError(status.getStatusCode());
break;
}
// Re-enables the Google Pay payment button.
googlePayButton.setClickable(true);
}
}
If the payment attempt is succeeded with resultCode == RESULT_OK
, the payment data can be collected and then send to BBMSL through the PayAPI Auth.
If the PayAPI response with responseCode == "0000"
, meaning your payment is completed.
A sample code of parsing the Google Pay paymentData
and requesting PayAPI is attached below for reference.
private fun requestPayApi(paymentData: PaymentData) {
val gatewayApi = RetrofitHelper.getInstance(BBMSL_PAYAPI_BASE_URL)
.create(GatewayApi::class.java)
val jsonObject = JSONObject()
jsonObject.put("currency", "USD")
jsonObject.put("amount", 50)
jsonObject.put("merchantId", 3)
jsonObject.put("merchantReference", "UNIQUE_MERCHANT_REF")
jsonObject.put("notifyUrl", "https://www.bbmsl.com/notify")
val priceData = JSONObject()
priceData.put("name", "Book")
priceData.put("unitAmount", 50)
val lineItem = JSONObject()
lineItem.put("priceData", priceData)
lineItem.put("quantity", 1)
val lineItems = JSONArray()
lineItems.put(lineItem)
jsonObject.put("lineItems", lineItems)
val jsonString = jsonObject.toString().replace("[\\n\t ]".toRegex(), "")
val signature = SignatureUtils.sign(jsonString, privateKey, true)
val json = mutableMapOf<String, Any>()
json["request"] = jsonString
json["signature"] = signature ?: ""
val tokenJsonString = JSONObject(paymentData.toJson()).getJSONObject("paymentMethodData").getJSONObject("tokenizationData").getString("token")
val tokenJsonObject = JSONObject(tokenJsonString)
val googlePayMap = mutableMapOf<String, Any>()
googlePayMap["cardType"] = data.getJSONObject("info").getString("cardNetwork").toCardType()
googlePayMap["protocolVersion"] = tokenJsonObject.getString("protocolVersion")
googlePayMap["signature"] = tokenJsonObject.getString("signature")
googlePayMap["signedMessage"] = tokenJsonObject.getString("signedMessage")
json["googlePay"] = googlePayMap
GlobalScope.launch {
try {
val result = gatewayApi.sendAuth(Gson().toJsonTree(json).asJsonObject)
val responseJson = JSONObject(resultString)
if (responseJson.getString("responseCode") == "0000") {
// payment success
} else {
// payment failed
}
} catch (e: Exception) {
// payment failed
}
}
}
note
The sample code used Retrofit library for HTTP request, but any other libraries will do the same.
Parsing Google Pay Payment Data
The paymentData.paymentMethodData.tokenizationData.token
is returned as JSON string from Google Pay API, you have to parse it to JSON object to retrieve the JSON value before posting to the PayAPI.
Result Handling
Different from Apple Pay, Google Pay dismisses the payment view when the payment data is successfully obtained, merchants need to handle the payment failed scenarios by the app UI.