@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.
- package/LICENSE +21 -0
- package/README.md +365 -0
- package/RNBraintreeDropIn.podspec +22 -0
- package/android/build.gradle +50 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java +265 -0
- package/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInPackage.java +27 -0
- package/index.js +5 -0
- package/index.js.flow +42 -0
- package/ios/RNBraintreeDropIn.h +40 -0
- package/ios/RNBraintreeDropIn.m +280 -0
- package/ios/RNBraintreeDropIn.xcodeproj/project.pbxproj +291 -0
- package/ios/RNBraintreeDropIn.xcworkspace/contents.xcworkspacedata +8 -0
- package/package.json +34 -0
- package/react-native.config.js +13 -0
@@ -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
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
|