@expensify/react-native-wallet 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +259 -0
  3. package/android/build.gradle +103 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifestNew.xml +2 -0
  6. package/android/src/main/java/com/expensify/wallet/Utils.kt +148 -0
  7. package/android/src/main/java/com/expensify/wallet/WalletModule.kt +301 -0
  8. package/android/src/main/java/com/expensify/wallet/WalletPackage.kt +43 -0
  9. package/android/src/main/java/com/expensify/wallet/error/InvalidNetworkError.kt +6 -0
  10. package/android/src/main/java/com/expensify/wallet/event/OnCardActivatedEvent.kt +18 -0
  11. package/android/src/main/java/com/expensify/wallet/model/CardData.kt +12 -0
  12. package/android/src/main/java/com/expensify/wallet/model/CardStatus.kt +15 -0
  13. package/android/src/main/java/com/expensify/wallet/model/TokenizationStatus.kt +7 -0
  14. package/android/src/main/java/com/expensify/wallet/model/WalletData.kt +7 -0
  15. package/android/src/paper/java/com/expensify/wallet/NativeWalletSpec.java +71 -0
  16. package/assets/buttons/android/af.svg +10 -0
  17. package/assets/buttons/android/am.svg +11 -0
  18. package/assets/buttons/android/ar.svg +16 -0
  19. package/assets/buttons/android/az.svg +16 -0
  20. package/assets/buttons/android/be.svg +16 -0
  21. package/assets/buttons/android/bg.svg +16 -0
  22. package/assets/buttons/android/bn.svg +10 -0
  23. package/assets/buttons/android/br.svg +16 -0
  24. package/assets/buttons/android/bs.svg +16 -0
  25. package/assets/buttons/android/ca.svg +16 -0
  26. package/assets/buttons/android/cs.svg +16 -0
  27. package/assets/buttons/android/da.svg +16 -0
  28. package/assets/buttons/android/de.svg +16 -0
  29. package/assets/buttons/android/el.svg +16 -0
  30. package/assets/buttons/android/en.svg +16 -0
  31. package/assets/buttons/android/es.svg +16 -0
  32. package/assets/buttons/android/et.svg +16 -0
  33. package/assets/buttons/android/fa.svg +11 -0
  34. package/assets/buttons/android/fi.svg +16 -0
  35. package/assets/buttons/android/fil.svg +16 -0
  36. package/assets/buttons/android/fr.svg +16 -0
  37. package/assets/buttons/android/he.svg +16 -0
  38. package/assets/buttons/android/hk.svg +16 -0
  39. package/assets/buttons/android/hr.svg +16 -0
  40. package/assets/buttons/android/hu.svg +16 -0
  41. package/assets/buttons/android/hy.svg +16 -0
  42. package/assets/buttons/android/id.svg +16 -0
  43. package/assets/buttons/android/is.svg +16 -0
  44. package/assets/buttons/android/it.svg +16 -0
  45. package/assets/buttons/android/ja.svg +16 -0
  46. package/assets/buttons/android/ka.svg +16 -0
  47. package/assets/buttons/android/kk.svg +16 -0
  48. package/assets/buttons/android/km.svg +10 -0
  49. package/assets/buttons/android/ky.svg +16 -0
  50. package/assets/buttons/android/lo.svg +10 -0
  51. package/assets/buttons/android/lt.svg +16 -0
  52. package/assets/buttons/android/lv.svg +16 -0
  53. package/assets/buttons/android/mk.svg +16 -0
  54. package/assets/buttons/android/mn.svg +10 -0
  55. package/assets/buttons/android/my.svg +10 -0
  56. package/assets/buttons/android/ne.svg +10 -0
  57. package/assets/buttons/android/nl.svg +16 -0
  58. package/assets/buttons/android/no.svg +16 -0
  59. package/assets/buttons/android/pl.svg +16 -0
  60. package/assets/buttons/android/pt.svg +16 -0
  61. package/assets/buttons/android/ro.svg +16 -0
  62. package/assets/buttons/android/ru.svg +16 -0
  63. package/assets/buttons/android/se.svg +16 -0
  64. package/assets/buttons/android/si.svg +10 -0
  65. package/assets/buttons/android/sk.svg +16 -0
  66. package/assets/buttons/android/sl.svg +16 -0
  67. package/assets/buttons/android/sq.svg +16 -0
  68. package/assets/buttons/android/sr.svg +16 -0
  69. package/assets/buttons/android/sw.svg +10 -0
  70. package/assets/buttons/android/th.svg +16 -0
  71. package/assets/buttons/android/tr.svg +16 -0
  72. package/assets/buttons/android/tw.svg +16 -0
  73. package/assets/buttons/android/uk.svg +16 -0
  74. package/assets/buttons/android/ur.svg +11 -0
  75. package/assets/buttons/android/uz.svg +16 -0
  76. package/assets/buttons/android/vi.svg +16 -0
  77. package/assets/buttons/apple/ar.svg +269 -0
  78. package/assets/buttons/apple/az.svg +275 -0
  79. package/assets/buttons/apple/bg.svg +275 -0
  80. package/assets/buttons/apple/cs.svg +288 -0
  81. package/assets/buttons/apple/da.svg +267 -0
  82. package/assets/buttons/apple/de.svg +282 -0
  83. package/assets/buttons/apple/el.svg +298 -0
  84. package/assets/buttons/apple/en.svg +259 -0
  85. package/assets/buttons/apple/es.svg +291 -0
  86. package/assets/buttons/apple/et.svg +279 -0
  87. package/assets/buttons/apple/fi.svg +287 -0
  88. package/assets/buttons/apple/fil.svg +292 -0
  89. package/assets/buttons/apple/fr.svg +295 -0
  90. package/assets/buttons/apple/he.svg +274 -0
  91. package/assets/buttons/apple/hi.svg +227 -0
  92. package/assets/buttons/apple/hk.svg +265 -0
  93. package/assets/buttons/apple/hr.svg +277 -0
  94. package/assets/buttons/apple/hu.svg +302 -0
  95. package/assets/buttons/apple/id.svg +284 -0
  96. package/assets/buttons/apple/it.svg +280 -0
  97. package/assets/buttons/apple/ja.svg +268 -0
  98. package/assets/buttons/apple/ko.svg +266 -0
  99. package/assets/buttons/apple/lt.svg +283 -0
  100. package/assets/buttons/apple/lv.svg +277 -0
  101. package/assets/buttons/apple/ms.svg +280 -0
  102. package/assets/buttons/apple/mt.svg +268 -0
  103. package/assets/buttons/apple/nl.svg +290 -0
  104. package/assets/buttons/apple/no.svg +284 -0
  105. package/assets/buttons/apple/pl.svg +280 -0
  106. package/assets/buttons/apple/pt.svg +290 -0
  107. package/assets/buttons/apple/ro.svg +290 -0
  108. package/assets/buttons/apple/ru.svg +279 -0
  109. package/assets/buttons/apple/sk.svg +288 -0
  110. package/assets/buttons/apple/sl.svg +269 -0
  111. package/assets/buttons/apple/sv.svg +287 -0
  112. package/assets/buttons/apple/th.svg +277 -0
  113. package/assets/buttons/apple/tr.svg +273 -0
  114. package/assets/buttons/apple/uk.svg +274 -0
  115. package/assets/buttons/apple/vi.svg +259 -0
  116. package/assets/buttons/apple/zh.svg +279 -0
  117. package/assets/hero-image.png +0 -0
  118. package/assets/signature-dark.png +0 -0
  119. package/assets/signature-light.png +0 -0
  120. package/ios/RNWallet.h +13 -0
  121. package/ios/RNWallet.mm +153 -0
  122. package/ios/WalletManager.swift +292 -0
  123. package/ios/stucts/AddPassResponse.swift +36 -0
  124. package/ios/stucts/CardInfo.swift +37 -0
  125. package/ios/stucts/WalletEncryptedPayload.swift +27 -0
  126. package/lib/commonjs/AddToWalletButton.js +47 -0
  127. package/lib/commonjs/AddToWalletButton.js.map +1 -0
  128. package/lib/commonjs/NativeWallet.js +22 -0
  129. package/lib/commonjs/NativeWallet.js.map +1 -0
  130. package/lib/commonjs/constants.js +195 -0
  131. package/lib/commonjs/constants.js.map +1 -0
  132. package/lib/commonjs/index.js +114 -0
  133. package/lib/commonjs/index.js.map +1 -0
  134. package/lib/commonjs/utils.js +38 -0
  135. package/lib/commonjs/utils.js.map +1 -0
  136. package/lib/module/AddToWalletButton.js +38 -0
  137. package/lib/module/AddToWalletButton.js.map +1 -0
  138. package/lib/module/NativeWallet.js +17 -0
  139. package/lib/module/NativeWallet.js.map +1 -0
  140. package/lib/module/constants.js +188 -0
  141. package/lib/module/constants.js.map +1 -0
  142. package/lib/module/index.js +92 -0
  143. package/lib/module/index.js.map +1 -0
  144. package/lib/module/utils.js +32 -0
  145. package/lib/module/utils.js.map +1 -0
  146. package/lib/typescript/src/AddToWalletButton.d.ts +10 -0
  147. package/lib/typescript/src/AddToWalletButton.d.ts.map +1 -0
  148. package/lib/typescript/src/NativeWallet.d.ts +64 -0
  149. package/lib/typescript/src/NativeWallet.d.ts.map +1 -0
  150. package/lib/typescript/src/constants.d.ts +4 -0
  151. package/lib/typescript/src/constants.d.ts.map +1 -0
  152. package/lib/typescript/src/index.d.ts +20 -0
  153. package/lib/typescript/src/index.d.ts.map +1 -0
  154. package/lib/typescript/src/utils.d.ts +5 -0
  155. package/lib/typescript/src/utils.d.ts.map +1 -0
  156. package/package.json +172 -0
  157. package/react-native-wallet.podspec +36 -0
  158. package/src/AddToWalletButton.tsx +49 -0
  159. package/src/NativeWallet.ts +87 -0
  160. package/src/constants.ts +191 -0
  161. package/src/index.tsx +124 -0
  162. package/src/utils.ts +35 -0
