diff --git a/README.md b/README.md index 2ced92d..6b3a01e 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,24 @@ BraintreeDropIn.fetchMostRecentPaymentMethod(clientToken) }); ``` +### Tokenize card + +```javascript +import BraintreeDropIn from 'react-native-braintree-dropin-ui'; + +BraintreeDropIn.tokenizeCard(clientToken, { + number: '4111111111111111', + expirationMonth: '10', + expirationYear: '23', + cvv: '123', + postalCode: '12345', +}) +.then(cardNonce => console.log(cardNonce)) +.catch((error) => { + // Handle error +}); +``` + ### Custom Fonts ``` BraintreeDropIn.show({ diff --git a/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java b/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java index e59b8f7..bcce697 100644 --- a/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java +++ b/android/src/main/java/tech/power/RNBraintreeDropIn/RNBraintreeDropInModule.java @@ -5,6 +5,9 @@ import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; +import com.braintreepayments.api.BraintreeClient; +import com.braintreepayments.api.Card; +import com.braintreepayments.api.CardClient; import com.braintreepayments.api.DropInClient; import com.braintreepayments.api.DropInListener; import com.braintreepayments.api.DropInPaymentMethod; @@ -176,6 +179,52 @@ public void fetchMostRecentPaymentMethod(final String clientToken, final Promise }); } + @ReactMethod + public void tokenizeCard(final String clientToken, final ReadableMap cardInfo, final Promise promise) { + if (clientToken == null) { + promise.reject("NO_CLIENT_TOKEN", "You must provide a client token"); + return; + } + + if ( + !cardInfo.hasKey("number") || + !cardInfo.hasKey("expirationMonth") || + !cardInfo.hasKey("expirationYear") || + !cardInfo.hasKey("cvv") || + !cardInfo.hasKey("postalCode") + ) { + promise.reject("INVALID_CARD_INFO", "Invalid card info"); + return; + } + + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + promise.reject("NO_ACTIVITY", "There is no current activity"); + return; + } + + BraintreeClient braintreeClient = new BraintreeClient(getCurrentActivity(), clientToken); + CardClient cardClient = new CardClient(braintreeClient); + + Card card = new Card(); + card.setNumber(cardInfo.getString("number")); + card.setExpirationMonth(cardInfo.getString("expirationMonth")); + card.setExpirationYear(cardInfo.getString("expirationYear")); + card.setCvv(cardInfo.getString("cvv")); + card.setPostalCode(cardInfo.getString("postalCode")); + + cardClient.tokenize(card, (cardNonce, error) -> { + if (error != null) { + promise.reject(error.getMessage(), error.getMessage()); + } else if (cardNonce == null) { + promise.reject("NO_CARD_NONCE", "Card nonce is null"); + } else { + promise.resolve(cardNonce.getString()); + } + }); + } + private void resolvePayment(DropInResult dropInResult, Promise promise) { String deviceData = dropInResult.getDeviceData(); PaymentMethodNonce paymentMethodNonce = dropInResult.getPaymentMethodNonce(); diff --git a/index.js.flow b/index.js.flow index 579e6f4..a46e7b5 100644 --- a/index.js.flow +++ b/index.js.flow @@ -19,6 +19,14 @@ type ShowOptions = {| boldFontFamily?: string, |}; +type CardInfo = {| + number: string, + expirationMonth: string, + expirationYear: string, + cvv: string, + postalCode: string, +|}; + type ShowResult = {| nonce: string, description: string, @@ -30,4 +38,5 @@ type ShowResult = {| declare module.exports: { show: (options: ShowOptions) => Promise, fetchMostRecentPaymentMethod: (clientToken: string) => Promise, + tokenizeCard: (clientToken: string, cardInfo: CardInfo) => Promise, }; diff --git a/ios/RNBraintreeDropIn.m b/ios/RNBraintreeDropIn.m index 17ea5a4..9380e1c 100644 --- a/ios/RNBraintreeDropIn.m +++ b/ios/RNBraintreeDropIn.m @@ -161,6 +161,41 @@ - (dispatch_queue_t)methodQueue }]; } +RCT_EXPORT_METHOD(tokenizeCard:(NSString*)clientToken + info:(NSDictionary*)cardInfo + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + NSString *number = cardInfo[@"number"]; + NSString *expirationMonth = cardInfo[@"expirationMonth"]; + NSString *expirationYear = cardInfo[@"expirationYear"]; + NSString *cvv = cardInfo[@"cvv"]; + NSString *postalCode = cardInfo[@"postalCode"]; + + if (!number || !expirationMonth || !expirationYear || !cvv || !postalCode) { + reject(@"INVALID_CARD_INFO", @"Invalid card info", nil); + return; + } + + BTAPIClient *braintreeClient = [[BTAPIClient alloc] initWithAuthorization:clientToken]; + BTCardClient *cardClient = [[BTCardClient alloc] initWithAPIClient:braintreeClient]; + BTCard *card = [[BTCard alloc] init]; + card.number = number; + card.expirationMonth = expirationMonth; + card.expirationYear = expirationYear; + card.cvv = cvv; + card.postalCode = postalCode; + + [cardClient tokenizeCard:card + completion:^(BTCardNonce *tokenizedCard, NSError *error) { + if (error == nil) { + resolve(tokenizedCard.nonce); + } else { + reject(@"TOKENIZE_ERROR", @"Error tokenizing card.", error); + } + }]; +} + - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didAuthorizePayment:(PKPayment *)payment handler:(nonnull void (^)(PKPaymentAuthorizationResult * _Nonnull))completion