Skip to content

Commit

Permalink
Redirect component (#9)
Browse files Browse the repository at this point in the history
* Implement redirectComponent support for android and ios. Small refactoring to unify code and extract constants
* Update types declarations, remove unused and commented code
  • Loading branch information
EJohnF authored Dec 30, 2020
1 parent 69df860 commit 512bc14
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 112 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ promise.then((data: EncryptedCard) => {
// do something neat
})
```

### Redirect component

Check documentation for native code changes required to support redirects
https://docs.adyen.com/checkout/android/components#redirect-component

1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ repositories {
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation "com.adyen.checkout:redirect:3.8.0"
implementation "com.adyen.checkout:card-ui:3.8.0"
implementation "com.adyen.checkout:3ds2:3.8.0"
implementation 'org.jdeferred.v2:jdeferred-android-aar:2.0.0'
Expand Down
98 changes: 87 additions & 11 deletions android/src/main/java/com/reactlibrary/RNAdyenEncryptorModule.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package com.reactlibrary;

import android.app.Activity;

import androidx.lifecycle.Observer;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;

import com.adyen.checkout.base.ActionComponentData;
import com.adyen.checkout.base.ComponentError;
import com.adyen.checkout.base.model.payments.response.Action;
import com.adyen.checkout.base.model.payments.response.RedirectAction;
import com.adyen.checkout.base.model.payments.response.Threeds2ChallengeAction;
import com.adyen.checkout.base.model.payments.response.Threeds2FingerprintAction;
import com.adyen.checkout.cse.Card;
import com.adyen.checkout.cse.CardEncryptor;
import com.adyen.checkout.cse.EncryptedCard;
import com.adyen.checkout.cse.internal.CardEncryptorImpl;
import com.adyen.checkout.redirect.RedirectComponent;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
Expand All @@ -33,8 +37,11 @@
import com.adyen.checkout.adyen3ds2.Adyen3DS2Component;



public class RNAdyenEncryptorModule extends ReactContextBaseJavaModule {

private final String SUCCESS_CALLBACK = "AdyenCardEncryptedSuccess";
private final String ERROR_CALLBACK = "AdyenCardEncryptedError";
private final ReactApplicationContext reactContext;

public RNAdyenEncryptorModule(ReactApplicationContext reactContext) {
Expand Down Expand Up @@ -71,19 +78,22 @@ public void encryptWithData(ReadableMap cardData) throws Exception {
encryptedCardMap.putString("encryptedExpiryYear", encryptedCard.getEncryptedExpiryYear());
} catch (Exception e) {
encryptedCardMap.putString("error", e.toString());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError", encryptedCardMap);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, encryptedCardMap);
}

reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedSuccess", encryptedCardMap);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(SUCCESS_CALLBACK, encryptedCardMap);
}

private Adyen3DS2Component authenticator;
private RedirectComponent redirectComponent;
private Callback callback;

private interface Callback {
void onSuccess(ActionComponentData data);

void onError(ComponentError error);
}

private Deferred<Adyen3DS2Component, Throwable, Void> getAuthenticator(Callback callback) {
final Deferred<Adyen3DS2Component, Throwable, Void> deferred = new DeferredObject<>();

Expand Down Expand Up @@ -134,22 +144,22 @@ private void dispatchAction(final Action action, final String resultKey) {
public void onSuccess(ActionComponentData data) {
final JSONObject details = data.getDetails();
final String result = details.optString(resultKey);
if (!result.isEmpty()){
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedSuccess", details.toString());
}else{
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError","String is empty?");
if (!result.isEmpty()) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(SUCCESS_CALLBACK, details.toString());
} else {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, "String is empty?");
}
}

@Override
public void onError(ComponentError error) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError", error.getErrorMessage());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, error.getErrorMessage());
}
}).then(new DoneCallback<Adyen3DS2Component>() {
@Override
public void onDone(Adyen3DS2Component authenticator) {
if (authenticator == null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError", "activity is null");
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, "activity is null");
return;
}

Expand All @@ -160,9 +170,9 @@ public void onDone(Adyen3DS2Component authenticator) {
@Override
public void onFail(Throwable result) {
if (result != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError", result.toString());
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, result.toString());
} else {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AdyenCardEncryptedError", "error null");
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, "error null");
}

}
Expand All @@ -185,4 +195,70 @@ public void challenge(final String token, final String paymentData) {
dispatchAction(action, "threeds2.challengeResult");
}

@ReactMethod
public void redirect(final String url, final String paymentData) {
final RedirectAction action = new RedirectAction();
action.setUrl(url);
action.setType(RedirectAction.ACTION_TYPE);
action.setPaymentData(paymentData);

final Deferred<RedirectComponent, Throwable, Void> deferred = new DeferredObject<>();
new Handler(Looper.getMainLooper())
.post(new Runnable() {
@Override
public void run() {
try {
final Activity activity = getCurrentActivity();

if (activity == null) {
deferred.reject(null);
}

final FragmentActivity fragmentActivity = (FragmentActivity) activity;
redirectComponent = RedirectComponent.PROVIDER.get(fragmentActivity);

redirectComponent.observe(fragmentActivity, new Observer<ActionComponentData>() {
@Override
public void onChanged(@Nullable ActionComponentData actionComponentData) {
final JSONObject details = actionComponentData.getDetails();
if (details != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(SUCCESS_CALLBACK, details.toString());
} else {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, "empty return");
}
}
});

redirectComponent.observeErrors(fragmentActivity, new Observer<ComponentError>() {
@Override
public void onChanged(@Nullable ComponentError componentError) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, componentError.getErrorMessage());
}
});
deferred.resolve(redirectComponent);
} catch (Throwable e) {
deferred.reject(e);
}
}
});

deferred.then(new DoneCallback<RedirectComponent>() {
@Override
public void onDone(RedirectComponent redirectComponent) {
if (redirectComponent == null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(ERROR_CALLBACK, "activity is null");
return;
}

final Activity activity = getCurrentActivity();
redirectComponent.handleAction(activity, action);
}
});
}

@ReactMethod
public void getRedirectUrl() {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(SUCCESS_CALLBACK, RedirectComponent.getReturnUrl(getReactApplicationContext()));
}

}
29 changes: 17 additions & 12 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
declare module 'react-native-adyen-encrypt' {
interface CardForm {
cardNumber: string;
securityCode: string;
expiryMonth: string;
expiryYear: string;
cardNumber: string;
securityCode: string;
expiryMonth: string;
expiryYear: string;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars

declare class AdyenEncryptor {
constructor(adyenPublicKey: string);
encryptCard(cardForm: CardForm);
identify(token: String, paymentData: String): Promise<any>;
challenge(token: String, paymentData: String): Promise<String>;
constructor(adyenPublicKey: string);

encryptCard(cardForm: CardForm);

identify(token: String, paymentData: String): Promise<String>;

challenge(token: String, paymentData: String): Promise<String>;

redirect(url: String, paymentData: String): Promise<String>;

getRedirectUrl(): Promise<String>;
}
}

}
5 changes: 5 additions & 0 deletions ios/RNAdyenEncryptor/AdyenThreeDS2Wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ let helperSingleton = HelperThreeDS2()
guard let token = token, let paymentData = paymentData else { return }
helperSingleton.challenger(token, paymentData)
}

@objc public func redirect(_ url: String?, paymentData: String?) {
guard let url = url, let paymentData = paymentData else { return }
helperSingleton.redirect(url, paymentData)
}
}
34 changes: 22 additions & 12 deletions ios/RNAdyenEncryptor/HelperThreeDS2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,48 @@ import Foundation
import Adyen

let threeDS2ComponentSingleton = ThreeDS2Component()
let redirectComponentSingleton = RedirectComponent()

public class HelperThreeDS2: NSObject, ActionComponentDelegate {

let threeDS2Component: ThreeDS2Component

let redirectComponent: RedirectComponent

public override init() {
threeDS2Component = threeDS2ComponentSingleton
redirectComponent = redirectComponentSingleton
super.init()
self.threeDS2Component.delegate = self
self.redirectComponent.delegate = self
}

public func identify(_ token: String?, _ paymentData: String?) {
guard let token = token, let paymentData = paymentData else { return }
let action = ThreeDS2FingerprintAction(token: token, paymentData: paymentData)
self.threeDS2Component.handle(action)
}

public func challenger(_ token: String?, _ paymentData: String?) {
guard let token = token, let paymentData = paymentData else { return }
let action = ThreeDS2ChallengeAction(token: token, paymentData: paymentData)
self.threeDS2Component.handle(action)
}


public func redirect(_ url: String?, _ paymentData: String?) {
guard let url = url, let paymentData = paymentData else { return }
let action = RedirectAction(url: URL(string: url)!, paymentData: paymentData)
self.redirectComponent.handle(action)
}

public func didProvide(_ data: ActionComponentData, from component: ActionComponent) {
let dictionary = data.details.dictionaryRepresentation;
if let jsonData = try? JSONSerialization.data( withJSONObject: dictionary, options: .prettyPrinted ) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
RNAdyenEventEmitter.sharedInstance().emitEncryptedCard(jsonString)
let dictionary = data.details.dictionaryRepresentation;
if let jsonData = try? JSONSerialization.data( withJSONObject: dictionary, options: .prettyPrinted ) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
RNAdyenEventEmitter.sharedInstance().emitEncryptedCard(jsonString)
}
}
}
}

public func didFail(with error: Error, from component: ActionComponent) {
var json: [String: String] = [:]
json["error"] = error.localizedDescription
Expand All @@ -50,5 +60,5 @@ public class HelperThreeDS2: NSObject, ActionComponentDelegate {
}
RNAdyenEventEmitter.sharedInstance().emitEncryptedCardError(json)
}

}
5 changes: 5 additions & 0 deletions ios/RNAdyenEncryptor/RNAdyenEncryptor.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ @implementation RNAdyenEncryptor
[wrapper challenger:token paymentData:paymentData];
}

RCT_EXPORT_METHOD(redirect: (NSString*)url setPaymentData:(NSString*)paymentData) {
AdyenThreeDS2Wrapper *wrapper = [AdyenThreeDS2Wrapper new];
[wrapper redirect:url paymentData:paymentData];
}

- (NSString *)safeString:(id)object {
if ([object isKindOfClass:[NSString class]]) {
return (NSString *)object;
Expand Down
Loading

0 comments on commit 512bc14

Please sign in to comment.