@xbenjii/react-native-braintree-dropin-ui 2.0.0

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.
@@ -0,0 +1,265 @@
1
+ package com.xbenjii.RNBraintreeDropIn;
2
+
3
+ import android.app.Activity;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.fragment.app.FragmentActivity;
7
+
8
+ import com.braintreepayments.api.BraintreeClient;
9
+ import com.braintreepayments.api.Card;
10
+ import com.braintreepayments.api.CardClient;
11
+ import com.braintreepayments.api.DropInClient;
12
+ import com.braintreepayments.api.DropInListener;
13
+ import com.braintreepayments.api.DropInPaymentMethod;
14
+ import com.braintreepayments.api.ThreeDSecureRequest;
15
+ import com.braintreepayments.api.UserCanceledException;
16
+ import com.facebook.react.bridge.ReactApplicationContext;
17
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
18
+ import com.facebook.react.bridge.ReactMethod;
19
+ import com.facebook.react.bridge.ReadableMap;
20
+ import com.facebook.react.bridge.Arguments;
21
+ import com.facebook.react.bridge.WritableMap;
22
+ import com.facebook.react.bridge.Promise;
23
+ import com.braintreepayments.api.DropInRequest;
24
+ import com.braintreepayments.api.DropInResult;
25
+ import com.braintreepayments.api.PaymentMethodNonce;
26
+ import com.braintreepayments.api.CardNonce;
27
+ import com.braintreepayments.api.ThreeDSecureInfo;
28
+ import com.braintreepayments.api.GooglePayRequest;
29
+ import com.google.android.gms.wallet.TransactionInfo;
30
+ import com.google.android.gms.wallet.WalletConstants;
31
+
32
+ import java.util.Objects;
33
+
34
+ public class RNBraintreeDropInModule extends ReactContextBaseJavaModule {
35
+ private boolean isVerifyingThreeDSecure = false;
36
+ private static DropInClient dropInClient = null;
37
+ private static String clientToken = null;
38
+
39
+ public static void initDropInClient(FragmentActivity activity) {
40
+ dropInClient = new DropInClient(activity, callback -> {
41
+ if (clientToken != null) {
42
+ callback.onSuccess(clientToken);
43
+ } else {
44
+ callback.onFailure(new Exception("Client token is null"));
45
+ }
46
+ });
47
+ }
48
+
49
+ public RNBraintreeDropInModule(ReactApplicationContext reactContext) {
50
+ super(reactContext);
51
+ }
52
+
53
+ @ReactMethod
54
+ public void show(final ReadableMap options, final Promise promise) {
55
+ isVerifyingThreeDSecure = false;
56
+
57
+ if (!options.hasKey("clientToken")) {
58
+ promise.reject("NO_CLIENT_TOKEN", "You must provide a client token");
59
+ return;
60
+ }
61
+
62
+ FragmentActivity currentActivity = (FragmentActivity) getCurrentActivity();
63
+ if (currentActivity == null) {
64
+ promise.reject("NO_ACTIVITY", "There is no current activity");
65
+ return;
66
+ }
67
+
68
+ DropInRequest dropInRequest = new DropInRequest();
69
+
70
+ if(options.hasKey("vaultManager")) {
71
+ dropInRequest.setVaultManagerEnabled(options.getBoolean("vaultManager"));
72
+ }
73
+
74
+ if(options.hasKey("googlePay") && options.getBoolean("googlePay")){
75
+ GooglePayRequest googlePayRequest = new GooglePayRequest();
76
+ googlePayRequest.setTransactionInfo(TransactionInfo.newBuilder()
77
+ .setTotalPrice(Objects.requireNonNull(options.getString("orderTotal")))
78
+ .setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
79
+ .setCurrencyCode(Objects.requireNonNull(options.getString("currencyCode")))
80
+ .build());
81
+ googlePayRequest.setBillingAddressRequired(true);
82
+ googlePayRequest.setGoogleMerchantId(options.getString("googlePayMerchantId"));
83
+
84
+ dropInRequest.setGooglePayDisabled(false);
85
+ dropInRequest.setGooglePayRequest(googlePayRequest);
86
+ }else{
87
+ dropInRequest.setGooglePayDisabled(true);
88
+ }
89
+
90
+ if(options.hasKey("cardDisabled")) {
91
+ dropInRequest.setCardDisabled(options.getBoolean("cardDisabled"));
92
+ }
93
+
94
+ if (options.hasKey("threeDSecure")) {
95
+ final ReadableMap threeDSecureOptions = options.getMap("threeDSecure");
96
+ if (threeDSecureOptions == null || !threeDSecureOptions.hasKey("amount")) {
97
+ promise.reject("NO_3DS_AMOUNT", "You must provide an amount for 3D Secure");
98
+ return;
99
+ }
100
+
101
+ isVerifyingThreeDSecure = true;
102
+
103
+ ThreeDSecureRequest threeDSecureRequest = new ThreeDSecureRequest();
104
+ threeDSecureRequest.setAmount(threeDSecureOptions.getString("amount"));
105
+
106
+ dropInRequest.setThreeDSecureRequest(threeDSecureRequest);
107
+ }
108
+
109
+ dropInRequest.setPayPalDisabled(!options.hasKey("payPal") || !options.getBoolean("payPal"));
110
+
111
+ clientToken = options.getString("clientToken");
112
+
113
+ if (dropInClient == null) {
114
+ promise.reject(
115
+ "DROP_IN_CLIENT_UNINITIALIZED",
116
+ "Did you forget to call RNBraintreeDropInModule.initDropInClient(this) in MainActivity.onCreate?"
117
+ );
118
+ return;
119
+ }
120
+ dropInClient.setListener(new DropInListener() {
121
+ @Override
122
+ public void onDropInSuccess(@NonNull DropInResult dropInResult) {
123
+ PaymentMethodNonce paymentMethodNonce = dropInResult.getPaymentMethodNonce();
124
+
125
+ if (isVerifyingThreeDSecure && paymentMethodNonce instanceof CardNonce) {
126
+ CardNonce cardNonce = (CardNonce) paymentMethodNonce;
127
+ ThreeDSecureInfo threeDSecureInfo = cardNonce.getThreeDSecureInfo();
128
+ if (!threeDSecureInfo.isLiabilityShiftPossible()) {
129
+ promise.reject("3DSECURE_NOT_ABLE_TO_SHIFT_LIABILITY", "3D Secure liability cannot be shifted");
130
+ } else if (!threeDSecureInfo.isLiabilityShifted()) {
131
+ promise.reject("3DSECURE_LIABILITY_NOT_SHIFTED", "3D Secure liability was not shifted");
132
+ } else {
133
+ resolvePayment(dropInResult, promise);
134
+ }
135
+ } else {
136
+ resolvePayment(dropInResult, promise);
137
+ }
138
+ }
139
+
140
+ @Override
141
+ public void onDropInFailure(@NonNull Exception exception) {
142
+ if (exception instanceof UserCanceledException) {
143
+ promise.reject("USER_CANCELLATION", "The user cancelled");
144
+ } else {
145
+ promise.reject(exception.getMessage(), exception.getMessage());
146
+ }
147
+ }
148
+ });
149
+ dropInClient.launchDropIn(dropInRequest);
150
+ }
151
+
152
+ @ReactMethod
153
+ public void fetchMostRecentPaymentMethod(final String clientToken, final Promise promise) {
154
+ FragmentActivity currentActivity = (FragmentActivity) getCurrentActivity();
155
+
156
+ if (currentActivity == null) {
157
+ promise.reject("NO_ACTIVITY", "There is no current activity");
158
+ return;
159
+ }
160
+
161
+ if (dropInClient == null) {
162
+ promise.reject(
163
+ "DROP_IN_CLIENT_UNINITIALIZED",
164
+ "Did you forget to call RNBraintreeDropInModule.initDropInClient(this) in MainActivity.onCreate?"
165
+ );
166
+ return;
167
+ }
168
+
169
+ RNBraintreeDropInModule.clientToken = clientToken;
170
+
171
+ dropInClient.fetchMostRecentPaymentMethod(currentActivity, (dropInResult, error) -> {
172
+ if (error != null) {
173
+ promise.reject(error.getMessage(), error.getMessage());
174
+ } else if (dropInResult == null) {
175
+ promise.reject("NO_DROP_IN_RESULT", "dropInResult is null");
176
+ } else {
177
+ resolvePayment(dropInResult, promise);
178
+ }
179
+ });
180
+ }
181
+
182
+ @ReactMethod
183
+ public void tokenizeCard(final String clientToken, final ReadableMap cardInfo, final Promise promise) {
184
+ if (clientToken == null) {
185
+ promise.reject("NO_CLIENT_TOKEN", "You must provide a client token");
186
+ return;
187
+ }
188
+
189
+ if (
190
+ !cardInfo.hasKey("number") ||
191
+ !cardInfo.hasKey("expirationMonth") ||
192
+ !cardInfo.hasKey("expirationYear") ||
193
+ !cardInfo.hasKey("cvv") ||
194
+ !cardInfo.hasKey("postalCode")
195
+ ) {
196
+ promise.reject("INVALID_CARD_INFO", "Invalid card info");
197
+ return;
198
+ }
199
+
200
+ Activity currentActivity = getCurrentActivity();
201
+
202
+ if (currentActivity == null) {
203
+ promise.reject("NO_ACTIVITY", "There is no current activity");
204
+ return;
205
+ }
206
+
207
+ BraintreeClient braintreeClient = new BraintreeClient(getCurrentActivity(), clientToken);
208
+ CardClient cardClient = new CardClient(braintreeClient);
209
+
210
+ Card card = new Card();
211
+ card.setNumber(cardInfo.getString("number"));
212
+ card.setExpirationMonth(cardInfo.getString("expirationMonth"));
213
+ card.setExpirationYear(cardInfo.getString("expirationYear"));
214
+ card.setCvv(cardInfo.getString("cvv"));
215
+ card.setPostalCode(cardInfo.getString("postalCode"));
216
+
217
+ cardClient.tokenize(card, (cardNonce, error) -> {
218
+ if (error != null) {
219
+ promise.reject(error.getMessage(), error.getMessage());
220
+ } else if (cardNonce == null) {
221
+ promise.reject("NO_CARD_NONCE", "Card nonce is null");
222
+ } else {
223
+ promise.resolve(cardNonce.getString());
224
+ }
225
+ });
226
+ }
227
+
228
+ private void resolvePayment(DropInResult dropInResult, Promise promise) {
229
+ String deviceData = dropInResult.getDeviceData();
230
+ PaymentMethodNonce paymentMethodNonce = dropInResult.getPaymentMethodNonce();
231
+
232
+ WritableMap jsResult = Arguments.createMap();
233
+
234
+ if (paymentMethodNonce == null) {
235
+ promise.resolve(null);
236
+ return;
237
+ }
238
+
239
+ Activity currentActivity = getCurrentActivity();
240
+ if (currentActivity == null) {
241
+ promise.reject("NO_ACTIVITY", "There is no current activity");
242
+ return;
243
+ }
244
+
245
+ DropInPaymentMethod dropInPaymentMethod = dropInResult.getPaymentMethodType();
246
+ if (dropInPaymentMethod == null) {
247
+ promise.reject("NO_PAYMENT_METHOD", "There is no payment method");
248
+ return;
249
+ }
250
+
251
+ jsResult.putString("nonce", paymentMethodNonce.getString());
252
+ jsResult.putString("type", currentActivity.getString(dropInPaymentMethod.getLocalizedName()));
253
+ jsResult.putString("description", dropInResult.getPaymentDescription());
254
+ jsResult.putBoolean("isDefault", paymentMethodNonce.isDefault());
255
+ jsResult.putString("deviceData", deviceData);
256
+
257
+ promise.resolve(jsResult);
258
+ }
259
+
260
+ @NonNull
261
+ @Override
262
+ public String getName() {
263
+ return "RNBraintreeDropIn";
264
+ }
265
+ }
@@ -0,0 +1,27 @@
1
+ package com.xbenjii.RNBraintreeDropIn;
2
+
3
+ import java.util.Arrays;
4
+ import java.util.Collections;
5
+ import java.util.List;
6
+
7
+ import com.facebook.react.ReactPackage;
8
+ import com.facebook.react.bridge.NativeModule;
9
+ import com.facebook.react.bridge.ReactApplicationContext;
10
+ import com.facebook.react.uimanager.ViewManager;
11
+ import com.facebook.react.bridge.JavaScriptModule;
12
+ public class RNBraintreeDropInPackage implements ReactPackage {
13
+ @Override
14
+ public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
15
+ return Arrays.<NativeModule>asList(new RNBraintreeDropInModule(reactContext));
16
+ }
17
+
18
+ // Deprecated from RN 0.47
19
+ public List<Class<? extends JavaScriptModule>> createJSModules() {
20
+ return Collections.emptyList();
21
+ }
22
+
23
+ @Override
24
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
25
+ return Collections.emptyList();
26
+ }
27
+ }
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { NativeModules } from 'react-native';
2
+
3
+ const { RNBraintreeDropIn } = NativeModules;
4
+
5
+ export default RNBraintreeDropIn;
package/index.js.flow ADDED
@@ -0,0 +1,42 @@
1
+ type ShowOptions = {|
2
+ clientToken: string,
3
+ threeDSecure?: {|
4
+ amount: number,
5
+ |},
6
+ vaultManager?: boolean,
7
+ cardDisabled?: boolean,
8
+ googlePay?: boolean,
9
+ orderTotal?: string,
10
+ currencyCode?: string,
11
+ googlePayMerchantId?: string,
12
+ payPal?: boolean,
13
+ applePay?: boolean,
14
+ merchantIdentifier?: string,
15
+ countryCode?: string,
16
+ merchantName?: string,
17
+ darkTheme?: boolean,
18
+ fontFamily?: string,
19
+ boldFontFamily?: string,
20
+ |};
21
+
22
+ type CardInfo = {|
23
+ number: string,
24
+ expirationMonth: string,
25
+ expirationYear: string,
26
+ cvv: string,
27
+ postalCode: string,
28
+ |};
29
+
30
+ type ShowResult = {|
31
+ nonce: string,
32
+ description: string,
33
+ type: string,
34
+ isDefault: boolean,
35
+ deviceData: string,
36
+ |};
37
+
38
+ declare module.exports: {
39
+ show: (options: ShowOptions) => Promise<ShowResult>,
40
+ fetchMostRecentPaymentMethod: (clientToken: string) => Promise<ShowResult>,
41
+ tokenizeCard: (clientToken: string, cardInfo: CardInfo) => Promise<string>,
42
+ };
@@ -0,0 +1,40 @@
1
+ @import UIKit;
2
+ @import PassKit;
3
+
4
+ #if __has_include("RCTBridgeModule.h")
5
+ #import "RCTBridgeModule.h"
6
+ #else
7
+ #import <React/RCTBridgeModule.h>
8
+ #endif
9
+
10
+ #import "BraintreeCore.h"
11
+ #import "BraintreeDropIn.h"
12
+ #import "BTCardNonce.h"
13
+ #import "BTDataCollector.h"
14
+
15
+ #import "BraintreeApplePay.h"
16
+
17
+ @interface RNBraintreeDropIn : NSObject <RCTBridgeModule, PKPaymentAuthorizationViewControllerDelegate>
18
+
19
+ @property (nonatomic, strong) UIViewController *_Nonnull reactRoot;
20
+
21
+ // Retain your `BTDataCollector` instance for your entire application lifecycle.
22
+ @property (nonatomic, strong) BTDataCollector *_Nonnull dataCollector;
23
+
24
+ @property (nonatomic, strong) BTAPIClient *_Nonnull braintreeClient;
25
+
26
+ @property (nonatomic, strong) PKPaymentRequest *_Nonnull paymentRequest;
27
+
28
+ @property (nonatomic, strong) PKPaymentAuthorizationViewController *_Nonnull viewController;
29
+
30
+ @property (nonatomic, strong) NSString * _Nonnull deviceDataCollector;
31
+
32
+ @property (nonatomic) RCTPromiseResolveBlock _Nonnull resolve;
33
+
34
+ @property (nonatomic) RCTPromiseRejectBlock _Nonnull reject;
35
+
36
+ @property (nonatomic, assign) BOOL applePayAuthorized;
37
+
38
+ + (void)resolvePayment:(BTDropInResult* _Nullable)result deviceData:(NSString * _Nonnull)deviceDataCollector resolver:(RCTPromiseResolveBlock _Nonnull)resolve;
39
+
40
+ @end
@@ -0,0 +1,280 @@
1
+ #import "RNBraintreeDropIn.h"
2
+ #import <React/RCTUtils.h>
3
+ #import "BTThreeDSecureRequest.h"
4
+
5
+ @implementation RNBraintreeDropIn
6
+
7
+ - (dispatch_queue_t)methodQueue
8
+ {
9
+ return dispatch_get_main_queue();
10
+ }
11
+ RCT_EXPORT_MODULE(RNBraintreeDropIn)
12
+
13
+ RCT_EXPORT_METHOD(show:(NSDictionary*)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
14
+ {
15
+ BTDropInColorScheme colorScheme;
16
+
17
+ if([options[@"darkTheme"] boolValue]){
18
+ if (@available(iOS 13.0, *)) {
19
+ colorScheme = BTDropInColorSchemeDynamic;
20
+ } else {
21
+ colorScheme = BTDropInColorSchemeDark;
22
+ }
23
+ } else {
24
+ colorScheme = BTDropInColorSchemeLight;
25
+ }
26
+
27
+ BTDropInUICustomization *uiCustomization = [[BTDropInUICustomization alloc] initWithColorScheme:colorScheme];
28
+
29
+ if(options[@"fontFamily"]){
30
+ uiCustomization.fontFamily = options[@"fontFamily"];
31
+ }
32
+ if(options[@"boldFontFamily"]){
33
+ uiCustomization.boldFontFamily = options[@"boldFontFamily"];
34
+ }
35
+
36
+ self.resolve = resolve;
37
+ self.reject = reject;
38
+ self.applePayAuthorized = NO;
39
+
40
+ NSString* clientToken = options[@"clientToken"];
41
+ if (!clientToken) {
42
+ reject(@"NO_CLIENT_TOKEN", @"You must provide a client token", nil);
43
+ return;
44
+ }
45
+
46
+ BTDropInRequest *request = [[BTDropInRequest alloc] init];
47
+ request.uiCustomization = uiCustomization;
48
+
49
+ NSDictionary* threeDSecureOptions = options[@"threeDSecure"];
50
+ if (threeDSecureOptions) {
51
+ NSNumber* threeDSecureAmount = threeDSecureOptions[@"amount"];
52
+ if (!threeDSecureAmount) {
53
+ reject(@"NO_3DS_AMOUNT", @"You must provide an amount for 3D Secure", nil);
54
+ return;
55
+ }
56
+
57
+ BTThreeDSecureRequest *threeDSecureRequest = [[BTThreeDSecureRequest alloc] init];
58
+ threeDSecureRequest.amount = [NSDecimalNumber decimalNumberWithString:threeDSecureAmount.stringValue];
59
+ request.threeDSecureRequest = threeDSecureRequest;
60
+
61
+ }
62
+
63
+ BTAPIClient *apiClient = [[BTAPIClient alloc] initWithAuthorization:clientToken];
64
+ self.dataCollector = [[BTDataCollector alloc] initWithAPIClient:apiClient];
65
+ [self.dataCollector collectDeviceData:^(NSString * _Nonnull deviceDataCollector) {
66
+ // Save deviceData
67
+ self.deviceDataCollector = deviceDataCollector;
68
+ }];
69
+
70
+ if([options[@"vaultManager"] boolValue]){
71
+ request.vaultManager = YES;
72
+ }
73
+
74
+ if([options[@"cardDisabled"] boolValue]){
75
+ request.cardDisabled = YES;
76
+ }
77
+
78
+ if([options[@"applePay"] boolValue]){
79
+ NSString* merchantIdentifier = options[@"merchantIdentifier"];
80
+ NSString* countryCode = options[@"countryCode"];
81
+ NSString* currencyCode = options[@"currencyCode"];
82
+ NSString* merchantName = options[@"merchantName"];
83
+ NSDecimalNumber* orderTotal = [NSDecimalNumber decimalNumberWithDecimal:[options[@"orderTotal"] decimalValue]];
84
+ if(!merchantIdentifier || !countryCode || !currencyCode || !merchantName || !orderTotal){
85
+ reject(@"MISSING_OPTIONS", @"Not all required Apple Pay options were provided", nil);
86
+ return;
87
+ }
88
+ self.braintreeClient = [[BTAPIClient alloc] initWithAuthorization:clientToken];
89
+
90
+ self.paymentRequest = [[PKPaymentRequest alloc] init];
91
+ self.paymentRequest.merchantIdentifier = merchantIdentifier;
92
+ self.paymentRequest.merchantCapabilities = PKMerchantCapability3DS;
93
+ self.paymentRequest.countryCode = countryCode;
94
+ self.paymentRequest.currencyCode = currencyCode;
95
+ self.paymentRequest.supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkVisa, PKPaymentNetworkMasterCard, PKPaymentNetworkDiscover, PKPaymentNetworkChinaUnionPay];
96
+ self.paymentRequest.paymentSummaryItems =
97
+ @[
98
+ [PKPaymentSummaryItem summaryItemWithLabel:merchantName amount:orderTotal]
99
+ ];
100
+
101
+ self.viewController = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest: self.paymentRequest];
102
+ self.viewController.delegate = self;
103
+ }else{
104
+ request.applePayDisabled = YES;
105
+ }
106
+
107
+ if(![options[@"payPal"] boolValue]){ //disable paypal
108
+ request.paypalDisabled = YES;
109
+ }
110
+
111
+ BTDropInController *dropIn = [[BTDropInController alloc] initWithAuthorization:clientToken request:request handler:^(BTDropInController * _Nonnull controller, BTDropInResult * _Nullable result, NSError * _Nullable error) {
112
+ [self.reactRoot dismissViewControllerAnimated:YES completion:nil];
113
+
114
+ //result.paymentOptionType == .ApplePay
115
+ //NSLog(@"paymentOptionType = %ld", result.paymentOptionType);
116
+
117
+ if (error != nil) {
118
+ reject(error.localizedDescription, error.localizedDescription, error);
119
+ } else if (result.canceled) {
120
+ reject(@"USER_CANCELLATION", @"The user cancelled", nil);
121
+ } else {
122
+ if (threeDSecureOptions && [result.paymentMethod isKindOfClass:[BTCardNonce class]]) {
123
+ BTCardNonce *cardNonce = (BTCardNonce *)result.paymentMethod;
124
+ if (!cardNonce.threeDSecureInfo.liabilityShiftPossible && cardNonce.threeDSecureInfo.wasVerified) {
125
+ reject(@"3DSECURE_NOT_ABLE_TO_SHIFT_LIABILITY", @"3D Secure liability cannot be shifted", nil);
126
+ } else if (!cardNonce.threeDSecureInfo.liabilityShifted && cardNonce.threeDSecureInfo.wasVerified) {
127
+ reject(@"3DSECURE_LIABILITY_NOT_SHIFTED", @"3D Secure liability was not shifted", nil);
128
+ } else{
129
+ [[self class] resolvePayment:result deviceData:self.deviceDataCollector resolver:resolve];
130
+ }
131
+ } else if(result.paymentMethod == nil && (result.paymentMethodType == 16 || result.paymentMethodType == 17 || result.paymentMethodType == 18)){ //Apple Pay
132
+ // UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
133
+ // [ctrl presentViewController:self.viewController animated:YES completion:nil];
134
+ UIViewController *rootViewController = RCTPresentedViewController();
135
+ [rootViewController presentViewController:self.viewController animated:YES completion:nil];
136
+ } else{
137
+ [[self class] resolvePayment:result deviceData:self.deviceDataCollector resolver:resolve];
138
+ }
139
+ }
140
+ }];
141
+
142
+ if (dropIn != nil) {
143
+ [self.reactRoot presentViewController:dropIn animated:YES completion:nil];
144
+ } else {
145
+ reject(@"INVALID_CLIENT_TOKEN", @"The client token seems invalid", nil);
146
+ }
147
+ }
148
+
149
+ RCT_EXPORT_METHOD(fetchMostRecentPaymentMethod:(NSString*)clientToken
150
+ resolver:(RCTPromiseResolveBlock)resolve
151
+ rejecter:(RCTPromiseRejectBlock)reject)
152
+ {
153
+ [BTDropInResult mostRecentPaymentMethodForClientToken:clientToken completion:^(BTDropInResult * _Nullable result, NSError * _Nullable error) {
154
+ if (error != nil) {
155
+ reject(error.localizedDescription, error.localizedDescription, error);
156
+ } else if (result.canceled) {
157
+ reject(@"USER_CANCELLATION", @"The user cancelled", nil);
158
+ } else {
159
+ [[self class] resolvePayment:result deviceData:result.deviceData resolver:resolve];
160
+ }
161
+ }];
162
+ }
163
+
164
+ RCT_EXPORT_METHOD(tokenizeCard:(NSString*)clientToken
165
+ info:(NSDictionary*)cardInfo
166
+ resolver:(RCTPromiseResolveBlock)resolve
167
+ rejecter:(RCTPromiseRejectBlock)reject)
168
+ {
169
+ NSString *number = cardInfo[@"number"];
170
+ NSString *expirationMonth = cardInfo[@"expirationMonth"];
171
+ NSString *expirationYear = cardInfo[@"expirationYear"];
172
+ NSString *cvv = cardInfo[@"cvv"];
173
+ NSString *postalCode = cardInfo[@"postalCode"];
174
+
175
+ if (!number || !expirationMonth || !expirationYear || !cvv || !postalCode) {
176
+ reject(@"INVALID_CARD_INFO", @"Invalid card info", nil);
177
+ return;
178
+ }
179
+
180
+ BTAPIClient *braintreeClient = [[BTAPIClient alloc] initWithAuthorization:clientToken];
181
+ BTCardClient *cardClient = [[BTCardClient alloc] initWithAPIClient:braintreeClient];
182
+ BTCard *card = [[BTCard alloc] init];
183
+ card.number = number;
184
+ card.expirationMonth = expirationMonth;
185
+ card.expirationYear = expirationYear;
186
+ card.cvv = cvv;
187
+ card.postalCode = postalCode;
188
+
189
+ [cardClient tokenizeCard:card
190
+ completion:^(BTCardNonce *tokenizedCard, NSError *error) {
191
+ if (error == nil) {
192
+ resolve(tokenizedCard.nonce);
193
+ } else {
194
+ reject(@"TOKENIZE_ERROR", @"Error tokenizing card.", error);
195
+ }
196
+ }];
197
+ }
198
+
199
+ - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
200
+ didAuthorizePayment:(PKPayment *)payment
201
+ handler:(nonnull void (^)(PKPaymentAuthorizationResult * _Nonnull))completion
202
+ {
203
+
204
+ // Example: Tokenize the Apple Pay payment
205
+ BTApplePayClient *applePayClient = [[BTApplePayClient alloc]
206
+ initWithAPIClient:self.braintreeClient];
207
+ [applePayClient tokenizeApplePayPayment:payment
208
+ completion:^(BTApplePayCardNonce *tokenizedApplePayPayment,
209
+ NSError *error) {
210
+ if (tokenizedApplePayPayment) {
211
+ // On success, send nonce to your server for processing.
212
+ // If applicable, address information is accessible in `payment`.
213
+ // NSLog(@"description = %@", tokenizedApplePayPayment.localizedDescription);
214
+
215
+ completion([[PKPaymentAuthorizationResult alloc] initWithStatus:PKPaymentAuthorizationStatusSuccess errors:nil]);
216
+ self.applePayAuthorized = YES;
217
+
218
+
219
+ NSMutableDictionary* result = [NSMutableDictionary new];
220
+ [result setObject:tokenizedApplePayPayment.nonce forKey:@"nonce"];
221
+ [result setObject:@"Apple Pay" forKey:@"type"];
222
+ [result setObject:[NSString stringWithFormat: @"%@ %@", @"", tokenizedApplePayPayment.type] forKey:@"description"];
223
+ [result setObject:[NSNumber numberWithBool:false] forKey:@"isDefault"];
224
+ [result setObject:self.deviceDataCollector forKey:@"deviceData"];
225
+
226
+ self.resolve(result);
227
+
228
+ } else {
229
+ // Tokenization failed. Check `error` for the cause of the failure.
230
+
231
+ // Indicate failure via the completion callback:
232
+ completion([[PKPaymentAuthorizationResult alloc] initWithStatus:PKPaymentAuthorizationStatusFailure errors:nil]);
233
+ }
234
+ }];
235
+ }
236
+
237
+ // Be sure to implement -paymentAuthorizationViewControllerDidFinish:
238
+ - (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller{
239
+ [self.reactRoot dismissViewControllerAnimated:YES completion:nil];
240
+ if(self.applePayAuthorized == NO){
241
+ self.reject(@"USER_CANCELLATION", @"The user cancelled", nil);
242
+ }
243
+ }
244
+
245
+ + (void)resolvePayment:(BTDropInResult* _Nullable)result deviceData:(NSString * _Nonnull)deviceDataCollector resolver:(RCTPromiseResolveBlock _Nonnull)resolve {
246
+ //NSLog(@"result = %@", result);
247
+
248
+ if (!result) {
249
+ resolve(nil);
250
+ return;
251
+ }
252
+
253
+ NSMutableDictionary* jsResult = [NSMutableDictionary new];
254
+
255
+ //NSLog(@"paymentMethod = %@", result.paymentMethod);
256
+ //NSLog(@"paymentIcon = %@", result.paymentIcon);
257
+
258
+ [jsResult setObject:result.paymentMethod.nonce forKey:@"nonce"];
259
+ [jsResult setObject:result.paymentMethod.type forKey:@"type"];
260
+ [jsResult setObject:result.paymentDescription forKey:@"description"];
261
+ [jsResult setObject:[NSNumber numberWithBool:result.paymentMethod.isDefault] forKey:@"isDefault"];
262
+ [jsResult setObject:deviceDataCollector forKey:@"deviceData"];
263
+
264
+ resolve(jsResult);
265
+ }
266
+
267
+ - (UIViewController*)reactRoot {
268
+ UIViewController *root = [UIApplication sharedApplication].keyWindow.rootViewController;
269
+ UIViewController *maybeModal = root.presentedViewController;
270
+
271
+ UIViewController *modalRoot = root;
272
+
273
+ if (maybeModal != nil) {
274
+ modalRoot = maybeModal;
275
+ }
276
+
277
+ return modalRoot;
278
+ }
279
+
280
+ @end