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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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