1 //
2 //  AddToWalletButtonView.swift
3 //  stripe-react-native
4 //
5 //  Created by Charles Cruzan on 3/28/22.
6 //
7 
8 import Foundation
9 import Stripe
10 
11 @objc(ABI48_0_0AddToWalletButtonView)
12 class AddToWalletButtonView: UIView {
13     var pushProvisioningContext: STPPushProvisioningContext? = nil
14     var addToWalletButton: PKAddPassButton? = nil
15 
16     @objc var testEnv: Bool = false
17     @objc var iOSButtonStyle: NSString?
18     @objc var cardDetails: NSDictionary?
19     @objc var ephemeralKey: NSDictionary?
20     @objc var onCompleteAction: ABI48_0_0RCTDirectEventBlock?
21 
22     required init?(coder: NSCoder) {
23         fatalError("init(coder:) has not been implemented")
24     }
25 
didSetPropsnull26     override func didSetProps(_ changedProps: [String]!) {
27         if let addToWalletButton = addToWalletButton {
28             addToWalletButton.removeFromSuperview()
29         }
30 
31         let style = Mappers.mapToPKAddPassButtonStyle(style: iOSButtonStyle as String?)
32 
33         self.addToWalletButton = PKAddPassButton.init(addPassButtonStyle: style)
34 
35         if let addToWalletButton = self.addToWalletButton {
36             addToWalletButton.addTarget(self, action: #selector(beginPushProvisioning), for: .touchUpInside)
37             self.addSubview(addToWalletButton)
38         }
39     }
40 
beginPushProvisioningnull41     @objc func beginPushProvisioning() {
42         if (!PushProvisioningUtils.canAddPaymentPass(primaryAccountIdentifier: cardDetails?["primaryAccountIdentifier"] as? String ?? "", isTestMode: self.testEnv)) {
43             onCompleteAction!(
44                 Errors.createError(
45                     ErrorType.Failed,
46                     "This app cannot add cards to Apple Pay. For information on requesting the necessary entitlement, see the Card Issuers section at developer.apple.com/apple-pay/."
47                 ) as? [AnyHashable : Any]
48             )
49             return
50         }
51 
52         guard let cardHolderName = cardDetails?["name"] as? String else {
53             onCompleteAction!(
54                 Errors.createError(
55                     ErrorType.Failed,
56                     "Missing parameters. `cardDetails.name` must be supplied in the props to <AddToWalletButton />"
57                 ) as? [AnyHashable : Any]
58             )
59             return
60         }
61 
62         if (cardHolderName.isEmpty) {
63             onCompleteAction!(
64                 Errors.createError(
65                     ErrorType.Failed,
66                     "`cardDetails.name` is required, but the passed string was empty"
67                 ) as? [AnyHashable : Any]
68             )
69             return
70         }
71 
72         let config = STPPushProvisioningContext.requestConfiguration(
73             withName: cardHolderName,
74             description: cardDetails?["description"] as? String,
75             last4: cardDetails?["lastFour"] as? String,
76             brand: Mappers.mapToCardBrand(cardDetails?["brand"] as? String),
77             primaryAccountIdentifier: cardDetails?["primaryAccountIdentifier"] as? String
78         )
79 
80         // We can use STPFakeAddPaymentPassViewController ONLY IN TEST MODE. If STPFakeAddPaymentPassViewController is
81         // used with a live mode card, the flow will fail and show a 'Signing certificate was invalid' error.
82         let controller = {
83             return self.testEnv ? STPFakeAddPaymentPassViewController(requestConfiguration: config, delegate: self) : PKAddPaymentPassViewController(requestConfiguration: config, delegate: self)
84         }()
85 
86         let vc = findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController())
87         vc.present(controller!, animated: true, completion: nil)
88     }
89 
90     override init(frame: CGRect) {
91         super.init(frame: frame)
92     }
93 
layoutSubviewsnull94     override func layoutSubviews() {
95         if let addToWalletButton = self.addToWalletButton {
96             addToWalletButton.frame = self.bounds
97         }
98     }
99 }
100 
101 
102 extension AddToWalletButtonView: PKAddPaymentPassViewControllerDelegate {
103     func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, generateRequestWithCertificateChain certificates: [Data], nonce: Data, nonceSignature: Data, completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
104         self.pushProvisioningContext = STPPushProvisioningContext(keyProvider: self)
105 
106         self.pushProvisioningContext?.addPaymentPassViewController(controller, generateRequestWithCertificateChain: certificates, nonce: nonce, nonceSignature: nonceSignature, completionHandler: handler);
107     }
108 
addPaymentPassViewControllernull109     func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, didFinishAdding pass: PKPaymentPass?, error: Error?) {
110         if let error = error as NSError? {
111             onCompleteAction!(
112                 Errors.createError(
113                     error.code == PKAddPaymentPassError.userCancelled.rawValue ? ErrorType.Canceled : ErrorType.Failed,
114                     error as NSError?
115                 ) as? [AnyHashable : Any]
116             )
117         } else {
118             onCompleteAction!([
119                 "error": NSNull(),
120             ] as [AnyHashable : Any])
121         }
122         controller.dismiss(animated: true, completion: nil)
123     }
124 }
125 
126 
127 extension AddToWalletButtonView: STPIssuingCardEphemeralKeyProvider {
createIssuingCardKeynull128     func createIssuingCardKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
129         if let ephemeralKey = self.ephemeralKey as? [AnyHashable : Any] {
130             completion(ephemeralKey, nil)
131         } else {
132             onCompleteAction!(
133                 Errors.createError(
134                     ErrorType.Failed,
135                     "Missing parameters. `ephemeralKey` must be supplied in the props to <AddToWalletButton />"
136                 ) as? [AnyHashable : Any]
137             )
138             completion(nil, nil)
139         }
140     }
141 }
142