Currently I'm presenting the PKAddPaymentPassViewController
in my react-native
application with the following code
let delegate = PKAddPaymentPassDelegate();
let pkAddPaymentPassViewController = PKAddPaymentPassViewController.init(requestConfiguration: pkAddPaymentPassRequestConfiguration!, delegate:delegate );
DispatchQueue.main.async {
RCTPresentedViewController()?.present(pkAddPaymentPassViewController!, animated: true, completion: nil);
}
The problem is, that when I'm taping the cancel
button on the left-top, the View is not disappearing.
Has anyone faced this problem ? any help would be appreciated
I managed to solve this by adding dismiss
functionality to error handler of PKAddPaymentPassViewControllerDelegate
implementation, something like this
func addPaymentPassViewController(
_ controller: PKAddPaymentPassViewController,
didFinishAdding pass: PKPaymentPass?,
error: Error?) {
RCTPresentedViewController()?.dismiss(animated: true, completion: nil)
}
here is whole PKAddPaymentPassViewControllerDelegate
implementation, I can't explain what's going on here, nor this is a best implementation as I wrote it over a year ago with 0 swift experience whatsoever. But this is a working solution.
// RNPasskit.swift
import PassKit
import React
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
extension String {
var hexadecimal: Data? {
var data = Data(capacity: count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
let byteString = (self as NSString).substring(with: match!.range)
let num = UInt8(byteString, radix: 16)!
data.append(num)
}
guard data.count > 0 else { return nil }
return data
}
}
@available(iOS 12.3, *)
@objc(RNPassKit)
class RNPassKit: NSObject, PKAddPaymentPassViewControllerDelegate {
var accessToken: String = "";
var cardRef: String = "";
var isMaster: Bool = false;
@objc public static var isApplePayAvailableForDevice: Bool {
return PKAddPaymentPassViewController.canAddPaymentPass()
}
@objc(checkSupportApplePay:resolver:rejecter:)
public func checkSupportApplePay(config: [String: String], resolve: @escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
let cardSuffix = config["cardSuffix"]!;
let canAddToApplePay = PassKitCardDetector.checkSupportApplePay(cardSuffix: cardSuffix, bankName: nil);
resolve("\(canAddToApplePay)");
}
@objc func initToken(_ token: String) {
Http.accessToken = token;
}
@objc(presentAddPass:resolver:rejecter:)
func presentAddPass(config: [String: String], resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
let cardNumber = config["cardNumber"]!;
let json: [String: Any] = [
"requestId": "inappprov",
"cardNumbers": [cardNumber],
];
Http.post(path: "/GetPanReferenceIds", json: json) { res in
self.isMaster = config["isMaster"] == "true" ? true : false;
if(res != nil && res.count > 0){
let responseJSON = res as! [String: Any];
let refList = responseJSON["refList"]! as! [[String: String]];
let panReferenceId = refList[0]["panReferenceId"]!
let pkAddPaymentPassRequestConfiguration = PKAddPaymentPassRequestConfiguration.init(encryptionScheme: .ECC_V2);
pkAddPaymentPassRequestConfiguration?.cardholderName = config["cardholderName"];
pkAddPaymentPassRequestConfiguration?.style = PKAddPaymentPassStyle.payment;
pkAddPaymentPassRequestConfiguration?.primaryAccountIdentifier = panReferenceId;
pkAddPaymentPassRequestConfiguration?.primaryAccountSuffix = config["primaryAccountSuffix"];
self.cardRef = config["cardRef"]!;
DispatchQueue.main.async {
let pkAddPaymentPassViewController = PKAddPaymentPassViewController.init(requestConfiguration: pkAddPaymentPassRequestConfiguration!, delegate: self);
pkAddPaymentPassViewController?.modalPresentationStyle = UIModalPresentationStyle.pageSheet;
RCTPresentedViewController()?.present(pkAddPaymentPassViewController!, animated: true, completion: nil);
}
}
resolve(nil);
}
}
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, generateRequestWithCertificateChain certificates: [Data], nonce: Data, nonceSignature: Data, completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
let stringCertificates = certificates.map {
$0.hexEncodedString(options: .upperCase)
};
let json: [String: Any] = [
"requestId": "inappprov" + self.cardRef,
"cardRef": self.cardRef,
"certificates": certificates.map {$0.hexEncodedString(options: .upperCase)},
"nonce": nonce.hexEncodedString(options: .upperCase),
"nonceSignature": nonceSignature.hexEncodedString(options: .upperCase),
];
Http.post(path: "/GetDigitizationRequest", json: json) { res in
if(res != nil && res.count > 0){
let responseJSON = res as! [String: String];
let encryptedPassDataString = responseJSON["encryptedPassData"]!;
let activationDataString = responseJSON["activationData"]!;
let ephemeralPublicKeyString = responseJSON["ephemeralPublicKey"]!;
let pkAddPaymentPassRequest = PKAddPaymentPassRequest.init();
if(self.isMaster){
pkAddPaymentPassRequest.activationData = Data.init(base64Encoded: activationDataString, options: []);
} else {
pkAddPaymentPassRequest.activationData = activationDataString.data(using: .utf8);
}
pkAddPaymentPassRequest.encryptedPassData = encryptedPassDataString.hexadecimal;
pkAddPaymentPassRequest.ephemeralPublicKey = ephemeralPublicKeyString.hexadecimal;
handler(pkAddPaymentPassRequest);
}
}
}
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, didFinishAdding pass: PKPaymentPass?, error: Error?) {
RCTPresentedViewController()?.dismiss(animated: true, completion: nil)
RNPassKitEvents.emitter.sendEvent(withName: "onAppleScreen", body: [])
}
}
PassKitCardDetector
implementation was taken from here
I was using RCTEventEmitter
to talk back with javascript
with implementation as simple as follows:
// RNPasskitEvents.swift
import Foundation
import React
@objc(RNPassKitEvents)
open class RNPassKitEvents: RCTEventEmitter {
public static var emitter: RCTEventEmitter!
override init() {
super.init()
RNPassKitEvents.emitter = self
}
open override func supportedEvents() -> [String] {
["onAppleScreen"]
}
}
To present the provisioning screen I was calling presentAddPass
as follows:
NativeModules.RNPassKit.presentAddPass(...).then(...).catch(...)
to track unsuccessful provisioning
const eventEmitter = new NativeEventEmitter(NativeModules.RNPassKitEvents);
eventEmitter.addListener('onAppleScreen', callback);
PS. Don't forget to export methods to js
// RNPassKit.m
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_REMAP_MODULE(RNPassKit, RNPassKit, NSObject)
RCT_EXTERN_METHOD(checkSupportApplePay: (NSDictionary *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(initToken: (NSString *)token)
RCT_EXTERN_METHOD(presentAddPass: (NSDictionary *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
// RNPasskitEvents.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(RNPassKitEvents, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
@end