@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.
- 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
|