@@ -0,0 +1,153 @@
1
+ #import <PassKit/PassKit.h>
2
+ #import "RNWallet.h"
3
+
4
+ #if RNWallet_USE_FRAMEWORKS
5
+ #import <react_native_wallet/react_native_wallet-Swift.h>
6
+ #else
7
+ #import <react_native_wallet-Swift.h>
8
+ #endif
9
+
10
+
11
+ @interface RNWallet () <WalletDelegate>
12
+ @end
13
+
14
+ @implementation RNWallet {
15
+ WalletManager *walletManager;
16
+ }
17
+
18
+ - (instancetype)init {
19
+ self = [super init];
20
+ if(self) {
21
+ walletManager = [WalletManager new];
22
+ walletManager.delegate = self;
23
+ }
24
+ return self;
25
+ }
26
+
27
+ RCT_EXPORT_MODULE()
28
+
29
+ RCT_REMAP_METHOD(checkWalletAvailability,
30
+ checkWalletAvailability:(RCTPromiseResolveBlock)resolve
31
+ reject:(RCTPromiseRejectBlock)reject)
32
+ {
33
+ resolve(@([walletManager checkWalletAvailability]));
34
+ }
35
+
36
+ RCT_REMAP_METHOD(IOSPresentAddPaymentPassView,
37
+ IOSPresentAddPaymentPassView:(JS::NativeWallet::IOSCardData &)cardData
38
+ resolve:(RCTPromiseResolveBlock)resolve
39
+ reject:(RCTPromiseRejectBlock)reject)
40
+ {
41
+ @try {
42
+ NSDictionary *cardDataDict = @{
43
+ @"network": [self safeString:cardData.network()],
44
+ @"cardHolderName": [self safeString:cardData.cardHolderName()],
45
+ @"lastDigits": [self safeString:cardData.lastDigits()],
46
+ @"cardDescription": [self safeString:cardData.cardDescription()],
47
+ };
48
+ dispatch_async(dispatch_get_main_queue(), ^{
49
+ [self->walletManager IOSPresentAddPaymentPassViewWithCardData:cardDataDict completion:^(OperationResult result, NSDictionary* data) {
50
+ [self handleWalletResponse:result data:data completedBlock:resolve errorPrefix:@"present_payment_pass_view_failed" defaultErrorMessage:@"Failed to present the payment pass view" rejecter:reject];
51
+ }];
52
+ });
53
+ } @catch (NSException *exception) {
54
+ [self rejectWithErrorType:@"present_payment_pass_view_failed" code:500 description:exception.reason ?: @"An unexpected error occurred." rejecter:reject];
55
+ }
56
+ }
57
+
58
+ RCT_REMAP_METHOD(IOSHandleAddPaymentPassResponse,
59
+ IOSHandleAddPaymentPassResponse:(JS::NativeWallet::IOSEncryptPayload &)payload
60
+ resolve:(RCTPromiseResolveBlock)resolve
61
+ reject:(RCTPromiseRejectBlock)reject)
62
+ {
63
+ @try {
64
+ NSDictionary *payloadDict = @{
65
+ @"encryptedPassData": [self safeString:payload.encryptedPassData()],
66
+ @"activationData": [self safeString:payload.activationData()],
67
+ @"ephemeralPublicKey": [self safeString:payload.ephemeralPublicKey()],
68
+ };
69
+ dispatch_async(dispatch_get_main_queue(), ^{
70
+ [self->walletManager IOSHandleAddPaymentPassResponseWithPayload:payloadDict completion:^(OperationResult result, NSDictionary* data) {
71
+ [self handleWalletResponse:result data:data completedBlock:resolve errorPrefix:@"add_card_failed" defaultErrorMessage:@"Failed to add the card to the wallet" rejecter:reject];
72
+ }];
73
+ });
74
+ } @catch (NSException *exception) {
75
+ [self rejectWithErrorType:@"add_card_failed" code:500 description:exception.reason ?: @"An unexpected error occurred." rejecter:reject];
76
+ }
77
+ }
78
+
79
+ RCT_REMAP_METHOD(getCardStatusBySuffix,
80
+ getCardStatusBySuffix:(NSString *)last4Digits
81
+ resolve:(RCTPromiseResolveBlock)resolve
82
+ reject:(RCTPromiseRejectBlock)reject)
83
+ {
84
+ resolve([walletManager getCardStatusBySuffixWithLast4Digits:last4Digits]);
85
+ }
86
+
87
+ RCT_REMAP_METHOD(getCardStatusByIdentifier,
88
+ getCardStatusByIdentifier:(NSString *)identifier
89
+ tsp:(NSString *)tsp
90
+ resolve:(RCTPromiseResolveBlock)resolve
91
+ reject:(RCTPromiseRejectBlock)reject)
92
+ {
93
+ resolve([walletManager getCardStatusByIdentifierWithIdentifier:identifier]);
94
+ }
95
+
96
+ - (void)addCardToGoogleWallet:(JS::NativeWallet::AndroidCardData &)cardData resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
97
+ // no-op
98
+ }
99
+
100
+ - (void)getSecureWalletInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
101
+ // no-op
102
+ }
103
+
104
+ - (NSArray<NSString *> *)supportedEvents {
105
+ return [WalletManager supportedEvents];
106
+ }
107
+
108
+ - (void)sendEventWithName:(NSString * _Nonnull)name result:(NSDictionary *)result {
109
+ [self sendEventWithName:name body:result];
110
+ }
111
+
112
+ - (void)rejectWithErrorType:(NSString *)type
113
+ code:(NSInteger)code
114
+ description:(NSString *)description
115
+ rejecter:(RCTPromiseRejectBlock)reject {
116
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
117
+ NSString *errorWithDomain = walletManager.packageName;
118
+ NSError *error = [NSError errorWithDomain:errorWithDomain
119
+ code:code
120
+ userInfo:userInfo];
121
+
122
+ NSString *errorMessage = [NSString stringWithFormat:@"%@ - %@", errorWithDomain, description];
123
+ reject(type, errorMessage, error);
124
+ }
125
+
126
+ - (void)handleWalletResponse:(OperationResult)result
127
+ data:(NSDictionary *)data
128
+ completedBlock:(RCTPromiseResolveBlock)resolve
129
+ errorPrefix:(NSString *)errorPrefix
130
+ defaultErrorMessage:(NSString *)defaultErrorMsg
131
+ rejecter:(RCTPromiseRejectBlock)reject {
132
+ if (result < 3) {
133
+ resolve(data);
134
+ } else {
135
+ NSString *errorMessage = data[@"errorMessage"] ?: defaultErrorMsg ?: @"Operation failed";
136
+ [self rejectWithErrorType:errorPrefix code:1001 description:errorMessage rejecter:reject];
137
+ }
138
+ }
139
+
140
+ - (NSString *)safeString:(NSString *)value {
141
+ return value ?: @"";
142
+ }
143
+
144
+ #ifdef RCT_NEW_ARCH_ENABLED
145
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
146
+ (const facebook::react::ObjCTurboModule::InitParams &)params
147
+ {
148
+ return std::make_shared<facebook::react::NativeWalletSpecJSI>(params);
149
+ }
150
+ #endif
151
+
152
+
153
+ @end
@@ -0,0 +1,292 @@
1
+ import Foundation
2
+ import PassKit
3
+ import UIKit
4
+ import React
5
+
6
+ public typealias CompletionHandler = (OperationResult, NSDictionary?) -> Void
7
+
8
+ @objc public protocol WalletDelegate {
9
+ func sendEvent(name: String, result: NSDictionary)
10
+ }
11
+
12
+ @objc
13
+ open class WalletManager: UIViewController {
14
+
15
+ @objc public weak var delegate: WalletDelegate? = nil
16
+
17
+ private var addPassViewController: PKAddPaymentPassViewController?
18
+
19
+ private var presentAddPaymentPassCompletionHandler: (CompletionHandler)?
20
+
21
+ private var addPaymentPassCompletionHandler: (CompletionHandler)?
22
+
23
+ private var addPassHandler: ((PKAddPaymentPassRequest) -> Void)?
24
+
25
+ @objc public var packageName = "react-native-wallet"
26
+
27
+ let passLibrary = PKPassLibrary()
28
+
29
+ override init(nibName: String?, bundle: Bundle?) {
30
+ super.init(nibName: nibName, bundle: bundle)
31
+ addPassObserver()
32
+ }
33
+
34
+ required public init?(coder: NSCoder) {
35
+ super.init(coder: coder)
36
+ addPassObserver()
37
+ }
38
+
39
+ deinit {
40
+ NotificationCenter.default.removeObserver(self)
41
+ }
42
+
43
+ func addPassObserver() {
44
+ NotificationCenter.default.addObserver(
45
+ self,
46
+ selector: #selector(passLibraryDidChange),
47
+ name: NSNotification.Name(rawValue: PKPassLibraryNotificationName.PKPassLibraryDidChange.rawValue),
48
+ object: passLibrary
49
+ )
50
+ }
51
+
52
+ @objc func passLibraryDidChange(_ notification: Notification) {
53
+ guard let userInfo = notification.userInfo else {
54
+ return
55
+ }
56
+
57
+ // Check if passes were added or status changed
58
+ if let addedPasses = userInfo[PKPassLibraryNotificationKey.addedPassesUserInfoKey] as? [PKPass] {
59
+ checkPassActivationStatus(addedPasses)
60
+ }
61
+
62
+ // Check for updated passes
63
+ if let replacedPasses = userInfo[PKPassLibraryNotificationKey.replacementPassesUserInfoKey] as? [PKPass] {
64
+ checkPassActivationStatus(replacedPasses)
65
+ }
66
+ }
67
+
68
+ func checkPassActivationStatus(_ passes: [PKPass]) {
69
+ for pass in passes {
70
+ if pass.secureElementPass?.passActivationState == .activated {
71
+ delegate?.sendEvent(name: Event.onCardActivated.rawValue, result: [
72
+ "state": "activated",
73
+ "serialNumber": pass.serialNumber
74
+ ]);
75
+ }
76
+ }
77
+ }
78
+
79
+ @objc
80
+ public func checkWalletAvailability() -> Bool {
81
+ return isPassKitAvailable();
82
+ }
83
+
84
+ @objc
85
+ public func IOSPresentAddPaymentPassView(cardData: NSDictionary, completion: @escaping CompletionHandler) {
86
+ guard isPassKitAvailable() else {
87
+ completion(.error, [
88
+ "errorMessage": "InApp enrollment not available for this device"
89
+ ])
90
+ return
91
+ }
92
+
93
+ let card: CardInfo
94
+ do {
95
+ card = try CardInfo(cardData: cardData)
96
+ }
97
+ catch {
98
+ completion(.error, [
99
+ "errorMessage": "Invalid card data. Please check your card information and try again..."
100
+ ])
101
+ return
102
+ }
103
+
104
+ guard let configuration = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
105
+ completion(.error, [
106
+ "errorMessage": "InApp enrollment configuraton fails"
107
+ ])
108
+ return
109
+ }
110
+
111
+ configuration.cardholderName = card.cardHolderName
112
+ configuration.primaryAccountSuffix = card.lastDigits
113
+ configuration.localizedDescription = String(card.cardDescription)
114
+
115
+ guard let enrollViewController = PKAddPaymentPassViewController(requestConfiguration: configuration, delegate: self) else {
116
+ completion(.error, [
117
+ "errorMessage": "InApp enrollment controller configuration fails"
118
+ ])
119
+ return
120
+ }
121
+
122
+ presentAddPaymentPassCompletionHandler = completion
123
+ DispatchQueue.main.async {
124
+ if self.addPassViewController == nil {
125
+ self.addPassViewController = enrollViewController
126
+ RCTPresentedViewController()?.present(enrollViewController, animated: true, completion: nil)
127
+ } else {
128
+ self.logInfo(message: "EnrollViewController is already presented.")
129
+ }
130
+ }
131
+ }
132
+
133
+ @objc
134
+ public func IOSHandleAddPaymentPassResponse(payload: NSDictionary, completion: @escaping CompletionHandler) {
135
+ guard addPassHandler != nil else {
136
+ hideModal()
137
+ completion(.error, [
138
+ "errorMessage": "addPassHandler unavailable"
139
+ ])
140
+ return
141
+ }
142
+
143
+ let walletData: WalletEncryptedPayload
144
+ do {
145
+ walletData = try WalletEncryptedPayload(data: payload)
146
+ } catch {
147
+ hideModal()
148
+ completion(.error, [
149
+ "errorMessage": "Invalid payload data"
150
+ ])
151
+ return
152
+ }
153
+
154
+ self.addPaymentPassCompletionHandler = completion
155
+
156
+ let addPaymentPassRequest = PKAddPaymentPassRequest()
157
+ addPaymentPassRequest.encryptedPassData = walletData.encryptedPassData
158
+ addPaymentPassRequest.activationData = walletData.activationData
159
+ addPaymentPassRequest.ephemeralPublicKey = walletData.ephemeralPublicKey
160
+ self.addPassHandler?(addPaymentPassRequest)
161
+ self.addPassHandler = nil
162
+ }
163
+
164
+ private func getPassActivationState(matching condition: (PKSecureElementPass) -> Bool) -> NSNumber {
165
+ let paymentPasses = passLibrary.passes(of: .payment)
166
+ if paymentPasses.isEmpty {
167
+ self.logInfo(message: "No passes found in Wallet.")
168
+ return -1
169
+ }
170
+
171
+ for pass in paymentPasses {
172
+ guard let securePassElement = pass.secureElementPass else { continue }
173
+ if condition(securePassElement) {
174
+ return NSNumber(value: securePassElement.passActivationState.rawValue)
175
+ }
176
+ }
177
+ return -1
178
+ }
179
+
180
+ @objc public func getCardStatusBySuffix(last4Digits: NSString) -> NSNumber {
181
+ return getPassActivationState { pass in
182
+ return pass.primaryAccountNumberSuffix.hasSuffix(last4Digits as String)
183
+ }
184
+ }
185
+
186
+ @objc public func getCardStatusByIdentifier(identifier: NSString) -> NSNumber {
187
+ return getPassActivationState { pass in
188
+ return pass.primaryAccountIdentifier == identifier as String
189
+ }
190
+ }
191
+
192
+ private func isPassKitAvailable() -> Bool {
193
+ return PKAddPaymentPassViewController.canAddPaymentPass()
194
+ }
195
+
196
+ private func hideModal() {
197
+ DispatchQueue.main.async {
198
+ if let enrollVC = self.addPassViewController, enrollVC.isBeingPresented || enrollVC.presentingViewController != nil {
199
+ enrollVC.dismiss(animated: true, completion: {
200
+ self.addPassViewController = nil
201
+ })
202
+ } else {
203
+ self.logInfo(message: "EnrollViewController is not presented currently.")
204
+ }
205
+ }
206
+ }
207
+
208
+ private func logInfo(message: String) {
209
+ print("[\(packageName)] \(message)")
210
+ }
211
+ }
212
+
213
+ extension WalletManager: PKAddPaymentPassViewControllerDelegate {
214
+ // Perform the bridge from Apple -> Issuer -> Apple
215
+ public func addPaymentPassViewController(
216
+ _ controller: PKAddPaymentPassViewController,
217
+ generateRequestWithCertificateChain certificates: [Data],
218
+ nonce: Data, nonceSignature: Data,
219
+ completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
220
+ let stringNonce = nonce.base64EncodedString() as NSString
221
+ let stringNonceSignature = nonceSignature.base64EncodedString() as NSString
222
+ let stringCertificates = certificates.map {
223
+ $0.base64EncodedString() as NSString
224
+ }
225
+ let reqestCardData = AddPassResponse(status: .completed, nonce: stringNonce, nonceSignature: stringNonceSignature, certificates: stringCertificates)
226
+ self.addPassHandler = handler
227
+
228
+ // Retry the JS issuer callback if the user tries again to add a payment pass
229
+ if let addPaymentPassHandler = addPaymentPassCompletionHandler {
230
+ addPaymentPassHandler(.retry, reqestCardData.toNSDictionary())
231
+ addPaymentPassCompletionHandler = nil
232
+ return
233
+ }
234
+
235
+ // Finish IOSPresentAddPaymentPassView function
236
+ if let presentPassHandler = presentAddPaymentPassCompletionHandler {
237
+ presentPassHandler(.completed, reqestCardData.toNSDictionary())
238
+ presentAddPaymentPassCompletionHandler = nil
239
+ }
240
+ }
241
+
242
+ // This method will be called when enroll process ends (with success/error)
243
+ public func addPaymentPassViewController(
244
+ _ controller: PKAddPaymentPassViewController,
245
+ didFinishAdding pass: PKPaymentPass?,
246
+ error: Error?) {
247
+ if addPassViewController == nil {
248
+ return
249
+ }
250
+
251
+ let errorMessage = error?.localizedDescription ?? ""
252
+
253
+ if error != nil {
254
+ self.logInfo(message: "Error: \(errorMessage)")
255
+ delegate?.sendEvent(name: Event.onCardActivated.rawValue, result: [
256
+ "state": "canceled"
257
+ ]);
258
+ }
259
+
260
+ // Cancel the IOSPresentAddPaymentPassView function when the user cancelled the modal
261
+ if let handler = presentAddPaymentPassCompletionHandler {
262
+ let response = AddPassResponse(status: .canceled, nonce: nil, nonceSignature: nil, certificates: nil)
263
+ handler(.canceled, response.toNSDictionary())
264
+ }
265
+
266
+ // If the pass is returned complete the IOSHandleAddPaymentPassResponse function
267
+ if let addPaymentPassHandler = addPaymentPassCompletionHandler {
268
+ if pass != nil {
269
+ addPaymentPassHandler(.completed, nil)
270
+ } else {
271
+ addPaymentPassHandler(.error, [
272
+ "errorMessage": "Could not add card. \(errorMessage))."
273
+ ])
274
+ }
275
+ }
276
+
277
+ hideModal()
278
+ addPaymentPassCompletionHandler = nil
279
+ presentAddPaymentPassCompletionHandler = nil
280
+ }
281
+ }
282
+
283
+ extension WalletManager {
284
+ enum Event: String, CaseIterable {
285
+ case onCardActivated
286
+ }
287
+
288
+ @objc
289
+ public static var supportedEvents: [String] {
290
+ return Event.allCases.map(\.rawValue);
291
+ }
292
+ }
@@ -0,0 +1,36 @@
1
+ import PassKit
2
+
3
+ struct AddPassResponse {
4
+ let status: OperationResult
5
+ let nonce: NSString?
6
+ let nonceSignature: NSString?
7
+ let certificates: [NSString]?
8
+
9
+ func toNSDictionary() -> NSDictionary {
10
+ if status.rawValue > 0 {
11
+ return [
12
+ "status": status.rawValue,
13
+ ]
14
+ }
15
+
16
+ guard let nonce = nonce,
17
+ let nonceSignature = nonceSignature,
18
+ let certificates = certificates else {
19
+ return [:]
20
+ }
21
+
22
+ return [
23
+ "status": status.rawValue,
24
+ "nonce": nonce,
25
+ "nonceSignature": nonceSignature,
26
+ "certificates": certificates
27
+ ]
28
+ }
29
+ }
30
+
31
+ @objc public enum OperationResult: Int {
32
+ case completed = 0
33
+ case canceled
34
+ case retry
35
+ case error
36
+ }
@@ -0,0 +1,37 @@
1
+ import PassKit
2
+
3
+ struct CardInfo {
4
+ let network: PKPaymentNetwork
5
+ let cardHolderName: String
6
+ let lastDigits: String
7
+ let cardDescription: String
8
+
9
+ init(cardData: NSDictionary) throws {
10
+ guard let networkString = cardData["network"] as? String, !networkString.isEmpty,
11
+ let network = CardInfo.getNetwork(from: networkString),
12
+ let cardHolderName = cardData["cardHolderName"] as? String, !cardHolderName.isEmpty,
13
+ let lastDigits = cardData["lastDigits"] as? String, !lastDigits.isEmpty,
14
+ let cardDescription = cardData["cardDescription"] as? String, !cardDescription.isEmpty else {
15
+ throw CardInfoError.invalidData(description: "Required data fields are missing or invalid.")
16
+ }
17
+
18
+ self.network = network
19
+ self.cardHolderName = cardHolderName
20
+ self.lastDigits = lastDigits
21
+ self.cardDescription = cardDescription
22
+ }
23
+
24
+ private static func getNetwork(from identifier: String) -> PKPaymentNetwork? {
25
+ switch identifier.lowercased() {
26
+ case "visa": return .visa
27
+ case "mastercard": return .masterCard
28
+ case "amex": return .amex
29
+ case "discover": return .discover
30
+ default: return nil
31
+ }
32
+ }
33
+ }
34
+
35
+ enum CardInfoError: Error {
36
+ case invalidData(description: String)
37
+ }
@@ -0,0 +1,27 @@
1
+ struct WalletEncryptedPayload {
2
+ let activationData: Data?
3
+ let ephemeralPublicKey: Data?
4
+ let encryptedPassData: Data?
5
+
6
+ init(data: NSDictionary) throws {
7
+ guard let activationDataString = data["activationData"] as? String, !activationDataString.isEmpty,
8
+ let ephemeralPublicKeyString = data["ephemeralPublicKey"] as? String, !ephemeralPublicKeyString.isEmpty,
9
+ let encryptedPassDataString = data["encryptedPassData"] as? String, !encryptedPassDataString.isEmpty else {
10
+ throw WalletPayloadError.invalidData(description: "Required data fields are missing or invalid.")
11
+ }
12
+
13
+ guard let activationData = Data(base64Encoded: activationDataString, options: .ignoreUnknownCharacters),
14
+ let ephemeralPublicKey = Data(base64Encoded: ephemeralPublicKeyString, options: .ignoreUnknownCharacters),
15
+ let encryptedPassData = Data(base64Encoded: encryptedPassDataString, options: .ignoreUnknownCharacters) else {
16
+ throw WalletPayloadError.invalidData(description: "Data encoding failed or data is corrupted.")
17
+ }
18
+
19
+ self.activationData = activationData
20
+ self.ephemeralPublicKey = ephemeralPublicKey
21
+ self.encryptedPassData = encryptedPassData
22
+ }
23
+ }
24
+
25
+ enum WalletPayloadError: Error {
26
+ case invalidData(description: String)
27
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _expoImage = require("expo-image");
10
+ var _constants = _interopRequireDefault(require("./constants"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ const platform = _reactNative.Platform.OS === 'ios' ? 'ios' : 'android';
15
+ function AddToWalletButton({
16
+ onPress,
17
+ locale,
18
+ buttonStyle
19
+ }) {
20
+ const IconComponent = (0, _react.useMemo)(() => {
21
+ const platformIcons = _constants.default[platform];
22
+ return platformIcons[locale] ?? platformIcons.default;
23
+ }, [locale]);
24
+ return /*#__PURE__*/_react.default.createElement(_reactNative.Pressable, {
25
+ style: [styles.button, buttonStyle],
26
+ onPress: onPress
27
+ }, /*#__PURE__*/_react.default.createElement(_expoImage.Image, {
28
+ source: IconComponent,
29
+ style: styles.image,
30
+ contentFit: "contain"
31
+ }));
32
+ }
33
+ const styles = _reactNative.StyleSheet.create({
34
+ button: {
35
+ width: 200,
36
+ height: 70,
37
+ padding: 10,
38
+ alignItems: 'center',
39
+ justifyContent: 'center'
40
+ },
41
+ image: {
42
+ width: '100%',
43
+ height: '100%'
44
+ }
45
+ });
46
+ var _default = exports.default = AddToWalletButton;
47
+ //# sourceMappingURL=AddToWalletButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_expoImage","_constants","_interopRequireDefault","e","__esModule","default","_getRequireWildcardCache","WeakMap","r","t","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","platform","Platform","OS","AddToWalletButton","onPress","locale","buttonStyle","IconComponent","useMemo","platformIcons","LOCALIZED_BUTTONS","createElement","Pressable","style","styles","button","Image","source","image","contentFit","StyleSheet","create","width","height","padding","alignItems","justifyContent","_default","exports"],"sourceRoot":"../../src","sources":["AddToWalletButton.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.PACKAGE_NAME = void 0;
7
+ var _reactNative = require("react-native");
8
+ const PACKAGE_NAME = exports.PACKAGE_NAME = '@expensify/react-native-wallet';
9
+ // Try catch block to prevent crashing in case the module is not linked.
10
+ // Especialy useful for builds where Google SDK is not available
11
+ // eslint-disable-next-line import/no-mutable-exports
12
+ let Wallet;
13
+ try {
14
+ Wallet = _reactNative.TurboModuleRegistry.getEnforcing('RNWallet');
15
+ } catch (error) {
16
+ if (error instanceof Error) {
17
+ // eslint-disable-next-line no-console
18
+ console.warn(`[${PACKAGE_NAME}] Failed to load Wallet module, ${error.message}`);
19
+ }
20
+ }
21
+ var _default = exports.default = Wallet;
22
+ //# sourceMappingURL=NativeWallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","PACKAGE_NAME","exports","Wallet","TurboModuleRegistry","getEnforcing","error","Error","console","warn","message","_default","default"],"sourceRoot":"../../src","sources":["NativeWallet.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAsEA,MAAMC,YAAY,GAAAC,OAAA,CAAAD,YAAA,GAAG,gCAAgC;AACrD;AACA;AACA;AACA,IAAIE,MAAwB;AAC5B,IAAI;EACFA,MAAM,GAAGC,gCAAmB,CAACC,YAAY,CAAO,UAAU,CAAC;AAC7D,CAAC,CAAC,OAAOC,KAAK,EAAE;EACd,IAAIA,KAAK,YAAYC,KAAK,EAAE;IAC1B;IACAC,OAAO,CAACC,IAAI,CAAC,IAAIR,YAAY,mCAAmCK,KAAK,CAACI,OAAO,EAAE,CAAC;EAClF;AACF;AAAC,IAAAC,QAAA,GAAAT,OAAA,CAAAU,OAAA,GACcT,MAAM","ignoreList":[]}