From e13cf0e19974c9241c35437829deb7791311f768 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Fri, 22 Nov 2024 13:41:25 +0800 Subject: [PATCH 01/27] refactor to interface --- .../example/AuthenticationActivity.java | 3 +- .../xendit/example/CreateTokenActivity.java | 2 +- .../com/xendit/example/StoreCvnActivity.java | 3 +- .../androidTest/java/com/xendit/AuthTest.java | 2 +- .../java/com/xendit/TokenTest.java | 2 +- .../src/main/java/com/xendit/Xendit.java | 2103 +++++------------ .../src/main/java/com/xendit/XenditImpl.java | 1503 ++++++++++++ 7 files changed, 2117 insertions(+), 1501 deletions(-) create mode 100644 xendit-android/src/main/java/com/xendit/XenditImpl.java diff --git a/app/src/main/java/com/xendit/example/AuthenticationActivity.java b/app/src/main/java/com/xendit/example/AuthenticationActivity.java index cf5e2b5..d69797d 100644 --- a/app/src/main/java/com/xendit/example/AuthenticationActivity.java +++ b/app/src/main/java/com/xendit/example/AuthenticationActivity.java @@ -17,7 +17,6 @@ import com.google.gson.Gson; import com.xendit.AuthenticationCallback; import com.xendit.Models.Authentication; -import com.xendit.Models.CardInfo; import com.xendit.Models.XenditError; import com.xendit.Xendit; import com.xendit.example.models.AuthenticationResponse; @@ -84,7 +83,7 @@ private void setActionBarTitle(String title) { @Override public void onClick(View view) { - Xendit xendit = new Xendit(getApplicationContext(), CreateTokenActivity.PUBLISHABLE_KEY, this); + Xendit xendit = Xendit.create(getApplicationContext(), CreateTokenActivity.PUBLISHABLE_KEY, this); String tokenId = tokenIdEditText.getText().toString(); String amount = amountEditText.getText().toString(); diff --git a/app/src/main/java/com/xendit/example/CreateTokenActivity.java b/app/src/main/java/com/xendit/example/CreateTokenActivity.java index 7176e97..241b938 100644 --- a/app/src/main/java/com/xendit/example/CreateTokenActivity.java +++ b/app/src/main/java/com/xendit/example/CreateTokenActivity.java @@ -119,7 +119,7 @@ private void setActionBarTitle(String title) { @Override public void onClick(View view) { - final Xendit xendit = new Xendit(getApplicationContext(), apiKeyEditText.getText().toString(), this); + final Xendit xendit = Xendit.create(getApplicationContext(), apiKeyEditText.getText().toString(), this); final ProgressBar progressBar = findViewById(R.id.progressBar); progressBar.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/xendit/example/StoreCvnActivity.java b/app/src/main/java/com/xendit/example/StoreCvnActivity.java index 6fdc13c..dc7f8bb 100644 --- a/app/src/main/java/com/xendit/example/StoreCvnActivity.java +++ b/app/src/main/java/com/xendit/example/StoreCvnActivity.java @@ -25,7 +25,6 @@ import com.xendit.Models.XenditError; import com.xendit.Xendit; import com.xendit.example.models.StoreCVNResponse; -import com.xendit.example.models.TokenizationResponse; import com.xendit.utils.StoreCVNCallback; public class StoreCvnActivity extends AppCompatActivity implements View.OnClickListener { @@ -62,7 +61,7 @@ private void setActionBarTitle(String title) { @Override public void onClick(View view) { - final Xendit xendit = new Xendit(getApplicationContext(), DUMMY_PUBLISHABLE_KEY, this); + final Xendit xendit = Xendit.create(getApplicationContext(), DUMMY_PUBLISHABLE_KEY, this); final ProgressBar progressBar = findViewById(R.id.progressBar); progressBar.setVisibility(View.VISIBLE); diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index b35c831..6658725 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -20,7 +20,7 @@ public class AuthTest { private final static String PUBLISHABLE_KEY = "xnd_public_development_O4uGfOR3gbOunJU4frcaHmLCYNLy8oQuknDm+R1r9G3S/b2lBQR+gQ=="; private Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); - private final Xendit xendit = new Xendit(appContext, PUBLISHABLE_KEY); + private final Xendit xendit = Xendit.create(appContext, PUBLISHABLE_KEY); @Test public void test_createAuth() { diff --git a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java index 93a4741..54bbe7f 100644 --- a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java @@ -30,7 +30,7 @@ public class TokenTest { public void setup() { String PUBLISHABLE_KEY = "xnd_public_development_O4uGfOR3gbOunJU4frcaHmLCYNLy8oQuknDm+R1r9G3S/b2lBQR+gQ=="; Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); - xendit = new Xendit(appContext, PUBLISHABLE_KEY); + xendit = Xendit.create(appContext, PUBLISHABLE_KEY); } @Test diff --git a/xendit-android/src/main/java/com/xendit/Xendit.java b/xendit-android/src/main/java/com/xendit/Xendit.java index 9f100fb..ce904f8 100644 --- a/xendit-android/src/main/java/com/xendit/Xendit.java +++ b/xendit-android/src/main/java/com/xendit/Xendit.java @@ -1,1506 +1,621 @@ package com.xendit; -import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IntentFilter; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.util.Base64; -import com.android.volley.Request; -import com.android.volley.RequestQueue; -import com.android.volley.toolbox.BaseHttpStack; -import com.android.volley.toolbox.HurlStack; -import com.android.volley.toolbox.Volley; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.snowplowanalytics.snowplow.controller.TrackerController; -import com.snowplowanalytics.snowplow.event.Structured; -import com.xendit.DeviceInfo.AdInfo; -import com.xendit.DeviceInfo.DeviceInfo; -import com.xendit.Models.AuthenticatedToken; -import com.xendit.Models.Authentication; import com.xendit.Models.BillingDetails; import com.xendit.Models.Card; import com.xendit.Models.CardHolderData; import com.xendit.Models.Customer; -import com.xendit.Models.ThreeDSRecommendation; -import com.xendit.Models.Token; -import com.xendit.Models.XenditError; -import com.xendit.network.BaseRequest; -import com.xendit.network.DefaultResponseHandler; -import com.xendit.network.NetworkHandler; -import com.xendit.network.TLSSocketFactory; -import com.xendit.network.errors.ConnectionError; -import com.xendit.network.errors.NetworkError; -import com.xendit.network.interfaces.ResultListener; import com.xendit.utils.CardValidator; -import com.xendit.utils.PermissionUtils; import com.xendit.utils.StoreCVNCallback; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; - -import io.sentry.Hint; -import io.sentry.Sentry; -import io.sentry.SentryEvent; -import io.sentry.SentryOptions; -import io.sentry.android.core.SentryAndroid; -import io.sentry.android.core.SentryAndroidOptions; -import io.sentry.protocol.SentryException; -import io.sentry.protocol.SentryStackFrame; -import io.sentry.protocol.SentryStackTrace; - -import static com.xendit.Tracker.SnowplowTrackerBuilder.getTracker; - -/** - * Created by Dimon_GDA on 3/7/17. - */ - -public class Xendit { - - private static final String TAG = "Xendit"; - private static final String PRODUCTION_XENDIT_BASE_URL = "https://api.xendit.co"; - private static final String CREATE_CREDIT_CARD_URL = PRODUCTION_XENDIT_BASE_URL + "/credit_card_tokens"; - private static final String CREATE_CREDIT_CARD_TOKEN_URL = PRODUCTION_XENDIT_BASE_URL + "/v2/credit_card_tokens"; - private static final String GET_3DS_URL = PRODUCTION_XENDIT_BASE_URL + "/3ds_bin_recommendation"; - private static final String DSN_SERVER = "https://7190a1331444434eb6aed7b5a8d776f0@o30316.ingest.sentry.io/6314580"; - private static final String CLIENT_IDENTIFIER = "Xendit Android SDK"; - private static final String CLIENT_API_VERSION = "2.0.0"; - private static final String CLIENT_TYPE = "SDK"; - static final String ACTION_KEY = "ACTION_KEY"; - - private Context context; - private String publishableKey; - private RequestQueue requestQueue; - private ConnectivityManager connectivityManager; - private Activity activity; - private Gson gsonMapper; - - private AuthenticationBroadcastReceiver authenticationBroadcastReceiver; - private TokenBroadcastReceiver tokenBroadcastReceiver; - private AuthenticatedTokenBroadcastReceiver authenticatedTokenBroadcastReceiver; - - public Xendit(final Context context, String publishableKey, Activity activity) { - this(context, publishableKey); - this.activity = activity; - } - - public Xendit(final Context context, String publishableKey) { - this.context = context; - this.publishableKey = publishableKey; - this.gsonMapper = new Gson(); - - // init sentry - // Use the Sentry DSN (client key) from the Project Settings page on Sentry - SentryAndroid.init(context, new Sentry.OptionsConfiguration() { - @Override - public void configure(SentryAndroidOptions sentryAndroidOptions) { - sentryAndroidOptions.setDsn(DSN_SERVER); - - try { - PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - String versionName = pInfo.versionName; - String applicationName = - context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); - - sentryAndroidOptions.setTag("applicationName", applicationName); - sentryAndroidOptions.setTag("applicationVersionName", versionName); - sentryAndroidOptions.setTag("sdkVersionName", BuildConfig.VERSION_NAME); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - - sentryAndroidOptions.setBeforeSend(new SentryOptions.BeforeSendCallback() { - @Override - public SentryEvent execute(SentryEvent event, Hint hint) { - // decide whether to send the event - for (SentryException sentryException : event.getExceptions()) { - SentryStackTrace stackTrace = sentryException.getStacktrace(); - for (SentryStackFrame frame : stackTrace.getFrames()) { - if (frame.getModule().contains("com.xendit")) { - return event; - } - } - } - return null; - } - }); - } - }); - - //get device info - new Thread(new Runnable() { - public void run() { - try { - AdInfo adInfo = DeviceInfo.getAdvertisingIdInfo(context); - String advertisingId = adInfo.getId(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }).start(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - - BaseHttpStack stack; - try { - stack = new HurlStack(null, new TLSSocketFactory()); - } catch (KeyManagementException e) { - e.printStackTrace(); - stack = new HurlStack(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - stack = new HurlStack(); - } - requestQueue = Volley.newRequestQueue(context, stack); - } else { - requestQueue = Volley.newRequestQueue(context); - } - - connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - } - - /** - * Determines whether the credit card number provided is valid - * - * @param creditCardNumber A credit card number - * @return true if the credit card number is valid, false otherwise - * @deprecated Use CardValidator.isCardNumberValid - */ - @Deprecated - public static boolean isCardNumberValid(String creditCardNumber) { - return CardValidator.isCardNumberValid(creditCardNumber); - } - - /** - * Determines whether the card expiration month and year are valid - * - * @param cardExpirationMonth The month a card expired represented by digits (e.g. 12) - * @param cardExpirationYear The year a card expires represented by digits (e.g. 2026) - * @return true if both the expiration month and year are valid - * @deprecated Use CardValidator.isExpiryValid - */ - @Deprecated - public static boolean isExpiryValid(String cardExpirationMonth, String cardExpirationYear) { - return CardValidator.isExpiryValid(cardExpirationMonth, cardExpirationYear); - } - - /** - * Determines whether the card CVN is valid - * - * @param creditCardCVN The credit card CVN - * @return true if the cvn is valid, false otherwise - * @deprecated Use CardValidator.isCvnValid - */ - @Deprecated - public static boolean isCvnValid(String creditCardCVN) { - return CardValidator.isCvnValid(creditCardCVN); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken(final Card card, final int amount, final TokenCallback tokenCallback) { - String amountStr = Integer.toString(amount); - - createSingleOrMultipleUseToken( - card, amountStr, true, "", - false, null, null, - null, null, null, null, - tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, - final TokenCallback tokenCallback) { - String amountStr = Integer.toString(amount); - - createSingleOrMultipleUseToken( - card, amountStr, shouldAuthenticate, "", false, - null, null, null, - null, null, null, - tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, - final String onBehalfOf, final TokenCallback tokenCallback) { - String amountStr = Integer.toString(amount); - - createSingleOrMultipleUseToken( - card, amountStr, shouldAuthenticate, onBehalfOf, - false, null, null, null, - null, null, null, - tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken( - final Card card, - final int amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - TokenCallback tokenCallback) { - String amountStr = Integer.toString(amount); - - createSingleOrMultipleUseToken(card, amountStr, shouldAuthenticate, onBehalfOf, false, billingDetails, customer, - null, null, null, null, tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken( - final Card card, - final int amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - TokenCallback tokenCallback) { - String amountStr = Integer.toString(amount); - - createSingleOrMultipleUseToken(card, amountStr, shouldAuthenticate, onBehalfOf, false, - billingDetails, customer, - currency, null, null, null, tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - TokenCallback tokenCallback) { - createSingleOrMultipleUseToken(card, amount, shouldAuthenticate, onBehalfOf, false, - billingDetails, customer, - currency, null, null, null, tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this - * method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. - * This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are - * required for this token - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - public void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String currency, - TokenCallback tokenCallback) { - - createSingleOrMultipleUseToken( - card, - amount, - shouldAuthenticate, - null, false, null, null, - currency, null, null, null, - tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createSingleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - null, null, false, null, - false, billingDetails, customer, null, - tokenId, cardCvn, null, - tokenCallback); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this - * method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. - * This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are - * required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param midLabel Mid label to perform authentication if tokenization - * is bundled with authenticaiton. This is only - * applicable for switcher mid. - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - public void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - final String midLabel, - TokenCallback tokenCallback) { - createSingleOrMultipleUseToken(card, amount, shouldAuthenticate, onBehalfOf, false, - billingDetails, customer, - currency, null, null, midLabel, tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createMultipleUseToken(final Card card, final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - card, "0", false, - "", true, null, - null, null, null, null, null, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createMultipleUseToken(final Card card, final String onBehalfOf, - final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - card, "0", false, onBehalfOf, - true, null, null, null, - null, null, null, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, - final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - card, "0", false, onBehalfOf, true, - billingDetails, null, - null, null, null, null, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, Customer customer, final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - card, "0", false, onBehalfOf, true, - billingDetails, customer, null, - null, null, null, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param midLabel Mid label to perform authentication if tokenization is - * bundled with tokenization. - * This argument is only applicable for switcher merchant. - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - public void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, Customer customer, final String midLabel, - final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - card, "0", false, onBehalfOf, true, - billingDetails, customer, null, - null, null, midLabel, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if - * shouldAuthenticate - * is true. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - public void createMultipleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - null, null, false, null, - true, billingDetails, customer, - null, tokenId, cardCvn, null, - tokenCallback); - } - - /** - * Creates a multiple-use token. Authentication must be created separately if - * shouldAuthenticate - * is true. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param midLabel Mid label to perform authentication if tokenization is - * bundled with tokenization. - * This argument is only applicable for switcher merchant. - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - public void createMultipleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - final String midLabel, - final TokenCallback tokenCallback) { - createSingleOrMultipleUseToken( - null, null, false, null, - true, billingDetails, customer, - null, tokenId, cardCvn, midLabel, - tokenCallback); - } - - private void createSingleOrMultipleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final boolean isMultipleUse, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - final String tokenId, - final String cardCvn, - final String midLabel, - final TokenCallback tokenCallback - ) { - /** - * card must exist when doing normal tokenization - * tokenId must exist when doing re-tokenization - */ - if ((card != null || tokenId != null) && tokenCallback != null) { - if (card != null && !CardValidator.isCardNumberValid(card.getCreditCardNumber())) { - tokenCallback.onError( - new XenditError(context.getString(R.string.create_token_error_card_number))); - return; - } - - if (card != null && !CardValidator.isExpiryValid(card.getCardExpirationMonth(), - card.getCardExpirationYear())) { - tokenCallback.onError( - new XenditError( - context.getString(R.string.create_token_error_card_expiration))); - return; - } - - if (card != null && card.getCreditCardCVN() != null && !CardValidator.isCvnValid( - card.getCreditCardCVN())) { - tokenCallback.onError( - new XenditError(context.getString(R.string.create_token_error_card_cvn))); - return; - } - - if (card != null - && card.getCreditCardCVN() != null - && !CardValidator.isCvnValidForCardType( - card.getCreditCardCVN(), card.getCreditCardNumber())) { - tokenCallback.onError( - new XenditError(context.getString(R.string.error_card_cvn_invalid_for_type))); - return; - } - - if (cardCvn != null && !CardValidator.isCvnValid(cardCvn)) { - tokenCallback.onError( - new XenditError(context.getString(R.string.create_token_error_card_cvn))); - return; - } - - createCreditCardToken(card, amount, shouldAuthenticate, onBehalfOf, isMultipleUse, - billingDetails, customer, - currency, tokenId, cardCvn, midLabel, tokenCallback); - } - } - - /** - * Store CVN method will perform store cvn using an existing tokenId (retokenization). - * This method is commonly used for performing re-tokenization on subsequent usage of a multi-use token in the purpose of re-caching cardCVN. - * - * @param tokenId is a previously created Xendit multiple-use tokenId. Required field. - * @param cardCvn is card cvn code linked to the tokenId created. Required field. - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param onBehalfOf The onBehalfOf is sub account business id. This field is used for merchants utilizing xenPlatform feature. - * @param storeCVNCallback The callback that will be called when the token re-creation completes or - * fails - */ - public void storeCVN( - final String tokenId, - final String cardCvn, - final BillingDetails billingDetails, - final Customer customer, - final String onBehalfOf, - final StoreCVNCallback storeCVNCallback - ) { - NetworkHandler handler = new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(Token token) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("store-cvn") - .label("Store CVN") - .build()); - - storeCVNCallback.onSuccess(token); - } - - @Override - public void onFailure(NetworkError error) { - storeCVNCallback.onError(new XenditError(error)); - } - }); - - BaseRequest request = buildBaseRequest( - Request.Method.POST, - CREATE_CREDIT_CARD_TOKEN_URL, - onBehalfOf == null || onBehalfOf.equals("") ? "" : onBehalfOf, - Token.class, - new DefaultResponseHandler<>(handler) - ); - - if (tokenId == null || tokenId.equals("")) { - storeCVNCallback.onError(new XenditError("TokenId is required")); - return; - } - request.addParam("token_id", tokenId); - - if (cardCvn == null || cardCvn.equals("")) { - storeCVNCallback.onError(new XenditError("CVN is required")); - return; - } else { - if (!CardValidator.isCvnValid(cardCvn)) { - storeCVNCallback.onError(new XenditError("CVN in invalid")); - return; - } - request.addParam("card_cvn", cardCvn); - } - - if (customer != null) { - request.addJsonParam("customer", gsonMapper.toJsonTree(customer)); - } - - if (billingDetails != null) { - request.addJsonParam("billing_details", gsonMapper.toJsonTree(billingDetails)); - } - - sendRequest(request, handler); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final int amount, final String currency, - final AuthenticationCallback authenticationCallback) { - final String amountStr = Integer.toString(amount); - createAuthenticationInternal(tokenId, amountStr, currency, null, null, null, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final int amount, final String currency, - final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { - final String amountStr = Integer.toString(amount); - createAuthenticationInternal(tokenId, amountStr, currency, null, null, null, cardHolderData, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId The id of a multiple-use token - * @param amount The amount that will eventually be charged. This number is displayed to the - * user in the 3DS authentication view - * @param authenticationCallback The callback that will be called when the authentication - * creation completes or fails - */ - public void createAuthentication(final String tokenId, final int amount, - final AuthenticationCallback authenticationCallback) { - final String amountStr = Integer.toString(amount); - createAuthenticationInternal(tokenId, amountStr, null, null, null, null, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId The id of a multiple-use token - * @param amount The amount that will eventually be charged. This number is displayed to the - * user in the 3DS authentication view - * @param cardHolderData Additional information of the card holder - * @param authenticationCallback The callback that will be called when the - * authentication creation completes or fails - */ - public void createAuthentication(final String tokenId, final int amount, - CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { - final String amountStr = Integer.toString(amount); - createAuthenticationInternal(tokenId, amountStr, null, null, null, null, cardHolderData, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, null, null, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the authentication completes or - * fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, null, null, cardHolderData, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, null, null, null, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, null, null, null, cardHolderData, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final String onBehalfOf, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, null, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, null, - cardHolderData, authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param midLabel Mid label to perform authentication if - * tokenization is bundled with authenticaiton. - * This is only applicable for switcher mid. - * @param authenticationCallback The callback that will be called when the - * authentication completes or - * fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final String onBehalfOf, final String midLabel, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, null, - authenticationCallback); - } - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param midLabel Mid label to perform authentication if - * tokenization is bundled with authenticaiton. - * This is only applicable for switcher mid. - * @param authenticationCallback The callback that will be called when the - * authentication completes or - * fails - */ - public void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, - final String midLabel, - final AuthenticationCallback authenticationCallback) { - createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, - cardHolderData, authenticationCallback); - } - - private void createAuthenticationInternal(final String tokenId, final String amount, - final String currency, - final String cardCvn, final String onBehalfOf, final String midLabel, - final CardHolderData cardHolderData, - final AuthenticationCallback authenticationCallback) { - if (tokenId == null || tokenId.equals("")) { - authenticationCallback.onError( - new XenditError(context.getString(R.string.create_token_error_validation))); - return; - } - - if (Double.parseDouble(amount) < 0) { - authenticationCallback.onError( - new XenditError(context.getString(R.string.create_token_error_validation))); - return; - } - - _createAuthentication(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, - cardHolderData, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(Authentication authentication) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("create-authentication") - .label("Create Authentication") - .build()); - - if (!authentication.getStatus().equalsIgnoreCase("VERIFIED")) { - registerBroadcastReceiver(authenticationCallback); - context.startActivity( - XenditActivity.getLaunchIntent(context, authentication)); - } else { - authenticationCallback.onSuccess(authentication); - } - } - - @Override - public void onFailure(NetworkError error) { - authenticationCallback.onError(new XenditError(error)); - } - })); - } - - private void get3DSRecommendation(String tokenId, final AuthenticatedToken authentication, - final TokenCallback callback) { - _get3DSRecommendation(tokenId, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(ThreeDSRecommendation rec) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("get-3ds-recommendation") - .label("Get 3DS Recommendation") - .build()); - - callback.onSuccess(new Token(authentication, rec)); - } - - @Override - public void onFailure(NetworkError error) { - callback.onSuccess(new Token(authentication)); - } - })); - } - - // createCreditCardToken with 5 arguments - public void createCreditCardToken( - Card card, - String amount, - boolean shouldAuthenticate, - boolean isMultipleUse, - final TokenCallback tokenCallback) { - - _createToken( - card, - amount, - shouldAuthenticate, - "", - isMultipleUse, - null, - null, - null, - null, - null, - null, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(AuthenticatedToken authentication) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("create-token") - .label("Create Token") - .build()); - - handle3ds1Tokenization(authentication, tokenCallback); - } - - @Override - public void onFailure(NetworkError error) { - tokenCallback.onError(new XenditError(error)); - } - })); - } - - // createCreditCardToken with 6 arguments - public void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - final TokenCallback tokenCallback) { - - _createToken( - card, - amount, - shouldAuthenticate, - onBehalfOf, - isMultipleUse, - null, - null, - null, - null, - null, - null, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(final AuthenticatedToken authentication) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("create-token") - .label("Create Token") - .build()); - - handle3ds1Tokenization(authentication, tokenCallback); - } - - @Override - public void onFailure(NetworkError error) { - tokenCallback.onError(new XenditError(error)); - } - })); - } - - // createCreditCardToken with 11 arguments - public void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - BillingDetails billingDetails, - Customer customer, - final String currency, - final String tokenId, - final String cardCvn, - final TokenCallback tokenCallback) { - - _createToken( - card, - amount, - shouldAuthenticate, - onBehalfOf, - isMultipleUse, - billingDetails, - customer, - currency, - tokenId, - cardCvn, - null, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(final AuthenticatedToken authentication) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("create-token") - .label("Create Token") - .build()); - - handle3ds1Tokenization(authentication, tokenCallback); - } - - @Override - public void onFailure(NetworkError error) { - tokenCallback.onError(new XenditError(error)); - } - })); - } - - // createCreditCardToken with 12 arguments - public void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - BillingDetails billingDetails, - Customer customer, - final String currency, - final String tokenId, - final String cardCvn, - final String midLabel, - final TokenCallback tokenCallback) { - - _createToken( - card, - amount, - shouldAuthenticate, - onBehalfOf, - isMultipleUse, - billingDetails, - customer, - currency, - tokenId, - cardCvn, - midLabel, - new NetworkHandler().setResultListener( - new ResultListener() { - @Override - public void onSuccess(final AuthenticatedToken authentication) { - TrackerController tracker = getTracker(context); - tracker.track(Structured.builder() - .category("api-request") - .action("create-token") - .label("Create Token") - .build()); - - handle3ds1Tokenization(authentication, tokenCallback); - } - - @Override - public void onFailure(NetworkError error) { - tokenCallback.onError(new XenditError(error)); - } - })); - } - - public void unregisterXenBroadcastReceiver(BroadcastReceiver receiver) { - try { - context.unregisterReceiver(receiver); - } catch (Exception ignored) { - } - } - - public void unregisterAllBroadcastReceiver() { - if (authenticationBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(authenticationBroadcastReceiver); - authenticationBroadcastReceiver = null; - } - - if (tokenBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(tokenBroadcastReceiver); - tokenBroadcastReceiver = null; - } - - if (authenticatedTokenBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(authenticatedTokenBroadcastReceiver); - authenticatedTokenBroadcastReceiver = null; - } - } - - private void registerBroadcastReceiver(final AuthenticationCallback authenticationCallback) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - @SuppressLint("UnspecifiedRegisterReceiverFlag") - public void run() { - if (authenticationBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(authenticationBroadcastReceiver); - authenticationBroadcastReceiver = null; - } - - authenticationBroadcastReceiver = - new AuthenticationBroadcastReceiver(authenticationCallback); - // if version is over 33 - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(authenticationBroadcastReceiver, - new IntentFilter(ACTION_KEY), - Context.RECEIVER_EXPORTED); - } else { - context.registerReceiver(authenticationBroadcastReceiver, - new IntentFilter(ACTION_KEY)); - } - } - }); - } - - private void registerBroadcastReceiver(final TokenCallback tokenCallback) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - - @SuppressLint("UnspecifiedRegisterReceiverFlag") - @Override - public void run() { - if (tokenBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(tokenBroadcastReceiver); - tokenBroadcastReceiver = null; - } - - tokenBroadcastReceiver = new TokenBroadcastReceiver(tokenCallback); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(tokenBroadcastReceiver, new IntentFilter(ACTION_KEY), - Context.RECEIVER_EXPORTED); - } else { - context.registerReceiver(tokenBroadcastReceiver, new IntentFilter(ACTION_KEY)); - } - } - }); - } - - private void registerBroadcastReceiverAuthenticatedToken(final TokenCallback tokenCallback) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @SuppressLint("UnspecifiedRegisterReceiverFlag") - @Override - public void run() { - if (authenticatedTokenBroadcastReceiver != null) { - unregisterXenBroadcastReceiver(authenticatedTokenBroadcastReceiver); - authenticatedTokenBroadcastReceiver = null; - } - - authenticatedTokenBroadcastReceiver = - new AuthenticatedTokenBroadcastReceiver(tokenCallback); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(authenticatedTokenBroadcastReceiver, - new IntentFilter(ACTION_KEY), Context.RECEIVER_EXPORTED); - } else { - context.registerReceiver(authenticatedTokenBroadcastReceiver, - new IntentFilter(ACTION_KEY)); - } - } - }); - } - - private void _createToken( - Card card, - String amount, - boolean shouldAuthenticate, - String onBehalfOf, - boolean isMultipleUse, - BillingDetails billingDetails, - Customer customer, - String currency, - String tokenId, - String cardCvn, - String midLabel, - NetworkHandler handler) { - BaseRequest request = - buildBaseRequest(Request.Method.POST, CREATE_CREDIT_CARD_TOKEN_URL, onBehalfOf, - AuthenticatedToken.class, new DefaultResponseHandler<>(handler)); - - JsonObject cardData = new JsonObject(); - if (card != null) { - cardData.addProperty("account_number", card.getCreditCardNumber()); - cardData.addProperty("exp_year", card.getCardExpirationYear()); - cardData.addProperty("exp_month", card.getCardExpirationMonth()); - cardData.addProperty("cvn", card.getCreditCardCVN()); - CardHolderData cardHolderData = card.getCardHolder(); - if (cardHolderData != null) { - cardData.addProperty("card_holder_first_name", cardHolderData.getFirstName()); - cardData.addProperty("card_holder_last_name", cardHolderData.getLastName()); - cardData.addProperty("card_holder_email", cardHolderData.getEmail()); - cardData.addProperty("card_holder_phone_number", cardHolderData.getPhoneNumber()); - } - request.addJsonParam("card_data", cardData); - } - - // If tokenId doesn't exist add is_single_use and should_authenticate - if (tokenId == null) { - request.addParam("is_single_use", String.valueOf(!isMultipleUse)); - request.addParam("should_authenticate", String.valueOf(shouldAuthenticate)); - } - - if (customer != null) { - request.addJsonParam("customer", gsonMapper.toJsonTree(customer)); - } - - if (billingDetails != null) { - request.addJsonParam("billing_details", gsonMapper.toJsonTree(billingDetails)); - } - - if (!isMultipleUse) { - request.addParam("amount", amount); - } - - if (currency != null) { - request.addParam("currency", currency); - } - - if (tokenId != null) { - request.addParam("token_id", tokenId); - } - - if (cardCvn != null) { - request.addParam("card_cvn", cardCvn); - } - - if (midLabel != null) { - request.addParam("mid_label", midLabel); - } - - sendRequest(request, handler); - } - - private void _get3DSRecommendation(String tokenId, - NetworkHandler handler) { - String url = GET_3DS_URL + "?token_id=" + tokenId; - - BaseRequest request = - buildBaseRequest(Request.Method.GET, url, null, ThreeDSRecommendation.class, - new DefaultResponseHandler<>(handler)); - sendRequest(request, handler); - } - - private void _createAuthentication(String tokenId, String amount, String currency, - String cardCvn, - String onBehalfOf, String midLabel, CardHolderData cardHolder, - NetworkHandler handler) { - String requestUrl = CREATE_CREDIT_CARD_URL + "/" + tokenId + "/authentications"; - - BaseRequest request = - buildBaseRequest(Request.Method.POST, requestUrl, onBehalfOf, Authentication.class, - new DefaultResponseHandler<>(handler)); - request.addParam("amount", amount); - if (currency != null && !currency.isEmpty()) { - request.addParam("currency", currency); - } - if (cardCvn != null && !cardCvn.isEmpty()) { - request.addParam("card_cvn", cardCvn); - } - if (cardHolder != null) { - JsonObject cardHolderData = new JsonObject(); - cardHolderData.addProperty("card_holder_first_name", cardHolder.getFirstName()); - cardHolderData.addProperty("card_holder_last_name", cardHolder.getLastName()); - cardHolderData.addProperty("card_holder_email", cardHolder.getEmail()); - cardHolderData.addProperty("card_holder_phone_number", cardHolder.getPhoneNumber()); - request.addJsonParam("card_data", cardHolderData); - } - - if (midLabel != null) { - request.addParam("mid_label", midLabel); - } - - sendRequest(request, handler); - } - - private void handle3ds1Tokenization(AuthenticatedToken authentication, - TokenCallback tokenCallback) { - if (authentication.getStatus().equalsIgnoreCase("FAILED")) { - tokenCallback.onSuccess(new Token(authentication)); - } else if (!authentication.getStatus().equalsIgnoreCase("VERIFIED")) { - registerBroadcastReceiver(tokenCallback); - context.startActivity(XenditActivity.getLaunchIntent(context, authentication)); - } else { //for multi token - String tokenId = authentication.getId(); - get3DSRecommendation(tokenId, authentication, tokenCallback); - } - } - - private void handleAuthenticatedToken(String tokenId, Authentication authenticatedToken, - TokenCallback tokenCallback) { - if (authenticatedToken.getStatus().equalsIgnoreCase("VERIFIED") - || authenticatedToken.getStatus().equalsIgnoreCase("FAILED")) { - Token token = new Token(authenticatedToken, tokenId); - tokenCallback.onSuccess(token); - } else { - registerBroadcastReceiverAuthenticatedToken(tokenCallback); - context.startActivity(XenditActivity.getLaunchIntent(context, authenticatedToken)); - } - } - - private boolean is3ds2Version(String version) { - if (version != null) { - int currentMajorVersion = Integer.parseInt(version.substring(0, 1)); - return currentMajorVersion >= 2; - } - return false; - } - - private String encodeBase64(String key) { - try { - byte[] keyData = key.getBytes("UTF-8"); - return Base64.encodeToString(keyData, Base64.DEFAULT); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return null; - } - - private void sendRequest(BaseRequest request, NetworkHandler handler) { - if (isConnectionAvailable()) { - requestQueue.add(request); - } else if (handler != null) { - handler.handleError(new ConnectionError()); - } - } - - private boolean isConnectionAvailable() { - if (PermissionUtils.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { - @SuppressLint("MissingPermission") NetworkInfo activeNetwork = - connectivityManager.getActiveNetworkInfo(); - return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); - } else { - return false; - } - } - - private boolean getEnvironment() { - String publishKey = publishableKey.toUpperCase(); - return publishKey.contains("PRODUCTION"); - } - - private BaseRequest buildBaseRequest(int method, String url, String onBehalfOf, Type type, - DefaultResponseHandler handler) { - String encodedKey = encodeBase64(publishableKey + ":"); - String basicAuthCredentials = "Basic " + encodedKey; - BaseRequest request = new BaseRequest<>(method, url, type, handler); - if (onBehalfOf != null && !onBehalfOf.isEmpty()) { - request.addHeader("for-user-id", onBehalfOf); - } - request.addHeader("Authorization", basicAuthCredentials.replace("\n", "")); - request.addHeader("x-client-identifier", CLIENT_IDENTIFIER); - request.addHeader("client-version", CLIENT_API_VERSION); - request.addHeader("client-type", CLIENT_TYPE); - return request; - } -} +public interface Xendit { + String ACTION_KEY = "ACTION_KEY"; + + static Xendit create(final Context context, String publishableKey, + Activity activity) { + return new XenditImpl(context, publishableKey, activity); + } + + static Xendit create(final Context context, String publishableKey) { + return new XenditImpl(context, publishableKey); + } + + /** + * Determines whether the credit card number provided is valid + * + * @param creditCardNumber A credit card number + * @return true if the credit card number is valid, false otherwise + * @deprecated Use CardValidator.isCardNumberValid + */ + @Deprecated + static boolean isCardNumberValid(String creditCardNumber) { + return CardValidator.isCardNumberValid(creditCardNumber); + } + + /** + * Determines whether the card expiration month and year are valid + * + * @param cardExpirationMonth The month a card expired represented by digits (e.g. 12) + * @param cardExpirationYear The year a card expires represented by digits (e.g. 2026) + * @return true if both the expiration month and year are valid + * @deprecated Use CardValidator.isExpiryValid + */ + @Deprecated + static boolean isExpiryValid(String cardExpirationMonth, String cardExpirationYear) { + return CardValidator.isExpiryValid(cardExpirationMonth, cardExpirationYear); + } + + /** + * Determines whether the card CVN is valid + * + * @param creditCardCVN The credit card CVN + * @return true if the cvn is valid, false otherwise + * @deprecated Use CardValidator.isCvnValid + */ + @Deprecated + static boolean isCvnValid(String creditCardCVN) { + return CardValidator.isCvnValid(creditCardCVN); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final String onBehalfOf, final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. This value is used to display to + * the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param midLabel Mid label to perform authentication if tokenization + * is bundled with authenticaiton. This is only + * applicable for switcher mid. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + final String midLabel, + TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final String midLabel, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback); + + /** + * Store CVN method will perform store cvn using an existing tokenId (retokenization). + * This method is commonly used for performing re-tokenization on subsequent usage of a multi-use + * token in the purpose of re-caching cardCVN. + * + * @param tokenId is a previously created Xendit multiple-use tokenId. Required field. + * @param cardCvn is card cvn code linked to the tokenId created. Required field. + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param onBehalfOf The onBehalfOf is sub account business id. This field is used for merchants + * utilizing xenPlatform feature. + * @param storeCVNCallback The callback that will be called when the token re-creation completes + * or + * fails + */ + void storeCVN( + final String tokenId, + final String cardCvn, + final BillingDetails billingDetails, + final Customer customer, + final String onBehalfOf, + final StoreCVNCallback storeCVNCallback + ); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final int amount, final String currency, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final int amount, final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param authenticationCallback The callback that will be called when the authentication + * creation completes or fails + */ + void createAuthentication(final String tokenId, final int amount, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param cardHolderData Additional information of the card holder + * @param authenticationCallback The callback that will be called when the + * authentication creation completes or fails + */ + void createAuthentication(final String tokenId, final int amount, + CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the authentication + * completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, final String midLabel, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final String midLabel, + final AuthenticationCallback authenticationCallback); + + // createCreditCardToken with 5 arguments + void createCreditCardToken( + Card card, + String amount, + boolean shouldAuthenticate, + boolean isMultipleUse, + final TokenCallback tokenCallback); + + // createCreditCardToken with 6 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + final TokenCallback tokenCallback); + + // createCreditCardToken with 11 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback); + + // createCreditCardToken with 12 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback); + + void unregisterXenBroadcastReceiver(BroadcastReceiver receiver); + + void unregisterAllBroadcastReceiver(); +} \ No newline at end of file diff --git a/xendit-android/src/main/java/com/xendit/XenditImpl.java b/xendit-android/src/main/java/com/xendit/XenditImpl.java new file mode 100644 index 0000000..05b1d0b --- /dev/null +++ b/xendit-android/src/main/java/com/xendit/XenditImpl.java @@ -0,0 +1,1503 @@ +package com.xendit; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Base64; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.BaseHttpStack; +import com.android.volley.toolbox.HurlStack; +import com.android.volley.toolbox.Volley; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.snowplowanalytics.snowplow.controller.TrackerController; +import com.snowplowanalytics.snowplow.event.Structured; +import com.xendit.DeviceInfo.AdInfo; +import com.xendit.DeviceInfo.DeviceInfo; +import com.xendit.Models.AuthenticatedToken; +import com.xendit.Models.Authentication; +import com.xendit.Models.BillingDetails; +import com.xendit.Models.Card; +import com.xendit.Models.CardHolderData; +import com.xendit.Models.Customer; +import com.xendit.Models.ThreeDSRecommendation; +import com.xendit.Models.Token; +import com.xendit.Models.XenditError; +import com.xendit.network.BaseRequest; +import com.xendit.network.DefaultResponseHandler; +import com.xendit.network.NetworkHandler; +import com.xendit.network.TLSSocketFactory; +import com.xendit.network.errors.ConnectionError; +import com.xendit.network.errors.NetworkError; +import com.xendit.network.interfaces.ResultListener; +import com.xendit.utils.CardValidator; +import com.xendit.utils.PermissionUtils; +import com.xendit.utils.StoreCVNCallback; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import io.sentry.Hint; +import io.sentry.Sentry; +import io.sentry.SentryEvent; +import io.sentry.SentryOptions; +import io.sentry.android.core.SentryAndroid; +import io.sentry.android.core.SentryAndroidOptions; +import io.sentry.protocol.SentryException; +import io.sentry.protocol.SentryStackFrame; +import io.sentry.protocol.SentryStackTrace; + +import static com.xendit.Tracker.SnowplowTrackerBuilder.getTracker; + +/** + * Created by Dimon_GDA on 3/7/17. + */ + +public class XenditImpl implements Xendit { + + private static final String TAG = "Xendit"; + private static final String PRODUCTION_XENDIT_BASE_URL = "https://api.xendit.co"; + private static final String CREATE_CREDIT_CARD_URL = PRODUCTION_XENDIT_BASE_URL + "/credit_card_tokens"; + private static final String CREATE_CREDIT_CARD_TOKEN_URL = PRODUCTION_XENDIT_BASE_URL + "/v2/credit_card_tokens"; + private static final String GET_3DS_URL = PRODUCTION_XENDIT_BASE_URL + "/3ds_bin_recommendation"; + private static final String DSN_SERVER = "https://7190a1331444434eb6aed7b5a8d776f0@o30316.ingest.sentry.io/6314580"; + private static final String CLIENT_IDENTIFIER = "Xendit Android SDK"; + private static final String CLIENT_API_VERSION = "2.0.0"; + private static final String CLIENT_TYPE = "SDK"; + + private Context context; + private String publishableKey; + private RequestQueue requestQueue; + private ConnectivityManager connectivityManager; + private Activity activity; + private Gson gsonMapper; + + private AuthenticationBroadcastReceiver authenticationBroadcastReceiver; + private TokenBroadcastReceiver tokenBroadcastReceiver; + private AuthenticatedTokenBroadcastReceiver authenticatedTokenBroadcastReceiver; + + protected XenditImpl(final Context context, String publishableKey, Activity activity) { + this(context, publishableKey); + this.activity = activity; + } + + protected XenditImpl(final Context context, String publishableKey) { + this.context = context; + this.publishableKey = publishableKey; + this.gsonMapper = new Gson(); + + // init sentry + // Use the Sentry DSN (client key) from the Project Settings page on Sentry + SentryAndroid.init(context, new Sentry.OptionsConfiguration() { + @Override + public void configure(SentryAndroidOptions sentryAndroidOptions) { + sentryAndroidOptions.setDsn(DSN_SERVER); + + try { + PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + String versionName = pInfo.versionName; + String applicationName = + context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); + + sentryAndroidOptions.setTag("applicationName", applicationName); + sentryAndroidOptions.setTag("applicationVersionName", versionName); + sentryAndroidOptions.setTag("sdkVersionName", BuildConfig.VERSION_NAME); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + sentryAndroidOptions.setBeforeSend(new SentryOptions.BeforeSendCallback() { + @Override + public SentryEvent execute(SentryEvent event, Hint hint) { + // decide whether to send the event + for (SentryException sentryException : event.getExceptions()) { + SentryStackTrace stackTrace = sentryException.getStacktrace(); + for (SentryStackFrame frame : stackTrace.getFrames()) { + if (frame.getModule().contains("com.xendit")) { + return event; + } + } + } + return null; + } + }); + } + }); + + //get device info + new Thread(new Runnable() { + public void run() { + try { + AdInfo adInfo = DeviceInfo.getAdvertisingIdInfo(context); + String advertisingId = adInfo.getId(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN + && Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { + + BaseHttpStack stack; + try { + stack = new HurlStack(null, new TLSSocketFactory()); + } catch (KeyManagementException e) { + e.printStackTrace(); + stack = new HurlStack(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + stack = new HurlStack(); + } + requestQueue = Volley.newRequestQueue(context, stack); + } else { + requestQueue = Volley.newRequestQueue(context); + } + + connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken(final Card card, final int amount, final TokenCallback tokenCallback) { + String amountStr = Integer.toString(amount); + + createSingleOrMultipleUseToken( + card, amountStr, true, "", + false, null, null, + null, null, null, null, + tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final TokenCallback tokenCallback) { + String amountStr = Integer.toString(amount); + + createSingleOrMultipleUseToken( + card, amountStr, shouldAuthenticate, "", false, + null, null, null, + null, null, null, + tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final String onBehalfOf, final TokenCallback tokenCallback) { + String amountStr = Integer.toString(amount); + + createSingleOrMultipleUseToken( + card, amountStr, shouldAuthenticate, onBehalfOf, + false, null, null, null, + null, null, null, + tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + TokenCallback tokenCallback) { + String amountStr = Integer.toString(amount); + + createSingleOrMultipleUseToken(card, amountStr, shouldAuthenticate, onBehalfOf, false, billingDetails, customer, + null, null, null, null, tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback) { + String amountStr = Integer.toString(amount); + + createSingleOrMultipleUseToken(card, amountStr, shouldAuthenticate, onBehalfOf, false, + billingDetails, customer, + currency, null, null, null, tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback) { + createSingleOrMultipleUseToken(card, amount, shouldAuthenticate, onBehalfOf, false, + billingDetails, customer, + currency, null, null, null, tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String currency, + TokenCallback tokenCallback) { + + createSingleOrMultipleUseToken( + card, + amount, + shouldAuthenticate, + null, false, null, null, + currency, null, null, null, + tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + null, null, false, null, + false, billingDetails, customer, null, + tokenId, cardCvn, null, + tokenCallback); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param midLabel Mid label to perform authentication if tokenization + * is bundled with authenticaiton. This is only + * applicable for switcher mid. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + @Override + public void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + final String midLabel, + TokenCallback tokenCallback) { + createSingleOrMultipleUseToken(card, amount, shouldAuthenticate, onBehalfOf, false, + billingDetails, customer, + currency, null, null, midLabel, tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createMultipleUseToken(final Card card, final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + card, "0", false, + "", true, null, + null, null, null, null, null, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createMultipleUseToken(final Card card, final String onBehalfOf, + final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + card, "0", false, onBehalfOf, + true, null, null, null, + null, null, null, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, + final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + card, "0", false, onBehalfOf, true, + billingDetails, null, + null, null, null, null, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + card, "0", false, onBehalfOf, true, + billingDetails, customer, null, + null, null, null, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + @Override + public void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final String midLabel, + final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + card, "0", false, onBehalfOf, true, + billingDetails, customer, null, + null, null, midLabel, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + @Override + public void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + null, null, false, null, + true, billingDetails, customer, + null, tokenId, cardCvn, null, + tokenCallback); + } + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + @Override + public void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback) { + createSingleOrMultipleUseToken( + null, null, false, null, + true, billingDetails, customer, + null, tokenId, cardCvn, midLabel, + tokenCallback); + } + + private void createSingleOrMultipleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final boolean isMultipleUse, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback + ) { + /** + * card must exist when doing normal tokenization + * tokenId must exist when doing re-tokenization + */ + if ((card != null || tokenId != null) && tokenCallback != null) { + if (card != null && !CardValidator.isCardNumberValid(card.getCreditCardNumber())) { + tokenCallback.onError( + new XenditError(context.getString(R.string.create_token_error_card_number))); + return; + } + + if (card != null && !CardValidator.isExpiryValid(card.getCardExpirationMonth(), + card.getCardExpirationYear())) { + tokenCallback.onError( + new XenditError( + context.getString(R.string.create_token_error_card_expiration))); + return; + } + + if (card != null && card.getCreditCardCVN() != null && !CardValidator.isCvnValid( + card.getCreditCardCVN())) { + tokenCallback.onError( + new XenditError(context.getString(R.string.create_token_error_card_cvn))); + return; + } + + if (card != null + && card.getCreditCardCVN() != null + && !CardValidator.isCvnValidForCardType( + card.getCreditCardCVN(), card.getCreditCardNumber())) { + tokenCallback.onError( + new XenditError(context.getString(R.string.error_card_cvn_invalid_for_type))); + return; + } + + if (cardCvn != null && !CardValidator.isCvnValid(cardCvn)) { + tokenCallback.onError( + new XenditError(context.getString(R.string.create_token_error_card_cvn))); + return; + } + + createCreditCardToken(card, amount, shouldAuthenticate, onBehalfOf, isMultipleUse, + billingDetails, customer, + currency, tokenId, cardCvn, midLabel, tokenCallback); + } + } + + /** + * Store CVN method will perform store cvn using an existing tokenId (retokenization). + * This method is commonly used for performing re-tokenization on subsequent usage of a multi-use token in the purpose of re-caching cardCVN. + * + * @param tokenId is a previously created Xendit multiple-use tokenId. Required field. + * @param cardCvn is card cvn code linked to the tokenId created. Required field. + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param onBehalfOf The onBehalfOf is sub account business id. This field is used for merchants utilizing xenPlatform feature. + * @param storeCVNCallback The callback that will be called when the token re-creation completes or + * fails + */ + @Override + public void storeCVN( + final String tokenId, + final String cardCvn, + final BillingDetails billingDetails, + final Customer customer, + final String onBehalfOf, + final StoreCVNCallback storeCVNCallback + ) { + NetworkHandler handler = new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(Token token) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("store-cvn") + .label("Store CVN") + .build()); + + storeCVNCallback.onSuccess(token); + } + + @Override + public void onFailure(NetworkError error) { + storeCVNCallback.onError(new XenditError(error)); + } + }); + + BaseRequest request = buildBaseRequest( + Request.Method.POST, + CREATE_CREDIT_CARD_TOKEN_URL, + onBehalfOf == null || onBehalfOf.equals("") ? "" : onBehalfOf, + Token.class, + new DefaultResponseHandler<>(handler) + ); + + if (tokenId == null || tokenId.equals("")) { + storeCVNCallback.onError(new XenditError("TokenId is required")); + return; + } + request.addParam("token_id", tokenId); + + if (cardCvn == null || cardCvn.equals("")) { + storeCVNCallback.onError(new XenditError("CVN is required")); + return; + } else { + if (!CardValidator.isCvnValid(cardCvn)) { + storeCVNCallback.onError(new XenditError("CVN in invalid")); + return; + } + request.addParam("card_cvn", cardCvn); + } + + if (customer != null) { + request.addJsonParam("customer", gsonMapper.toJsonTree(customer)); + } + + if (billingDetails != null) { + request.addJsonParam("billing_details", gsonMapper.toJsonTree(billingDetails)); + } + + sendRequest(request, handler); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final int amount, final String currency, + final AuthenticationCallback authenticationCallback) { + final String amountStr = Integer.toString(amount); + createAuthenticationInternal(tokenId, amountStr, currency, null, null, null, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final int amount, final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { + final String amountStr = Integer.toString(amount); + createAuthenticationInternal(tokenId, amountStr, currency, null, null, null, cardHolderData, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param authenticationCallback The callback that will be called when the authentication + * creation completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final int amount, + final AuthenticationCallback authenticationCallback) { + final String amountStr = Integer.toString(amount); + createAuthenticationInternal(tokenId, amountStr, null, null, null, null, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param cardHolderData Additional information of the card holder + * @param authenticationCallback The callback that will be called when the + * authentication creation completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final int amount, + CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { + final String amountStr = Integer.toString(amount); + createAuthenticationInternal(tokenId, amountStr, null, null, null, null, cardHolderData, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, null, null, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the authentication completes or + * fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, null, null, cardHolderData, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, null, null, null, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, null, null, null, cardHolderData, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, null, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, null, + cardHolderData, authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, final String midLabel, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, null, + authenticationCallback); + } + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + @Override + public void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final String midLabel, + final AuthenticationCallback authenticationCallback) { + createAuthenticationInternal(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, + cardHolderData, authenticationCallback); + } + + private void createAuthenticationInternal(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, final String midLabel, + final CardHolderData cardHolderData, + final AuthenticationCallback authenticationCallback) { + if (tokenId == null || tokenId.equals("")) { + authenticationCallback.onError( + new XenditError(context.getString(R.string.create_token_error_validation))); + return; + } + + if (Double.parseDouble(amount) < 0) { + authenticationCallback.onError( + new XenditError(context.getString(R.string.create_token_error_validation))); + return; + } + + _createAuthentication(tokenId, amount, currency, cardCvn, onBehalfOf, midLabel, + cardHolderData, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(Authentication authentication) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("create-authentication") + .label("Create Authentication") + .build()); + + if (!authentication.getStatus().equalsIgnoreCase("VERIFIED")) { + registerBroadcastReceiver(authenticationCallback); + context.startActivity( + XenditActivity.getLaunchIntent(context, authentication)); + } else { + authenticationCallback.onSuccess(authentication); + } + } + + @Override + public void onFailure(NetworkError error) { + authenticationCallback.onError(new XenditError(error)); + } + })); + } + + private void get3DSRecommendation(String tokenId, final AuthenticatedToken authentication, + final TokenCallback callback) { + _get3DSRecommendation(tokenId, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(ThreeDSRecommendation rec) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("get-3ds-recommendation") + .label("Get 3DS Recommendation") + .build()); + + callback.onSuccess(new Token(authentication, rec)); + } + + @Override + public void onFailure(NetworkError error) { + callback.onSuccess(new Token(authentication)); + } + })); + } + + // createCreditCardToken with 5 arguments + @Override + public void createCreditCardToken( + Card card, + String amount, + boolean shouldAuthenticate, + boolean isMultipleUse, + final TokenCallback tokenCallback) { + + _createToken( + card, + amount, + shouldAuthenticate, + "", + isMultipleUse, + null, + null, + null, + null, + null, + null, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(AuthenticatedToken authentication) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("create-token") + .label("Create Token") + .build()); + + handle3ds1Tokenization(authentication, tokenCallback); + } + + @Override + public void onFailure(NetworkError error) { + tokenCallback.onError(new XenditError(error)); + } + })); + } + + // createCreditCardToken with 6 arguments + @Override + public void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + final TokenCallback tokenCallback) { + + _createToken( + card, + amount, + shouldAuthenticate, + onBehalfOf, + isMultipleUse, + null, + null, + null, + null, + null, + null, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(final AuthenticatedToken authentication) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("create-token") + .label("Create Token") + .build()); + + handle3ds1Tokenization(authentication, tokenCallback); + } + + @Override + public void onFailure(NetworkError error) { + tokenCallback.onError(new XenditError(error)); + } + })); + } + + // createCreditCardToken with 11 arguments + @Override + public void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback) { + + _createToken( + card, + amount, + shouldAuthenticate, + onBehalfOf, + isMultipleUse, + billingDetails, + customer, + currency, + tokenId, + cardCvn, + null, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(final AuthenticatedToken authentication) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("create-token") + .label("Create Token") + .build()); + + handle3ds1Tokenization(authentication, tokenCallback); + } + + @Override + public void onFailure(NetworkError error) { + tokenCallback.onError(new XenditError(error)); + } + })); + } + + // createCreditCardToken with 12 arguments + @Override + public void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback) { + + _createToken( + card, + amount, + shouldAuthenticate, + onBehalfOf, + isMultipleUse, + billingDetails, + customer, + currency, + tokenId, + cardCvn, + midLabel, + new NetworkHandler().setResultListener( + new ResultListener() { + @Override + public void onSuccess(final AuthenticatedToken authentication) { + TrackerController tracker = getTracker(context); + tracker.track(Structured.builder() + .category("api-request") + .action("create-token") + .label("Create Token") + .build()); + + handle3ds1Tokenization(authentication, tokenCallback); + } + + @Override + public void onFailure(NetworkError error) { + tokenCallback.onError(new XenditError(error)); + } + })); + } + + @Override + public void unregisterXenBroadcastReceiver(BroadcastReceiver receiver) { + try { + context.unregisterReceiver(receiver); + } catch (Exception ignored) { + } + } + + @Override + public void unregisterAllBroadcastReceiver() { + if (authenticationBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(authenticationBroadcastReceiver); + authenticationBroadcastReceiver = null; + } + + if (tokenBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(tokenBroadcastReceiver); + tokenBroadcastReceiver = null; + } + + if (authenticatedTokenBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(authenticatedTokenBroadcastReceiver); + authenticatedTokenBroadcastReceiver = null; + } + } + + private void registerBroadcastReceiver(final AuthenticationCallback authenticationCallback) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + @SuppressLint("UnspecifiedRegisterReceiverFlag") + public void run() { + if (authenticationBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(authenticationBroadcastReceiver); + authenticationBroadcastReceiver = null; + } + + authenticationBroadcastReceiver = + new AuthenticationBroadcastReceiver(authenticationCallback); + // if version is over 33 + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(authenticationBroadcastReceiver, + new IntentFilter(ACTION_KEY), + Context.RECEIVER_EXPORTED); + } else { + context.registerReceiver(authenticationBroadcastReceiver, + new IntentFilter(ACTION_KEY)); + } + } + }); + } + + private void registerBroadcastReceiver(final TokenCallback tokenCallback) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + + @SuppressLint("UnspecifiedRegisterReceiverFlag") + @Override + public void run() { + if (tokenBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(tokenBroadcastReceiver); + tokenBroadcastReceiver = null; + } + + tokenBroadcastReceiver = new TokenBroadcastReceiver(tokenCallback); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(tokenBroadcastReceiver, new IntentFilter(ACTION_KEY), + Context.RECEIVER_EXPORTED); + } else { + context.registerReceiver(tokenBroadcastReceiver, new IntentFilter(ACTION_KEY)); + } + } + }); + } + + private void registerBroadcastReceiverAuthenticatedToken(final TokenCallback tokenCallback) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @SuppressLint("UnspecifiedRegisterReceiverFlag") + @Override + public void run() { + if (authenticatedTokenBroadcastReceiver != null) { + unregisterXenBroadcastReceiver(authenticatedTokenBroadcastReceiver); + authenticatedTokenBroadcastReceiver = null; + } + + authenticatedTokenBroadcastReceiver = + new AuthenticatedTokenBroadcastReceiver(tokenCallback); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(authenticatedTokenBroadcastReceiver, + new IntentFilter(ACTION_KEY), Context.RECEIVER_EXPORTED); + } else { + context.registerReceiver(authenticatedTokenBroadcastReceiver, + new IntentFilter(ACTION_KEY)); + } + } + }); + } + + private void _createToken( + Card card, + String amount, + boolean shouldAuthenticate, + String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + String currency, + String tokenId, + String cardCvn, + String midLabel, + NetworkHandler handler) { + BaseRequest request = + buildBaseRequest(Request.Method.POST, CREATE_CREDIT_CARD_TOKEN_URL, onBehalfOf, + AuthenticatedToken.class, new DefaultResponseHandler<>(handler)); + + JsonObject cardData = new JsonObject(); + if (card != null) { + cardData.addProperty("account_number", card.getCreditCardNumber()); + cardData.addProperty("exp_year", card.getCardExpirationYear()); + cardData.addProperty("exp_month", card.getCardExpirationMonth()); + cardData.addProperty("cvn", card.getCreditCardCVN()); + CardHolderData cardHolderData = card.getCardHolder(); + if (cardHolderData != null) { + cardData.addProperty("card_holder_first_name", cardHolderData.getFirstName()); + cardData.addProperty("card_holder_last_name", cardHolderData.getLastName()); + cardData.addProperty("card_holder_email", cardHolderData.getEmail()); + cardData.addProperty("card_holder_phone_number", cardHolderData.getPhoneNumber()); + } + request.addJsonParam("card_data", cardData); + } + + // If tokenId doesn't exist add is_single_use and should_authenticate + if (tokenId == null) { + request.addParam("is_single_use", String.valueOf(!isMultipleUse)); + request.addParam("should_authenticate", String.valueOf(shouldAuthenticate)); + } + + if (customer != null) { + request.addJsonParam("customer", gsonMapper.toJsonTree(customer)); + } + + if (billingDetails != null) { + request.addJsonParam("billing_details", gsonMapper.toJsonTree(billingDetails)); + } + + if (!isMultipleUse) { + request.addParam("amount", amount); + } + + if (currency != null) { + request.addParam("currency", currency); + } + + if (tokenId != null) { + request.addParam("token_id", tokenId); + } + + if (cardCvn != null) { + request.addParam("card_cvn", cardCvn); + } + + if (midLabel != null) { + request.addParam("mid_label", midLabel); + } + + sendRequest(request, handler); + } + + private void _get3DSRecommendation(String tokenId, + NetworkHandler handler) { + String url = GET_3DS_URL + "?token_id=" + tokenId; + + BaseRequest request = + buildBaseRequest(Request.Method.GET, url, null, ThreeDSRecommendation.class, + new DefaultResponseHandler<>(handler)); + sendRequest(request, handler); + } + + private void _createAuthentication(String tokenId, String amount, String currency, + String cardCvn, + String onBehalfOf, String midLabel, CardHolderData cardHolder, + NetworkHandler handler) { + String requestUrl = CREATE_CREDIT_CARD_URL + "/" + tokenId + "/authentications"; + + BaseRequest request = + buildBaseRequest(Request.Method.POST, requestUrl, onBehalfOf, Authentication.class, + new DefaultResponseHandler<>(handler)); + request.addParam("amount", amount); + if (currency != null && !currency.isEmpty()) { + request.addParam("currency", currency); + } + if (cardCvn != null && !cardCvn.isEmpty()) { + request.addParam("card_cvn", cardCvn); + } + if (cardHolder != null) { + JsonObject cardHolderData = new JsonObject(); + cardHolderData.addProperty("card_holder_first_name", cardHolder.getFirstName()); + cardHolderData.addProperty("card_holder_last_name", cardHolder.getLastName()); + cardHolderData.addProperty("card_holder_email", cardHolder.getEmail()); + cardHolderData.addProperty("card_holder_phone_number", cardHolder.getPhoneNumber()); + request.addJsonParam("card_data", cardHolderData); + } + + if (midLabel != null) { + request.addParam("mid_label", midLabel); + } + + sendRequest(request, handler); + } + + private void handle3ds1Tokenization(AuthenticatedToken authentication, + TokenCallback tokenCallback) { + if (authentication.getStatus().equalsIgnoreCase("FAILED")) { + tokenCallback.onSuccess(new Token(authentication)); + } else if (!authentication.getStatus().equalsIgnoreCase("VERIFIED")) { + registerBroadcastReceiver(tokenCallback); + context.startActivity(XenditActivity.getLaunchIntent(context, authentication)); + } else { //for multi token + String tokenId = authentication.getId(); + get3DSRecommendation(tokenId, authentication, tokenCallback); + } + } + + private void handleAuthenticatedToken(String tokenId, Authentication authenticatedToken, + TokenCallback tokenCallback) { + if (authenticatedToken.getStatus().equalsIgnoreCase("VERIFIED") + || authenticatedToken.getStatus().equalsIgnoreCase("FAILED")) { + Token token = new Token(authenticatedToken, tokenId); + tokenCallback.onSuccess(token); + } else { + registerBroadcastReceiverAuthenticatedToken(tokenCallback); + context.startActivity(XenditActivity.getLaunchIntent(context, authenticatedToken)); + } + } + + private boolean is3ds2Version(String version) { + if (version != null) { + int currentMajorVersion = Integer.parseInt(version.substring(0, 1)); + return currentMajorVersion >= 2; + } + return false; + } + + private String encodeBase64(String key) { + try { + byte[] keyData = key.getBytes("UTF-8"); + return Base64.encodeToString(keyData, Base64.DEFAULT); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + + private void sendRequest(BaseRequest request, NetworkHandler handler) { + if (isConnectionAvailable()) { + requestQueue.add(request); + } else if (handler != null) { + handler.handleError(new ConnectionError()); + } + } + + private boolean isConnectionAvailable() { + if (PermissionUtils.hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE)) { + @SuppressLint("MissingPermission") NetworkInfo activeNetwork = + connectivityManager.getActiveNetworkInfo(); + return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); + } else { + return false; + } + } + + private boolean getEnvironment() { + String publishKey = publishableKey.toUpperCase(); + return publishKey.contains("PRODUCTION"); + } + + private BaseRequest buildBaseRequest(int method, String url, String onBehalfOf, Type type, + DefaultResponseHandler handler) { + String encodedKey = encodeBase64(publishableKey + ":"); + String basicAuthCredentials = "Basic " + encodedKey; + BaseRequest request = new BaseRequest<>(method, url, type, handler); + if (onBehalfOf != null && !onBehalfOf.isEmpty()) { + request.addHeader("for-user-id", onBehalfOf); + } + request.addHeader("Authorization", basicAuthCredentials.replace("\n", "")); + request.addHeader("x-client-identifier", CLIENT_IDENTIFIER); + request.addHeader("client-version", CLIENT_API_VERSION); + request.addHeader("client-type", CLIENT_TYPE); + return request; + } +} From 0f8d3a945e9ca106402c90e5f1ff01de98cc2360 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Fri, 22 Nov 2024 13:46:34 +0800 Subject: [PATCH 02/27] fix indent --- .../src/main/java/com/xendit/Xendit.java | 1214 ++++++++--------- 1 file changed, 607 insertions(+), 607 deletions(-) diff --git a/xendit-android/src/main/java/com/xendit/Xendit.java b/xendit-android/src/main/java/com/xendit/Xendit.java index ce904f8..1a09ac4 100644 --- a/xendit-android/src/main/java/com/xendit/Xendit.java +++ b/xendit-android/src/main/java/com/xendit/Xendit.java @@ -11,611 +11,611 @@ import com.xendit.utils.StoreCVNCallback; public interface Xendit { - String ACTION_KEY = "ACTION_KEY"; - - static Xendit create(final Context context, String publishableKey, - Activity activity) { - return new XenditImpl(context, publishableKey, activity); - } - - static Xendit create(final Context context, String publishableKey) { - return new XenditImpl(context, publishableKey); - } - - /** - * Determines whether the credit card number provided is valid - * - * @param creditCardNumber A credit card number - * @return true if the credit card number is valid, false otherwise - * @deprecated Use CardValidator.isCardNumberValid - */ - @Deprecated - static boolean isCardNumberValid(String creditCardNumber) { - return CardValidator.isCardNumberValid(creditCardNumber); - } - - /** - * Determines whether the card expiration month and year are valid - * - * @param cardExpirationMonth The month a card expired represented by digits (e.g. 12) - * @param cardExpirationYear The year a card expires represented by digits (e.g. 2026) - * @return true if both the expiration month and year are valid - * @deprecated Use CardValidator.isExpiryValid - */ - @Deprecated - static boolean isExpiryValid(String cardExpirationMonth, String cardExpirationYear) { - return CardValidator.isExpiryValid(cardExpirationMonth, cardExpirationYear); - } - - /** - * Determines whether the card CVN is valid - * - * @param creditCardCVN The credit card CVN - * @return true if the cvn is valid, false otherwise - * @deprecated Use CardValidator.isCvnValid - */ - @Deprecated - static boolean isCvnValid(String creditCardCVN) { - return CardValidator.isCvnValid(creditCardCVN); - } - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken(final Card card, final int amount, final TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, - final TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, - final String onBehalfOf, final TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken( - final Card card, - final int amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount you will eventually charge. This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken( - final Card card, - final int amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. This value is used to display to - * the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this - * method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. - * This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are - * required for this token - * @param currency Currency when requesting for 3DS authentication - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String currency, - TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this method unless you - * set shouldAuthenticate as false. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createSingleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - TokenCallback tokenCallback); - - /** - * Creates a single-use token. 3DS authentication will be bundled into this - * method unless you - * set shouldAuthenticate as false. - * - * @param card A credit card - * @param amount The amount in string you will eventually charge. - * This value is used to display to the - * user in the 3DS authentication view. - * @param shouldAuthenticate A flag indicating if 3DS authentication are - * required for this token - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer object making the transaction - * @param currency Currency when requesting for 3DS authentication - * @param midLabel Mid label to perform authentication if tokenization - * is bundled with authenticaiton. This is only - * applicable for switcher mid. - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - void createSingleUseToken( - final Card card, - final String amount, - final boolean shouldAuthenticate, - final String onBehalfOf, - final BillingDetails billingDetails, - final Customer customer, - final String currency, - final String midLabel, - TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createMultipleUseToken(final Card card, final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createMultipleUseToken(final Card card, final String onBehalfOf, - final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, - final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, Customer customer, final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate - * is true. - * - * @param card A credit card - * @param onBehalfOf The onBehalfOf is sub account business id - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param midLabel Mid label to perform authentication if tokenization is - * bundled with tokenization. - * This argument is only applicable for switcher merchant. - * @param tokenCallback The callback that will be called when the token creation completes or - * fails - */ - void createMultipleUseToken(final Card card, final String onBehalfOf, - BillingDetails billingDetails, Customer customer, final String midLabel, - final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if - * shouldAuthenticate - * is true. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - void createMultipleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - final TokenCallback tokenCallback); - - /** - * Creates a multiple-use token. Authentication must be created separately if - * shouldAuthenticate - * is true. - * - * @param tokenId ID of the token as the identifier - * @param cardCvn CVV number of the card - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param midLabel Mid label to perform authentication if tokenization is - * bundled with tokenization. - * This argument is only applicable for switcher merchant. - * @param tokenCallback The callback that will be called when the token - * creation completes or - * fails - */ - void createMultipleUseToken( - final BillingDetails billingDetails, - final Customer customer, - final String tokenId, - final String cardCvn, - final String midLabel, - final TokenCallback tokenCallback); - - /** - * Store CVN method will perform store cvn using an existing tokenId (retokenization). - * This method is commonly used for performing re-tokenization on subsequent usage of a multi-use - * token in the purpose of re-caching cardCVN. - * - * @param tokenId is a previously created Xendit multiple-use tokenId. Required field. - * @param cardCvn is card cvn code linked to the tokenId created. Required field. - * @param billingDetails Billing details of the card - * @param customer Customer linked to the payment method - * @param onBehalfOf The onBehalfOf is sub account business id. This field is used for merchants - * utilizing xenPlatform feature. - * @param storeCVNCallback The callback that will be called when the token re-creation completes - * or - * fails - */ - void storeCVN( - final String tokenId, - final String cardCvn, - final BillingDetails billingDetails, - final Customer customer, - final String onBehalfOf, - final StoreCVNCallback storeCVNCallback - ); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final int amount, final String currency, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final int amount, final String currency, - final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId The id of a multiple-use token - * @param amount The amount that will eventually be charged. This number is displayed to the - * user in the 3DS authentication view - * @param authenticationCallback The callback that will be called when the authentication - * creation completes or fails - */ - void createAuthentication(final String tokenId, final int amount, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId The id of a multiple-use token - * @param amount The amount that will eventually be charged. This number is displayed to the - * user in the 3DS authentication view - * @param cardHolderData Additional information of the card holder - * @param authenticationCallback The callback that will be called when the - * authentication creation completes or fails - */ - void createAuthentication(final String tokenId, final int amount, - CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the authentication - * completes or - * fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardHolderData Additional information of the card holder data - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final String onBehalfOf, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param authenticationCallback The callback that will be called when the - * authentication completes or fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param midLabel Mid label to perform authentication if - * tokenization is bundled with authenticaiton. - * This is only applicable for switcher mid. - * @param authenticationCallback The callback that will be called when the - * authentication completes or - * fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final String onBehalfOf, final String midLabel, - final AuthenticationCallback authenticationCallback); - - /** - * Creates a 3DS authentication for a multiple-use token - * - * @param tokenId A multi-use token id - * @param amount Amount of money to be authenticated - * @param currency Currency of the amount - * @param cardCvn CVV/CVN collected from the card holder - * @param cardHolderData Additional information of the card holder data - * @param onBehalfOf Business Id to call the API on behalf of - * (Applicable to Platform merchants) - * @param midLabel Mid label to perform authentication if - * tokenization is bundled with authenticaiton. - * This is only applicable for switcher mid. - * @param authenticationCallback The callback that will be called when the - * authentication completes or - * fails - */ - void createAuthentication(final String tokenId, final String amount, - final String currency, - final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, - final String midLabel, - final AuthenticationCallback authenticationCallback); - - // createCreditCardToken with 5 arguments - void createCreditCardToken( - Card card, - String amount, - boolean shouldAuthenticate, - boolean isMultipleUse, - final TokenCallback tokenCallback); - - // createCreditCardToken with 6 arguments - void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - final TokenCallback tokenCallback); - - // createCreditCardToken with 11 arguments - void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - BillingDetails billingDetails, - Customer customer, - final String currency, - final String tokenId, - final String cardCvn, - final TokenCallback tokenCallback); - - // createCreditCardToken with 12 arguments - void createCreditCardToken( - final Card card, - final String amount, - boolean shouldAuthenticate, - final String onBehalfOf, - boolean isMultipleUse, - BillingDetails billingDetails, - Customer customer, - final String currency, - final String tokenId, - final String cardCvn, - final String midLabel, - final TokenCallback tokenCallback); - - void unregisterXenBroadcastReceiver(BroadcastReceiver receiver); - - void unregisterAllBroadcastReceiver(); + String ACTION_KEY = "ACTION_KEY"; + + static Xendit create(final Context context, String publishableKey, + Activity activity) { + return new XenditImpl(context, publishableKey, activity); + } + + static Xendit create(final Context context, String publishableKey) { + return new XenditImpl(context, publishableKey); + } + + /** + * Determines whether the credit card number provided is valid + * + * @param creditCardNumber A credit card number + * @return true if the credit card number is valid, false otherwise + * @deprecated Use CardValidator.isCardNumberValid + */ + @Deprecated + static boolean isCardNumberValid(String creditCardNumber) { + return CardValidator.isCardNumberValid(creditCardNumber); + } + + /** + * Determines whether the card expiration month and year are valid + * + * @param cardExpirationMonth The month a card expired represented by digits (e.g. 12) + * @param cardExpirationYear The year a card expires represented by digits (e.g. 2026) + * @return true if both the expiration month and year are valid + * @deprecated Use CardValidator.isExpiryValid + */ + @Deprecated + static boolean isExpiryValid(String cardExpirationMonth, String cardExpirationYear) { + return CardValidator.isExpiryValid(cardExpirationMonth, cardExpirationYear); + } + + /** + * Determines whether the card CVN is valid + * + * @param creditCardCVN The credit card CVN + * @return true if the cvn is valid, false otherwise + * @deprecated Use CardValidator.isCvnValid + */ + @Deprecated + static boolean isCvnValid(String creditCardCVN) { + return CardValidator.isCvnValid(creditCardCVN); + } + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken(final Card card, final int amount, final boolean shouldAuthenticate, + final String onBehalfOf, final TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount you will eventually charge. This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final int amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. This value is used to display to + * the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param currency Currency when requesting for 3DS authentication + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String currency, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this method unless you + * set shouldAuthenticate as false. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createSingleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + TokenCallback tokenCallback); + + /** + * Creates a single-use token. 3DS authentication will be bundled into this + * method unless you + * set shouldAuthenticate as false. + * + * @param card A credit card + * @param amount The amount in string you will eventually charge. + * This value is used to display to the + * user in the 3DS authentication view. + * @param shouldAuthenticate A flag indicating if 3DS authentication are + * required for this token + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer object making the transaction + * @param currency Currency when requesting for 3DS authentication + * @param midLabel Mid label to perform authentication if tokenization + * is bundled with authenticaiton. This is only + * applicable for switcher mid. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createSingleUseToken( + final Card card, + final String amount, + final boolean shouldAuthenticate, + final String onBehalfOf, + final BillingDetails billingDetails, + final Customer customer, + final String currency, + final String midLabel, + TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if shouldAuthenticate + * is true. + * + * @param card A credit card + * @param onBehalfOf The onBehalfOf is sub account business id + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token creation completes or + * fails + */ + void createMultipleUseToken(final Card card, final String onBehalfOf, + BillingDetails billingDetails, Customer customer, final String midLabel, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback); + + /** + * Creates a multiple-use token. Authentication must be created separately if + * shouldAuthenticate + * is true. + * + * @param tokenId ID of the token as the identifier + * @param cardCvn CVV number of the card + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param midLabel Mid label to perform authentication if tokenization is + * bundled with tokenization. + * This argument is only applicable for switcher merchant. + * @param tokenCallback The callback that will be called when the token + * creation completes or + * fails + */ + void createMultipleUseToken( + final BillingDetails billingDetails, + final Customer customer, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback); + + /** + * Store CVN method will perform store cvn using an existing tokenId (retokenization). + * This method is commonly used for performing re-tokenization on subsequent usage of a multi-use + * token in the purpose of re-caching cardCVN. + * + * @param tokenId is a previously created Xendit multiple-use tokenId. Required field. + * @param cardCvn is card cvn code linked to the tokenId created. Required field. + * @param billingDetails Billing details of the card + * @param customer Customer linked to the payment method + * @param onBehalfOf The onBehalfOf is sub account business id. This field is used for merchants + * utilizing xenPlatform feature. + * @param storeCVNCallback The callback that will be called when the token re-creation completes + * or + * fails + */ + void storeCVN( + final String tokenId, + final String cardCvn, + final BillingDetails billingDetails, + final Customer customer, + final String onBehalfOf, + final StoreCVNCallback storeCVNCallback + ); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final int amount, final String currency, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final int amount, final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param authenticationCallback The callback that will be called when the authentication + * creation completes or fails + */ + void createAuthentication(final String tokenId, final int amount, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId The id of a multiple-use token + * @param amount The amount that will eventually be charged. This number is displayed to the + * user in the 3DS authentication view + * @param cardHolderData Additional information of the card holder + * @param authenticationCallback The callback that will be called when the + * authentication creation completes or fails + */ + void createAuthentication(final String tokenId, final int amount, + CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the authentication + * completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardHolderData Additional information of the card holder data + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final CardHolderData cardHolderData, final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param authenticationCallback The callback that will be called when the + * authentication completes or fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final String onBehalfOf, final String midLabel, + final AuthenticationCallback authenticationCallback); + + /** + * Creates a 3DS authentication for a multiple-use token + * + * @param tokenId A multi-use token id + * @param amount Amount of money to be authenticated + * @param currency Currency of the amount + * @param cardCvn CVV/CVN collected from the card holder + * @param cardHolderData Additional information of the card holder data + * @param onBehalfOf Business Id to call the API on behalf of + * (Applicable to Platform merchants) + * @param midLabel Mid label to perform authentication if + * tokenization is bundled with authenticaiton. + * This is only applicable for switcher mid. + * @param authenticationCallback The callback that will be called when the + * authentication completes or + * fails + */ + void createAuthentication(final String tokenId, final String amount, + final String currency, + final String cardCvn, final CardHolderData cardHolderData, final String onBehalfOf, + final String midLabel, + final AuthenticationCallback authenticationCallback); + + // createCreditCardToken with 5 arguments + void createCreditCardToken( + Card card, + String amount, + boolean shouldAuthenticate, + boolean isMultipleUse, + final TokenCallback tokenCallback); + + // createCreditCardToken with 6 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + final TokenCallback tokenCallback); + + // createCreditCardToken with 11 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final TokenCallback tokenCallback); + + // createCreditCardToken with 12 arguments + void createCreditCardToken( + final Card card, + final String amount, + boolean shouldAuthenticate, + final String onBehalfOf, + boolean isMultipleUse, + BillingDetails billingDetails, + Customer customer, + final String currency, + final String tokenId, + final String cardCvn, + final String midLabel, + final TokenCallback tokenCallback); + + void unregisterXenBroadcastReceiver(BroadcastReceiver receiver); + + void unregisterAllBroadcastReceiver(); } \ No newline at end of file From fc83a199ed09021a914285e03cd1dbd916ec376e Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Fri, 22 Nov 2024 14:45:41 +0800 Subject: [PATCH 03/27] interceptor --- .../src/main/java/com/xendit/XenditImpl.java | 22 +++++++++++++++---- .../com/xendit/interceptor/Interceptor.java | 16 ++++++++++++++ .../xendit/interceptor/InterceptorImpl.java | 20 +++++++++++++++++ .../network/DefaultResponseHandler.java | 14 ++++++++++-- 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 xendit-android/src/main/java/com/xendit/interceptor/Interceptor.java create mode 100644 xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java diff --git a/xendit-android/src/main/java/com/xendit/XenditImpl.java b/xendit-android/src/main/java/com/xendit/XenditImpl.java index 05b1d0b..65e077d 100644 --- a/xendit-android/src/main/java/com/xendit/XenditImpl.java +++ b/xendit-android/src/main/java/com/xendit/XenditImpl.java @@ -34,6 +34,7 @@ import com.xendit.Models.ThreeDSRecommendation; import com.xendit.Models.Token; import com.xendit.Models.XenditError; +import com.xendit.interceptor.Interceptor; import com.xendit.network.BaseRequest; import com.xendit.network.DefaultResponseHandler; import com.xendit.network.NetworkHandler; @@ -59,6 +60,7 @@ import io.sentry.protocol.SentryException; import io.sentry.protocol.SentryStackFrame; import io.sentry.protocol.SentryStackTrace; +import javax.annotation.Nullable; import static com.xendit.Tracker.SnowplowTrackerBuilder.getTracker; @@ -89,6 +91,15 @@ public class XenditImpl implements Xendit { private TokenBroadcastReceiver tokenBroadcastReceiver; private AuthenticatedTokenBroadcastReceiver authenticatedTokenBroadcastReceiver; + private Interceptor> requestInterceptor; + private Interceptor responseInterceptor; + + protected XenditImpl(final Context context, String publishableKey, @Nullable Interceptor> request, @Nullable Interceptor response) { + this(context, publishableKey); + this.requestInterceptor = request; + this.responseInterceptor = response; + } + protected XenditImpl(final Context context, String publishableKey, Activity activity) { this(context, publishableKey); this.activity = activity; @@ -691,7 +702,7 @@ public void onFailure(NetworkError error) { CREATE_CREDIT_CARD_TOKEN_URL, onBehalfOf == null || onBehalfOf.equals("") ? "" : onBehalfOf, Token.class, - new DefaultResponseHandler<>(handler) + new DefaultResponseHandler<>(handler, (Interceptor)responseInterceptor) ); if (tokenId == null || tokenId.equals("")) { @@ -1323,7 +1334,7 @@ private void _createToken( NetworkHandler handler) { BaseRequest request = buildBaseRequest(Request.Method.POST, CREATE_CREDIT_CARD_TOKEN_URL, onBehalfOf, - AuthenticatedToken.class, new DefaultResponseHandler<>(handler)); + AuthenticatedToken.class, new DefaultResponseHandler<>(handler, (Interceptor)responseInterceptor)); JsonObject cardData = new JsonObject(); if (card != null) { @@ -1384,7 +1395,7 @@ private void _get3DSRecommendation(String tokenId, BaseRequest request = buildBaseRequest(Request.Method.GET, url, null, ThreeDSRecommendation.class, - new DefaultResponseHandler<>(handler)); + new DefaultResponseHandler<>(handler, (Interceptor)responseInterceptor)); sendRequest(request, handler); } @@ -1396,7 +1407,7 @@ private void _createAuthentication(String tokenId, String amount, String currenc BaseRequest request = buildBaseRequest(Request.Method.POST, requestUrl, onBehalfOf, Authentication.class, - new DefaultResponseHandler<>(handler)); + new DefaultResponseHandler<>(handler, (Interceptor)responseInterceptor)); request.addParam("amount", amount); if (currency != null && !currency.isEmpty()) { request.addParam("currency", currency); @@ -1465,6 +1476,9 @@ private String encodeBase64(String key) { private void sendRequest(BaseRequest request, NetworkHandler handler) { if (isConnectionAvailable()) { + if (requestInterceptor != null) { + requestInterceptor.intercept(request); + } requestQueue.add(request); } else if (handler != null) { handler.handleError(new ConnectionError()); diff --git a/xendit-android/src/main/java/com/xendit/interceptor/Interceptor.java b/xendit-android/src/main/java/com/xendit/interceptor/Interceptor.java new file mode 100644 index 0000000..c7fa078 --- /dev/null +++ b/xendit-android/src/main/java/com/xendit/interceptor/Interceptor.java @@ -0,0 +1,16 @@ +package com.xendit.interceptor; + +import com.android.volley.VolleyError; +import com.xendit.Models.XenditError; + + +public interface Interceptor { + void intercept(T interceptedMessage); + void handleError(VolleyError error); + + interface Callback { + void onSuccess(T request); + void onError(XenditError error); + void onError(VolleyError error); + } +} diff --git a/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java b/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java new file mode 100644 index 0000000..541d35a --- /dev/null +++ b/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java @@ -0,0 +1,20 @@ +package com.xendit.interceptor; + +import com.android.volley.VolleyError; +import com.xendit.Models.XenditError; + +public class InterceptorImpl implements Interceptor { + private final Interceptor.Callback callback; + + InterceptorImpl(Interceptor.Callback callback) { + this.callback = callback; + } + + @Override public void intercept(T interceptedMessage) { + callback.onSuccess(interceptedMessage); + } + + @Override public void handleError(VolleyError error) { + callback.onError(error); + } +} diff --git a/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java b/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java index 24ad0ac..4f9fa4a 100644 --- a/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java +++ b/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java @@ -1,24 +1,31 @@ package com.xendit.network; +import androidx.annotation.Nullable; import com.android.volley.AuthFailureError; import com.android.volley.NoConnectionError; import com.android.volley.Response; import com.android.volley.TimeoutError; import com.android.volley.VolleyError; +import com.xendit.interceptor.Interceptor; import com.xendit.network.errors.AuthorisationError; import com.xendit.network.errors.ConnectionError; import com.xendit.network.errors.NetworkError; public class DefaultResponseHandler implements Response.Listener, Response.ErrorListener { - private NetworkHandler handler; + private final NetworkHandler handler;; + private final Interceptor interceptor; - public DefaultResponseHandler(NetworkHandler handler) { + public DefaultResponseHandler(NetworkHandler handler, @Nullable Interceptor interceptor) { + this.interceptor = interceptor; this.handler = handler; } @Override public void onErrorResponse(VolleyError error) { + if (interceptor != null) { + interceptor.handleError(error); + } if (handler != null) { NetworkError netError; if (error instanceof TimeoutError || error instanceof NoConnectionError) { @@ -34,6 +41,9 @@ public void onErrorResponse(VolleyError error) { @Override public void onResponse(T response) { + if (interceptor != null) { + interceptor.intercept(response); + } if (handler != null) { handler.handleSuccess(response); } From 17632cfe9016202fcf467296d317335f2eafd594 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Fri, 22 Nov 2024 17:08:07 +0800 Subject: [PATCH 04/27] testing test case --- xendit-android/build.gradle | 4 ++ .../androidTest/java/com/xendit/AuthTest.java | 14 +++++- .../java/com/xendit/TokenTest.java | 45 +++++++++++++++++-- .../xendit/interceptor/InterceptorImpl.java | 2 +- .../network/DefaultResponseHandler.java | 2 + 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/xendit-android/build.gradle b/xendit-android/build.gradle index 8da1720..44fd68f 100644 --- a/xendit-android/build.gradle +++ b/xendit-android/build.gradle @@ -104,6 +104,10 @@ dependencies { //Logging Network Calls api 'com.squareup.okhttp3:logging-interceptor:3.6.0' + androidTestImplementation('org.awaitility:awaitility:4.2.2') { + transitive = false + } + } diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 6658725..122f8b5 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -1,6 +1,7 @@ package com.xendit; import android.content.Context; +import android.os.Debug; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -11,29 +12,38 @@ import com.xendit.Models.Token; import com.xendit.Models.XenditError; +import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.runner.RunWith; +import org.awaitility.Awaitility; + @RunWith(AndroidJUnit4.class) @SmallTest public class AuthTest { - private final static String PUBLISHABLE_KEY = "xnd_public_development_O4uGfOR3gbOunJU4frcaHmLCYNLy8oQuknDm+R1r9G3S/b2lBQR+gQ=="; + private final static String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; private Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); private final Xendit xendit = Xendit.create(appContext, PUBLISHABLE_KEY); @Test public void test_createAuth() { + final boolean[] done = { false }; AuthenticationCallback callback = new AuthenticationCallback() { @Override public void onSuccess(Authentication authentication) { - + System.out.println("Success"); + done[0] = true; } @Override public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; } }; xendit.createAuthentication("9823104219412", 200, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } @Test diff --git a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java index 54bbe7f..680813a 100644 --- a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java @@ -6,11 +6,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.volley.VolleyError; +import com.xendit.Models.AuthenticatedToken; import com.xendit.Models.Card; import com.xendit.Models.CardHolderData; import com.xendit.Models.Token; import com.xendit.Models.XenditError; +import com.xendit.interceptor.Interceptor; +import com.xendit.interceptor.InterceptorImpl; +import com.xendit.network.BaseRequest; +import java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,31 +35,61 @@ public class TokenTest { private String onBehalfOf = ""; @Before public void setup() { - String PUBLISHABLE_KEY = "xnd_public_development_O4uGfOR3gbOunJU4frcaHmLCYNLy8oQuknDm+R1r9G3S/b2lBQR+gQ=="; + String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); xendit = Xendit.create(appContext, PUBLISHABLE_KEY); } @Test public void test_createSingleUseTokenAuthFalse() { - Card card = new Card("4000000000000002", + final boolean[] done = { false }; + Card card = new Card("4000000000001091", "12", - "2050", + "2030", "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + System.out.println("Success " + token.getId()); + done[0] = true; assertThat(token.getId(), isA(String.class)); assertThat(token.getAuthenticationId(), isA(String.class)); } @Override public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; fail(); } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; + Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); + Xendit xendit = new XenditImpl(appContext, PUBLISHABLE_KEY, + new Interceptor>() { + @Override public void intercept(BaseRequest interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getUrl()); + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage()); + } + }, new Interceptor() { + @Override public void intercept(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken.getId()); + done[0] = true; + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage()); + } + }); + xendit.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } @Test diff --git a/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java b/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java index 541d35a..4a2277c 100644 --- a/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java +++ b/xendit-android/src/main/java/com/xendit/interceptor/InterceptorImpl.java @@ -6,7 +6,7 @@ public class InterceptorImpl implements Interceptor { private final Interceptor.Callback callback; - InterceptorImpl(Interceptor.Callback callback) { + public InterceptorImpl(Interceptor.Callback callback) { this.callback = callback; } diff --git a/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java b/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java index 4f9fa4a..e906a71 100644 --- a/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java +++ b/xendit-android/src/main/java/com/xendit/network/DefaultResponseHandler.java @@ -25,6 +25,7 @@ public DefaultResponseHandler(NetworkHandler handler, @Nullable Interceptor Date: Tue, 3 Dec 2024 13:57:40 +0800 Subject: [PATCH 05/27] test for create token --- .../com/xendit/InterceptorTestCallback.java | 11 + .../java/com/xendit/TokenTest.java | 1225 +++++++++++++++-- 2 files changed, 1120 insertions(+), 116 deletions(-) create mode 100644 xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java diff --git a/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java new file mode 100644 index 0000000..dd7d231 --- /dev/null +++ b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java @@ -0,0 +1,11 @@ +package com.xendit; + +import com.google.gson.JsonObject; + +public interface InterceptorTestCallback { + void interceptRequest(JsonObject jsonObj); + void interceptRequestFailed(String error); + + void interceptResponse(Object interceptedMessage); + void interceptResponseFailed(String error); +} diff --git a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java index 680813a..dc84d74 100644 --- a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java @@ -7,15 +7,20 @@ import androidx.test.filters.SmallTest; import com.android.volley.VolleyError; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.xendit.Models.Address; import com.xendit.Models.AuthenticatedToken; +import com.xendit.Models.BillingDetails; import com.xendit.Models.Card; import com.xendit.Models.CardHolderData; import com.xendit.Models.Token; import com.xendit.Models.XenditError; import com.xendit.interceptor.Interceptor; -import com.xendit.interceptor.InterceptorImpl; import com.xendit.network.BaseRequest; +import java.io.UnsupportedEncodingException; import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.junit.Before; @@ -24,124 +29,452 @@ import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest public class TokenTest { - - private Xendit xendit; - private String onBehalfOf = ""; + private final String onBehalfOf = ""; + private static final Gson gson = new GsonBuilder().create(); + private static final String PROTOCOL_CHARSET = "utf-8"; + static final String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; @Before public void setup() { - String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; + } + + public static Xendit createXendit( + InterceptorTestCallback intercept + ) { Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); - xendit = Xendit.create(appContext, PUBLISHABLE_KEY); + return new XenditImpl(appContext, PUBLISHABLE_KEY, + new Interceptor>() { + @Override public void intercept(BaseRequest interceptedMessage) { + // convert utf-8 bytes to string + String jsonBody = ""; + try { + jsonBody= new String(interceptedMessage.getBody(), PROTOCOL_CHARSET); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JsonObject jsonObj = gson.fromJson(jsonBody, JsonObject.class); + System.out.println("Intercepted " + jsonObj.toString()); + + intercept.interceptRequest(jsonObj); + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage() + " " + error.getCause()); + intercept.interceptRequestFailed(error.getMessage()); + } + }, new Interceptor() { + @Override public void intercept(Object interceptedMessage) { + intercept.interceptResponse(interceptedMessage); + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage() + " " + error.getCause()); + intercept.interceptResponseFailed(error.getMessage()); + + } + }); } + // Create token with invalid card expiry month @Test - public void test_createSingleUseTokenAuthFalse() { + public void test_createSingleUseTokenAuth_invalid_card_expiry_month() { final boolean[] done = { false }; Card card = new Card("4000000000001091", - "12", + "13", "2030", "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { - System.out.println("Success " + token.getId()); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + assertEquals(xenditError.getErrorMessage(), "Card expiration date is invalid"); done[0] = true; - assertThat(token.getId(), isA(String.class)); - assertThat(token.getAuthenticationId(), isA(String.class)); + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create token with invalid card expiry year + @Test + public void test_createSingleUseToken_auth_invalid_card_expiry_year() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "20301", + "123"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { System.out.println("Error " + xenditError.getErrorMessage()); + assertEquals(xenditError.getErrorMessage(), "Card expiration date is invalid"); done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create token with invalid card number + @Test + public void test_createSingleUseToken_auth_invalid_card_number() { + final boolean[] done = { false }; + Card card = new Card("4000000000001099", + "12", + "2030", + "123"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { fail(); } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + assertEquals(xenditError.getErrorMessage(), "Card number is invalid"); + done[0] = true; + } }; - String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; - Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); - Xendit xendit = new XenditImpl(appContext, PUBLISHABLE_KEY, - new Interceptor>() { - @Override public void intercept(BaseRequest interceptedMessage) { - System.out.println("Intercepted " + interceptedMessage.getUrl()); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); - @Override public void handleError(VolleyError error) { - System.out.println("Error " + error.getMessage()); + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create token with invalid card CVN + @Test + public void test_createSingleUseToken_auth_invalid_card_cvn() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "1234"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + assertEquals(xenditError.getErrorMessage(), "Card cvn is invalid for this card type"); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); } - }, new Interceptor() { - @Override public void intercept(Object interceptedMessage) { + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create single use token without 3DS + @Test + public void test_createSingleUseToken_auth_without_3ds() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { System.out.println("Intercepted " + interceptedMessage.getClass().getName()); AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; - System.out.println("AuthenticatedToken " + authenticatedToken.getId()); + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); done[0] = true; } - - @Override public void handleError(VolleyError error) { - System.out.println("Error " + error.getMessage()); + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); } - }); - xendit.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with 3DS @Test - public void test_createSingleUseTokenAuthTrue() { - Card card = new Card("4000000000000002", - "12", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_3ds() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createSingleUseToken(card, 400, true, onBehalfOf, callback); - } + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "false"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "VERIFIED"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, false, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + // Create single use token with currency @Test - public void test_createMultipleUseToken() { - Card card = new Card("4000000000000002", - "12", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_currency() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createMultipleUseToken(card, onBehalfOf, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with unsupported currency @Test - public void test_createSingleUseTokenAuthFalseWithCardHolderData() { - CardHolderData cardHolderData = new CardHolderData("John", "Doe", "johndoe@example.com", "+628212223242526"); - Card card = new Card("4000000000000002", - "12", - "2050", - "123", - cardHolderData); + public void test_createSingleUseToken_auth_with_unsupported_currency() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { - assertThat(token.getId(), isA(String.class)); - assertThat(token.getAuthenticationId(), isA(String.class)); + fail(); } @Override @@ -150,112 +483,540 @@ public void onError(XenditError xenditError) { } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "GBP"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + } + }); + x.createSingleUseToken(card, "10000", true, "GBP", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with invalid currency @Test - public void test_createSingleUseTokenAuthTrueWithCardHolderData() { - CardHolderData cardHolderData = new CardHolderData("John", "Doe", "johndoe@example.com", "+628212223242526"); - Card card = new Card("4000000000000002", - "12", - "2050", - "123", - cardHolderData); + public void test_createSingleUseToken_auth_with_invalid_currency() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createSingleUseToken(card, 400, true, onBehalfOf, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "ZZZ"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + } + }); + x.createSingleUseToken(card, "10000", true, "ZZZ", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token without cardData.CVN + @Test + public void test_createSingleUseToken_auth_without_cardData_cvn() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + ""); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + assertEquals(xenditError.getErrorMessage(), "Card CVN is invalid"); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("cvn").toString(), ""); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + //Create single use token with complete set of cardData object @Test - public void test_createMultipleUseTokenWithCardHolderData() { - CardHolderData cardHolderData = new CardHolderData("John", "Doe", "johndoe@example.com", "+628212223242526"); - Card card = new Card("4000000000000002", - "12", - "2050", - "123", - cardHolderData); + public void test_createSingleUseToken_auth_with_cardData() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123", + new CardHolderData("John", "Doe", "johndoe@gmail.com", "+12345678") + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createMultipleUseToken(card, onBehalfOf, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + + + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with only one set of cardData object + //- cardData.cardHolderFirstName @Test - public void test_createCardTokenNotMultipleUse_deprecated() { - Card card = new Card("4000000000000002", - "12", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_cardData_cardHolderFirstName() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123", + new CardHolderData("John", null,null, null) + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createCreditCardToken(card, "123456789", false, onBehalfOf, false, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + System.out.println("Error " + error); + done[0] = true; + } + }); + + + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with only one set of cardData object + //- cardData.cardHolderLastName @Test - public void test_createCardTokenMultipleUse_deprecated() { - Card card = new Card("4000000000000002", - "12", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_cardData_cardHolderLastName() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123", + new CardHolderData(null, "Doe",null, null) + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createCreditCardToken(card, "123456789", false, onBehalfOf, true, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + System.out.println("Error " + error); + done[0] = true; + } + }); + + + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + //Create single use token with only one set of cardData object + //- cardData.cardHolderEmail + @Test + public void test_createSingleUseToken_auth_with_cardData_cardHolderEmail() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123", + new CardHolderData(null, null, "johndoe@gmail.com", null) + ); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + + + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + // Create single use token with complete set of cardData object + //- cardData.cardHolderPhoneNumber @Test - public void test_createSingleUseToken() { + public void test_createSingleUseToken_auth_with_cardData_cardHolderPhoneNumber() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123", + new CardHolderData(null, null,null, "+12345678") + ); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } - Card card = new Card("4000000000000002", - "12", - "2050", - "123"); + @Override + public void onError(XenditError xenditError) { + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + fail(); + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + System.out.println("Error " + error); + done[0] = true; + } + }); + + + x.createSingleUseToken(card, "10000", true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create single use token without amount + @Test + public void test_createSingleUseToken_auth_without_amount() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123" + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { + fail(); } @Override public void onError(XenditError xenditError) { + fail(); } }; - xendit.createSingleUseToken(card, 450, true, onBehalfOf, callback); + + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNull(jsonObj.get("amount")); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + } + }); + + + x.createSingleUseToken(card, null, true, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with mid_label @Test - public void test_createSingleUseTokenInvalidCard() { - Card card = new Card("4000000000000001", - "12", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_mid_label() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123" + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { @@ -264,20 +1025,58 @@ public void onSuccess(Token token) { @Override public void onError(XenditError xenditError) { - assertThat(xenditError.getErrorCode(), isA(String.class)); - assertEquals(xenditError.getErrorCode(), "API_VALIDATION_ERROR"); + fail(); } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("mid_label")); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + } + }); + + + x.createSingleUseToken(card, "10000", true, onBehalfOf, null, null, "IDR", "RANDOM", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with billingDetails @Test - public void test_createSingleUseTokenInvalidExpiryMonth() { - Card card = new Card("4000000000000002", - "120", - "2050", - "123"); + public void test_createSingleUseToken_auth_with_billingDetails() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123" + ); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { @@ -286,20 +1085,140 @@ public void onSuccess(Token token) { @Override public void onError(XenditError xenditError) { - assertThat(xenditError.getErrorCode(), isA(String.class)); - assertEquals(xenditError.getErrorCode(), "API_VALIDATION_ERROR"); + fail(); } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("given_names")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("surname")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("email")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("mobile_number")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("address").getAsJsonObject().get("country")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("address").getAsJsonObject().get("street_line1")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("address").getAsJsonObject().get("street_line2")); + assertNotNull(jsonObj.get("billing_details").getAsJsonObject().get("address").getAsJsonObject().get("city")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + + BillingDetails billingDetails = new BillingDetails(); + billingDetails.setGivenNames("John"); + billingDetails.setSurname("Doe"); + billingDetails.setEmail("john.doe@gmail.com"); + billingDetails.setMobileNumber("+12345678"); + Address address = new Address(); + address.setCountry("CA"); + address.setStreetLine1("California"); + address.setStreetLine2("random st"); + address.setCity("14045"); + billingDetails.setAddress(address); + + x.createSingleUseToken(card, "10000", true, onBehalfOf, billingDetails, null, "IDR", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create multipe use token without CVN + // Remark: Not possible to test this case @Test - public void test_createSingleUseTokenInvalidExpiryYear() { - Card card = new Card("4000000000000002", - "12", - "2016", - "123"); + public void test_createMultipleUseToken_auth_without_cvn() { + //final boolean[] done = { false }; + //Card card = new Card("4000000000001091", + // "12", + // "2030", + // ""); + //TokenCallback callback = new TokenCallback() { + // @Override + // public void onSuccess(Token token) { + // fail(); + // } + // + // @Override + // public void onError(XenditError xenditError) { + // System.out.println("Error " + xenditError.getErrorMessage()); + // fail(); + // } + //}; + // + //Xendit x = createXendit( + // new InterceptorTestCallback() { + // @Override + // public void interceptRequest(JsonObject jsonObj) { + // // verify if the json object contains card_data + // assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + // assertEquals(jsonObj.get("is_single_use").getAsString(), "true"); + // + // assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + // assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + // assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + // assertEquals(jsonObj.get("card_data").getAsJsonObject().get("cvn").getAsString(), ""); + // + // + // } + // @Override + // public void interceptRequestFailed(String error) { + // // should not be called + // fail(); + // } + // @Override + // public void interceptResponse(Object interceptedMessage) { + // System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + // AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + // System.out.println("AuthenticatedToken " + authenticatedToken); + // assertNotNull(authenticatedToken.getId()); + // assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + // done[0] = true; + // } + // @Override + // public void interceptResponseFailed(String error) { + // // should not be called + // fail(); + // } + // }); + //x.createMultipleUseToken(card, onBehalfOf, callback); + // + //Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create multiple use token with CVN + @Test + public void test_createMultipleUseToken_auth_with_cvn() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { @@ -308,20 +1227,59 @@ public void onSuccess(Token token) { @Override public void onError(XenditError xenditError) { - assertThat(xenditError.getErrorCode(), isA(String.class)); - assertEquals(xenditError.getErrorCode(), "API_VALIDATION_ERROR"); + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "false"); + assertEquals(jsonObj.get("is_single_use").getAsString(), "false"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "VERIFIED"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createMultipleUseToken(card, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create single use token with mid_label @Test - public void test_createSingleUseTokenInvalidCvn() { - Card card = new Card("4000000000000002", - "12", - "2050", - "12"); + public void test_createMultipleUseToken_auth_with_mid_label() { + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); TokenCallback callback = new TokenCallback() { @Override public void onSuccess(Token token) { @@ -330,11 +1288,46 @@ public void onSuccess(Token token) { @Override public void onError(XenditError xenditError) { - assertThat(xenditError.getErrorCode(), isA(String.class)); - assertEquals(xenditError.getErrorCode(), "API_VALIDATION_ERROR"); + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); } }; - xendit.createSingleUseToken(card, 400, false, onBehalfOf, callback); + Xendit x = createXendit( + new InterceptorTestCallback() { + @Override + public void interceptRequest(JsonObject jsonObj) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "false"); + assertEquals(jsonObj.get("is_single_use").getAsString(), "false"); + + assertNotNull(jsonObj.get("mid_label")); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + } + }); + x.createMultipleUseToken(card, onBehalfOf, null, null, "RANDOM", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } } \ No newline at end of file From 7376a8c2ea8f7f1583acc28bf4e0ab2debb489db Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Wed, 4 Dec 2024 15:40:19 +0800 Subject: [PATCH 06/27] test: create auth --- .../androidTest/java/com/xendit/AuthTest.java | 1049 ++++++++++++++++- .../com/xendit/InterceptorTestCallback.java | 1 + .../InterceptorTestWithURLCallback.java | 11 + .../java/com/xendit/TokenTest.java | 2 - 4 files changed, 1053 insertions(+), 10 deletions(-) create mode 100644 xendit-android/src/androidTest/java/com/xendit/InterceptorTestWithURLCallback.java diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 122f8b5..9fc3a20 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -6,28 +6,224 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.volley.AuthFailureError; +import com.android.volley.NoConnectionError; +import com.android.volley.TimeoutError; +import com.android.volley.VolleyError; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; import com.xendit.Models.AuthenticatedToken; import com.xendit.Models.Authentication; +import com.xendit.Models.BillingDetails; +import com.xendit.Models.Card; import com.xendit.Models.CardHolderData; +import com.xendit.Models.Customer; import com.xendit.Models.Token; import com.xendit.Models.XenditError; +import com.xendit.interceptor.Interceptor; +import com.xendit.network.BaseRequest; +import com.xendit.network.errors.AuthorisationError; +import com.xendit.network.errors.ConnectionError; +import com.xendit.network.errors.NetworkError; +import com.xendit.utils.StoreCVNCallback; +import java.io.UnsupportedEncodingException; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.runner.RunWith; import org.awaitility.Awaitility; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + @RunWith(AndroidJUnit4.class) @SmallTest public class AuthTest { - private final static String PUBLISHABLE_KEY = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg"; - private Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); - private final Xendit xendit = Xendit.create(appContext, PUBLISHABLE_KEY); + private final String onBehalfOf = ""; + private static final Gson gson = new GsonBuilder().create(); + private static final String PROTOCOL_CHARSET = "utf-8"; + static final String PUBLISHABLE_KEY = "xnd_public_development_O4uGfOR3gbOunJU4frcaHmLCYNLy8oQuknDm+R1r9G3S/b2lBQR+gQ=="; + + + public static Xendit createXendit( + InterceptorTestWithURLCallback intercept + ) { + Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); + return new XenditImpl(appContext, PUBLISHABLE_KEY, + new Interceptor>() { + @Override public void intercept(BaseRequest interceptedMessage) { + // convert utf-8 bytes to string + String jsonBody = ""; + try { + jsonBody= new String(interceptedMessage.getBody(), PROTOCOL_CHARSET); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JsonObject jsonObj = gson.fromJson(jsonBody, JsonObject.class); + System.out.println("Intercepted " + jsonObj.toString()); + System.out.println("Intercepted " + interceptedMessage.getUrl()); + + intercept.interceptRequest(jsonObj, interceptedMessage.getUrl()); + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage() + " " + error.getCause()); + intercept.interceptRequestFailed(error.getMessage()); + } + }, new Interceptor() { + @Override public void intercept(Object interceptedMessage) { + intercept.interceptResponse(interceptedMessage); + } + + @Override public void handleError(VolleyError error) { + System.out.println("Error " + error.getMessage() + " " + error.getCause()); + NetworkError netError; + if (error instanceof TimeoutError || error instanceof NoConnectionError) { + netError = new ConnectionError(error); + } else if (error instanceof AuthFailureError) { + netError = new AuthorisationError(error); + } else { + netError = new NetworkError(error); + } + XenditError err = new XenditError(netError); + intercept.interceptResponseFailed(err.getErrorMessage()); + + } + }); + } + + public String createSingleUseToken_auth_with_3ds() { + final boolean[] done = { false }; + final String[] authToken = { null }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "true"); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + authToken[0] = authenticatedToken.getId(); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createSingleUseToken(card, 10000, true, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + + return authToken[0]; + } + + public String createMultipleUseToken_auth_with_cvn() { + final String[] result = { "" }; + final boolean[] done = { false }; + Card card = new Card("4000000000001091", + "12", + "2030", + "123"); + TokenCallback callback = new TokenCallback() { + @Override + public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("should_authenticate").getAsString(), "false"); + assertEquals(jsonObj.get("is_single_use").getAsString(), "false"); + + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("account_number")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_year")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("exp_month")); + assertNotNull(jsonObj.get("card_data").getAsJsonObject().get("cvn")); + + + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + System.out.println("Intercepted " + interceptedMessage.getClass().getName()); + AuthenticatedToken authenticatedToken = (AuthenticatedToken) interceptedMessage; + System.out.println("AuthenticatedToken " + authenticatedToken); + assertNotNull(authenticatedToken.getId()); + assertEquals(authenticatedToken.getStatus(), "VERIFIED"); + result[0] = authenticatedToken.getId(); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + // should not be called + fail(); + } + }); + x.createMultipleUseToken(card, onBehalfOf, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + + return result[0]; + } + // Create authentication using a CREATED SINGLE USE token @Test - public void test_createAuth() { + public void test_createAuth_with_created_single_use_token() { + String authToken = createSingleUseToken_auth_with_3ds(); + System.out.println("authToken " + authToken); final boolean[] done = { false }; AuthenticationCallback callback = new AuthenticationCallback() { @Override public void onSuccess(Authentication authentication) { @@ -41,24 +237,861 @@ public void onError(XenditError xenditError) { done[0] = true; } }; - xendit.createAuthentication("9823104219412", 200, callback); + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + // should not be called + fail(); + } + }); + + x.createAuthentication(authToken, 10000, callback); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create authentication using a CREATED SINGLE USE token with CVN @Test - public void test_createAuthWithCardHolderData() { + public void test_createAuth_with_created_single_use_token_with_cvn() { + String authToken = createSingleUseToken_auth_with_3ds(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; AuthenticationCallback callback = new AuthenticationCallback() { @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; + } + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + x.createAuthentication(authToken, "10000", "IDR", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED SINGLE USE token with currency + @Test + public void test_createAuth_with_created_single_use_token_with_currency() { + String authToken = createSingleUseToken_auth_with_3ds(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; } @Override public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; } }; - CardHolderData cardHolderData = new CardHolderData("John", "Doe", "johndoe@example.com", "+628212223242526"); - xendit.createAuthentication("9823104219412", 200, cardHolderData, callback); + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + // should not be called + fail(); + } + }); + + x.createAuthentication(authToken, "10000", "IDR", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); } + // Create authentication using a CREATED SINGLE USE token with unsupported currency + @Test + public void test_createAuth_with_created_single_use_token_with_unsupported_currency() { + String authToken = createSingleUseToken_auth_with_3ds(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "GBP"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + assertEquals(error, "Invalid currency, your business cannot process transaction with this currency"); + } + }); + + x.createAuthentication(authToken, "10000", "GBP", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + //Create authentication using a CREATED SINGLE USE token with invalid currency + @Test + public void test_createAuth_with_created_single_use_token_with_invalid_currency() { + String authToken = createSingleUseToken_auth_with_3ds(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "200"); + assertEquals(jsonObj.get("currency").getAsString(), "ZZZ"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + done[0] = true; + assertEquals(error, "Currency ZZZ is invalid"); + } + }); + + x.createAuthentication(authToken, "200", "ZZZ", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token + @Test + public void test_createAuth_with_created_multiple_use_token() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + System.out.println(response); + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + x.createAuthentication(authToken, 10000, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token with CVN + @Test + public void test_createAuth_with_created_multiple_use_token_with_cvn() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + done[0] = true; + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + done[0] = true; + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + System.out.println(response); + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + x.createAuthentication(authToken, "10000", "IDR", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token with currency + @Test + public void test_createAuth_with_created_multiple_use_token_with_currency() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + System.out.println(response); + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + x.createAuthentication(authToken, "10000", "IDR", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token with unsupported currency + @Test + public void test_createAuth_with_created_multiple_use_token_with_unsupported_currency() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "GBP"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + assertEquals(error, "Invalid currency, your business cannot process transaction with this currency"); + done[0] = true; + } + }); + + x.createAuthentication(authToken, "10000", "GBP", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token with invalid currency + @Test + public void test_createAuth_with_created_multiple_use_token_with_invalid_currency() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "ZZZ"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + assertEquals(error, "Currency ZZZ is invalid"); + done[0] = true; + } + }); + + x.createAuthentication(authToken, "10000", "ZZZ", "123", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Create authentication using a CREATED MULTIPLE USE token with complete set of cardData object + @Test + public void test_createAuth_with_created_multiple_use_token_with_complete_cardData() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name").getAsString(), "John"); + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name").getAsString(), "Doe"); + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number").getAsString(), "+12345678"); + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email").getAsString(), "johndoe@example.com"); + + + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + System.out.println(response); + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + CardHolderData cardHolderData = new CardHolderData("John", "Doe", "johndoe@example.com", "+12345678"); + + x.createAuthentication(authToken, "10000", "IDR", "123", cardHolderData, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // "Create authentication using a CREATED MULTIPLE USE token with only one set of cardData object + //- cardData.cardHolderFirstName" + @Test + public void test_createAuth_with_created_multiple_use_token_with_only_one_cardData() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name").getAsString(), "John"); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + assertEquals(error, "\"card_holder_first_name\" missing required peer \"card_holder_last_name\""); + done[0] = true; + } + }); + + CardHolderData cardHolderData = new CardHolderData("John", null, null, null); + + x.createAuthentication(authToken, "10000", "IDR", "123", cardHolderData, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // "Create authentication using a CREATED MULTIPLE USE token with only one set of cardData object + //- cardData.cardHolderLastName" + @Test + public void test_createAuth_with_created_multiple_use_token_with_only_one_cardData_lastName() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name").getAsString(), "Doe"); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + assertEquals(error, "\"card_holder_last_name\" missing required peer \"card_holder_first_name\""); + done[0] = true; + } + }); + + CardHolderData cardHolderData = new CardHolderData(null, "Doe", null, null); + + x.createAuthentication(authToken, "10000", "IDR", "123", cardHolderData, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // "Create authentication using a CREATED MULTIPLE USE token with only one set of cardData object + //- cardData.cardHolderEmail" + @Test + public void test_createAuth_with_created_multiple_use_token_with_only_one_cardData_email() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email").getAsString(), "johndoe@example.com"); + + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number")); + + + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Authentication response = (Authentication) interceptedMessage; + System.out.println(response); + assertEquals(response.getStatus(), "IN_REVIEW"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + fail(); + } + }); + + CardHolderData cardHolderData = new CardHolderData(null, null, "johndoe@example.com", null); + + x.createAuthentication(authToken, "10000", "IDR", "123", cardHolderData, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + //"Create authentication using a CREATED MULTIPLE USE token with complete set of cardData object + //- cardData.cardHolderPhoneNumber" + @Test + public void test_createAuth_with_created_multiple_use_token_with_only_one_cardData_phoneNumber() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + AuthenticationCallback callback = new AuthenticationCallback() { + @Override public void onSuccess(Authentication authentication) { + System.out.println("Success"); + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("amount").getAsString(), "10000"); + assertEquals(jsonObj.get("currency").getAsString(), "IDR"); + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_first_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_last_name")); + assertNull(jsonObj.get("card_data").getAsJsonObject().get("card_holder_email")); + + assertEquals(jsonObj.get("card_data").getAsJsonObject().get("card_holder_phone_number").getAsString(), "+12345678"); + + assertTrue(url.contains(authToken)); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + // should not be called + fail(); + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + assertEquals(error, "\"card_holder_phone_number\" missing required peer \"card_holder_first_name\""); + done[0] = true; + } + }); + + CardHolderData cardHolderData = new CardHolderData(null, null, null, "+12345678"); + + x.createAuthentication(authToken, "10000", "IDR", "123", cardHolderData, callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } + + // Store CVN using a CREATED MULTIPLE USE token with card CVN + @Test + public void test_storeCVN_with_created_multiple_use_token_with_card_cvn() { + String authToken = createMultipleUseToken_auth_with_cvn(); + System.out.println("authToken " + authToken); + final boolean[] done = { false }; + StoreCVNCallback callback = new StoreCVNCallback() { + @Override public void onSuccess(Token token) { + fail(); + } + + @Override + public void onError(XenditError xenditError) { + System.out.println("Error " + xenditError.getErrorMessage()); + fail(); + } + }; + + Xendit x = createXendit( + new InterceptorTestWithURLCallback() { + @Override + public void interceptRequest(JsonObject jsonObj, String url) { + // verify if the json object contains card_data + assertEquals(jsonObj.get("card_cvn").getAsString(), "123"); + assertEquals(jsonObj.get("token_id").getAsString(), authToken); + } + @Override + public void interceptRequestFailed(String error) { + // should not be called + fail(); + } + @Override + public void interceptResponse(Object interceptedMessage) { + Token response = (Token) interceptedMessage; + assertEquals(response.getStatus(), "VERIFIED"); + done[0] = true; + } + @Override + public void interceptResponseFailed(String error) { + System.out.println("Error " + error); + // should not be called + fail(); + } + }); + + x.storeCVN(authToken, "123", new BillingDetails(), new Customer(), "", callback); + + Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> done[0]); + } } diff --git a/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java index dd7d231..64a4d49 100644 --- a/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java +++ b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestCallback.java @@ -9,3 +9,4 @@ public interface InterceptorTestCallback { void interceptResponse(Object interceptedMessage); void interceptResponseFailed(String error); } + diff --git a/xendit-android/src/androidTest/java/com/xendit/InterceptorTestWithURLCallback.java b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestWithURLCallback.java new file mode 100644 index 0000000..8809ae1 --- /dev/null +++ b/xendit-android/src/androidTest/java/com/xendit/InterceptorTestWithURLCallback.java @@ -0,0 +1,11 @@ +package com.xendit; + +import com.google.gson.JsonObject; + +public interface InterceptorTestWithURLCallback { + void interceptRequest(JsonObject jsonObj, String url); + void interceptRequestFailed(String error); + + void interceptResponse(Object interceptedMessage); + void interceptResponseFailed(String error); +} diff --git a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java index dc84d74..1ee3ef0 100644 --- a/xendit-android/src/androidTest/java/com/xendit/TokenTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/TokenTest.java @@ -27,11 +27,9 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) From 62e5d291b5e8bc27b54572f4f215893d3b147fce Mon Sep 17 00:00:00 2001 From: Pipans Cat Date: Mon, 16 Dec 2024 15:30:31 +0800 Subject: [PATCH 07/27] Create sdk_test.yml --- .github/sdk_test.yml | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/sdk_test.yml diff --git a/.github/sdk_test.yml b/.github/sdk_test.yml new file mode 100644 index 0000000..e55d662 --- /dev/null +++ b/.github/sdk_test.yml @@ -0,0 +1,46 @@ +name: SDK Test + +on: + pull_request: + +jobs: + unit-test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Print environment info + run: | + java -version + ./gradlew --version + echo "Free space:" + df -h + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Run unit tests + run: ./gradlew :xendit-android:connectedCheck --info + + # notify-slack: + # needs: unit-test + # runs-on: ubuntu-latest + # if: always() + # steps: + # - name: Notify Slack + # uses: ravsamhq/notify-slack-action@v1 + # with: + # status: ${{ needs.unit-test.result }} + # footer: 'Linked Repo <{repo_url}|{repo}> {branch}' + # notify_when: 'failure' + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From b44f90d4fc8f71b62b963a9e3a2c38d1ddfb9f7f Mon Sep 17 00:00:00 2001 From: Pipans Cat Date: Mon, 16 Dec 2024 15:34:27 +0800 Subject: [PATCH 08/27] Delete .github directory --- .github/sdk_test.yml | 46 -------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 .github/sdk_test.yml diff --git a/.github/sdk_test.yml b/.github/sdk_test.yml deleted file mode 100644 index e55d662..0000000 --- a/.github/sdk_test.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: SDK Test - -on: - pull_request: - -jobs: - unit-test: - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - - - name: Print environment info - run: | - java -version - ./gradlew --version - echo "Free space:" - df -h - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Run unit tests - run: ./gradlew :xendit-android:connectedCheck --info - - # notify-slack: - # needs: unit-test - # runs-on: ubuntu-latest - # if: always() - # steps: - # - name: Notify Slack - # uses: ravsamhq/notify-slack-action@v1 - # with: - # status: ${{ needs.unit-test.result }} - # footer: 'Linked Repo <{repo_url}|{repo}> {branch}' - # notify_when: 'failure' - # env: - # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From 5743aa21202ffc9de021852a46d2ef2ba20fd52b Mon Sep 17 00:00:00 2001 From: Pipans Cat Date: Mon, 16 Dec 2024 15:36:03 +0800 Subject: [PATCH 09/27] Create sdk_test.yml --- .github/workflows/sdk_test.yml | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/sdk_test.yml diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml new file mode 100644 index 0000000..e55d662 --- /dev/null +++ b/.github/workflows/sdk_test.yml @@ -0,0 +1,46 @@ +name: SDK Test + +on: + pull_request: + +jobs: + unit-test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Print environment info + run: | + java -version + ./gradlew --version + echo "Free space:" + df -h + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Run unit tests + run: ./gradlew :xendit-android:connectedCheck --info + + # notify-slack: + # needs: unit-test + # runs-on: ubuntu-latest + # if: always() + # steps: + # - name: Notify Slack + # uses: ravsamhq/notify-slack-action@v1 + # with: + # status: ${{ needs.unit-test.result }} + # footer: 'Linked Repo <{repo_url}|{repo}> {branch}' + # notify_when: 'failure' + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From fba02de06d99c72c44f257e37ff4cb29f7b94d4a Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 15:44:11 +0800 Subject: [PATCH 10/27] Update sdk_test.yml --- .github/workflows/sdk_test.yml | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index e55d662..fbac95c 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -15,6 +15,30 @@ jobs: java-version: '17' distribution: 'temurin' + - name: Touch local properties + run: touch local.properties + + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-29 + + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + arch: x86_64 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + ram-size: 4096M + script: echo "Generated AVD snapshot for caching." + - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -28,8 +52,15 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Run unit tests - run: ./gradlew :xendit-android:connectedCheck --info + - name: Run Instrumentation Test + uses: reactivecircus/android-emulator-runner@v2 + with: + emulator-build: 7425822 + api-level: 29 + arch: x86_64 + force-avd-creation: false + ram-size: 4096M + script: ./gradlew connectedQaDebugAndroidTest --stacktrace --no-daemon # notify-slack: # needs: unit-test From 65203dd3b7cad1c07b0c366ff25c5dd2f8621d3c Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 15:49:12 +0800 Subject: [PATCH 11/27] Update sdk_test.yml --- .github/workflows/sdk_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index fbac95c..560db04 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -32,7 +32,7 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 - arch: x86_64 + arch: arm64-v8a force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false @@ -60,7 +60,7 @@ jobs: arch: x86_64 force-avd-creation: false ram-size: 4096M - script: ./gradlew connectedQaDebugAndroidTest --stacktrace --no-daemon + script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon # notify-slack: # needs: unit-test From cc13c48aa666384d64df3ba63feb73a434c6405f Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:00:44 +0800 Subject: [PATCH 12/27] Update sdk_test.yml --- .github/workflows/sdk_test.yml | 50 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 560db04..b1839d3 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -9,6 +9,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Gradle cache + uses: gradle/gradle-build-action@v2 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -18,26 +21,26 @@ jobs: - name: Touch local properties run: touch local.properties - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-29 +# - name: AVD cache +# uses: actions/cache@v3 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-29 - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 29 - arch: arm64-v8a - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - ram-size: 4096M - script: echo "Generated AVD snapshot for caching." +# - name: create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: 29 +# arch: arm64-v8a +# force-avd-creation: false +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# ram-size: 4096M +# script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -55,12 +58,13 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: - emulator-build: 7425822 - api-level: 29 +# emulator-build: 7425822 + api-level: 30 + target: google_apis arch: x86_64 - force-avd-creation: false +# force-avd-creation: false ram-size: 4096M - script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon + script: ./gradlew :xendit-android:connectedCheck --stacktrace # notify-slack: # needs: unit-test From 285451718f07c75d75196c3a596aa2fddf990e6e Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:11:49 +0800 Subject: [PATCH 13/27] Update sdk_test.yml --- .github/workflows/sdk_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index b1839d3..c345593 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -5,7 +5,7 @@ on: jobs: unit-test: - runs-on: macos-latest + runs-on: macos-15 steps: - uses: actions/checkout@v3 From 2b5967c6955b94a806342500eb842667c340ef36 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:14:10 +0800 Subject: [PATCH 14/27] non arm --- .github/workflows/sdk_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index c345593..2fbed09 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -5,7 +5,7 @@ on: jobs: unit-test: - runs-on: macos-15 + runs-on: macos-13 steps: - uses: actions/checkout@v3 From 5c8b82ae6aa36624bacab6863c51614d3469dfc7 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:19:32 +0800 Subject: [PATCH 15/27] add back caching --- .github/workflows/sdk_test.yml | 47 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 2fbed09..d63740f 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -21,26 +21,26 @@ jobs: - name: Touch local properties run: touch local.properties -# - name: AVD cache -# uses: actions/cache@v3 -# id: avd-cache -# with: -# path: | -# ~/.android/avd/* -# ~/.android/adb* -# key: avd-29 + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-29 -# - name: create AVD and generate snapshot for caching -# if: steps.avd-cache.outputs.cache-hit != 'true' -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: 29 -# arch: arm64-v8a -# force-avd-creation: false -# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: false -# ram-size: 4096M -# script: echo "Generated AVD snapshot for caching." + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + arch: arm64-v8a + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + ram-size: 4096M + script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -58,13 +58,12 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: -# emulator-build: 7425822 - api-level: 30 - target: google_apis + emulator-build: 7425822 + api-level: 29 arch: x86_64 -# force-avd-creation: false + force-avd-creation: false ram-size: 4096M - script: ./gradlew :xendit-android:connectedCheck --stacktrace + script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon # notify-slack: # needs: unit-test From 8225152d5d475f71fffc71db83c18c49da2ff6e4 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:24:15 +0800 Subject: [PATCH 16/27] try ubuntu --- .github/workflows/sdk_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index d63740f..02d955d 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -5,7 +5,7 @@ on: jobs: unit-test: - runs-on: macos-13 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 2a1fd0dbb3aa1c8794d254cb8312f02453b605cf Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:26:41 +0800 Subject: [PATCH 17/27] try without caching --- .github/workflows/sdk_test.yml | 47 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 02d955d..b4498ca 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -21,26 +21,26 @@ jobs: - name: Touch local properties run: touch local.properties - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-29 +# - name: AVD cache +# uses: actions/cache@v3 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-29 - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 29 - arch: arm64-v8a - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - ram-size: 4096M - script: echo "Generated AVD snapshot for caching." +# - name: create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: 29 +# arch: arm64-v8a +# force-avd-creation: false +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# ram-size: 4096M +# script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -58,12 +58,13 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: - emulator-build: 7425822 - api-level: 29 +# emulator-build: 7425822 + api-level: 30 + target: google_apis arch: x86_64 - force-avd-creation: false +# force-avd-creation: false ram-size: 4096M - script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon + script: ./gradlew :xendit-android:connectedCheck --stacktrace # notify-slack: # needs: unit-test From 5796cfae56fdb275d79b74ab3d2b72a247356266 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:28:48 +0800 Subject: [PATCH 18/27] Revert "try without caching" This reverts commit 2a1fd0dbb3aa1c8794d254cb8312f02453b605cf. --- .github/workflows/sdk_test.yml | 47 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index b4498ca..02d955d 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -21,26 +21,26 @@ jobs: - name: Touch local properties run: touch local.properties -# - name: AVD cache -# uses: actions/cache@v3 -# id: avd-cache -# with: -# path: | -# ~/.android/avd/* -# ~/.android/adb* -# key: avd-29 + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-29 -# - name: create AVD and generate snapshot for caching -# if: steps.avd-cache.outputs.cache-hit != 'true' -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: 29 -# arch: arm64-v8a -# force-avd-creation: false -# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: false -# ram-size: 4096M -# script: echo "Generated AVD snapshot for caching." + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + arch: arm64-v8a + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + ram-size: 4096M + script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -58,13 +58,12 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: -# emulator-build: 7425822 - api-level: 30 - target: google_apis + emulator-build: 7425822 + api-level: 29 arch: x86_64 -# force-avd-creation: false + force-avd-creation: false ram-size: 4096M - script: ./gradlew :xendit-android:connectedCheck --stacktrace + script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon # notify-slack: # needs: unit-test From 69fec4d81eedd7a0d1c7ccddbe69e54b33e25c5d Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:28:51 +0800 Subject: [PATCH 19/27] Revert "try ubuntu" This reverts commit 8225152d5d475f71fffc71db83c18c49da2ff6e4. --- .github/workflows/sdk_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 02d955d..d63740f 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -5,7 +5,7 @@ on: jobs: unit-test: - runs-on: ubuntu-latest + runs-on: macos-13 steps: - uses: actions/checkout@v3 From 2ecc5c5cba4f68aaa4d5f76be29316e8571fe4b7 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 16:28:56 +0800 Subject: [PATCH 20/27] Revert "add back caching" This reverts commit 5c8b82ae6aa36624bacab6863c51614d3469dfc7. --- .github/workflows/sdk_test.yml | 47 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index d63740f..2fbed09 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -21,26 +21,26 @@ jobs: - name: Touch local properties run: touch local.properties - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-29 +# - name: AVD cache +# uses: actions/cache@v3 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-29 - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 29 - arch: arm64-v8a - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - ram-size: 4096M - script: echo "Generated AVD snapshot for caching." +# - name: create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: 29 +# arch: arm64-v8a +# force-avd-creation: false +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# ram-size: 4096M +# script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -58,12 +58,13 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: - emulator-build: 7425822 - api-level: 29 +# emulator-build: 7425822 + api-level: 30 + target: google_apis arch: x86_64 - force-avd-creation: false +# force-avd-creation: false ram-size: 4096M - script: ./gradlew :xendit-android:connectedCheck --stacktrace --no-daemon + script: ./gradlew :xendit-android:connectedCheck --stacktrace # notify-slack: # needs: unit-test From bd03d4fc36312d28ed3f70a52aa4d52aa7639eb8 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:04:50 +0800 Subject: [PATCH 21/27] test error --- xendit-android/src/androidTest/java/com/xendit/AuthTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 9fc3a20..3286e27 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -140,6 +140,7 @@ public void interceptResponse(Object interceptedMessage) { System.out.println("AuthenticatedToken " + authenticatedToken); assertNotNull(authenticatedToken.getId()); assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + fail(); authToken[0] = authenticatedToken.getId(); done[0] = true; } From d6bcfaaa463fa0a6e70b878275405139d440c476 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:09:27 +0800 Subject: [PATCH 22/27] Revert "test error" This reverts commit bd03d4fc36312d28ed3f70a52aa4d52aa7639eb8. --- xendit-android/src/androidTest/java/com/xendit/AuthTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 3286e27..9fc3a20 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -140,7 +140,6 @@ public void interceptResponse(Object interceptedMessage) { System.out.println("AuthenticatedToken " + authenticatedToken); assertNotNull(authenticatedToken.getId()); assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); - fail(); authToken[0] = authenticatedToken.getId(); done[0] = true; } From 696871701a9ac70f47d4884f7dfab7725c6acb0d Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:14:04 +0800 Subject: [PATCH 23/27] tru ubuntu again --- .github/workflows/sdk_test.yml | 54 ++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 2fbed09..84dd21f 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -5,13 +5,19 @@ on: jobs: unit-test: - runs-on: macos-13 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Gradle cache uses: gradle/gradle-build-action@v2 + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Set up JDK 17 uses: actions/setup-java@v3 with: @@ -21,26 +27,25 @@ jobs: - name: Touch local properties run: touch local.properties -# - name: AVD cache -# uses: actions/cache@v3 -# id: avd-cache -# with: -# path: | -# ~/.android/avd/* -# ~/.android/adb* -# key: avd-29 + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-30 -# - name: create AVD and generate snapshot for caching -# if: steps.avd-cache.outputs.cache-hit != 'true' -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: 29 -# arch: arm64-v8a -# force-avd-creation: false -# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: false -# ram-size: 4096M -# script: echo "Generated AVD snapshot for caching." + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 30 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + ram-size: 4096M + script: echo "Generated AVD snapshot for caching." - name: Setup Gradle uses: gradle/gradle-build-action@v2 @@ -58,12 +63,11 @@ jobs: - name: Run Instrumentation Test uses: reactivecircus/android-emulator-runner@v2 with: -# emulator-build: 7425822 api-level: 30 - target: google_apis - arch: x86_64 -# force-avd-creation: false + force-avd-creation: false + disable-animations: true ram-size: 4096M + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none script: ./gradlew :xendit-android:connectedCheck --stacktrace # notify-slack: From 682936fccdf0f30f0a41dcc3472e2d50193a3352 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:16:32 +0800 Subject: [PATCH 24/27] change arch --- .github/workflows/sdk_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 84dd21f..5f605a7 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -41,6 +41,7 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: 30 + arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false @@ -64,6 +65,7 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: 30 + arch: x86_64 force-avd-creation: false disable-animations: true ram-size: 4096M From 86e5165f3140efbd37e712cca5e30aa5bc31806e Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:24:30 +0800 Subject: [PATCH 25/27] try fail --- xendit-android/src/androidTest/java/com/xendit/AuthTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 9fc3a20..3286e27 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -140,6 +140,7 @@ public void interceptResponse(Object interceptedMessage) { System.out.println("AuthenticatedToken " + authenticatedToken); assertNotNull(authenticatedToken.getId()); assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); + fail(); authToken[0] = authenticatedToken.getId(); done[0] = true; } From 3bdf27c3b06429717da25c1ac633cdb19d744507 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Mon, 16 Dec 2024 17:28:34 +0800 Subject: [PATCH 26/27] working now revert --- xendit-android/src/androidTest/java/com/xendit/AuthTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java index 3286e27..9fc3a20 100644 --- a/xendit-android/src/androidTest/java/com/xendit/AuthTest.java +++ b/xendit-android/src/androidTest/java/com/xendit/AuthTest.java @@ -140,7 +140,6 @@ public void interceptResponse(Object interceptedMessage) { System.out.println("AuthenticatedToken " + authenticatedToken); assertNotNull(authenticatedToken.getId()); assertEquals(authenticatedToken.getStatus(), "IN_REVIEW"); - fail(); authToken[0] = authenticatedToken.getId(); done[0] = true; } From 383448eb1aeb084ed35438ab21381b68f71262a0 Mon Sep 17 00:00:00 2001 From: Fadzli Fadzil Date: Tue, 17 Dec 2024 16:35:12 +0800 Subject: [PATCH 27/27] coverage report --- .github/workflows/sdk_test.yml | 13 +++++++++++++ build.gradle | 2 ++ jacoco.gradle | 26 ++++++++++++++++++++++++++ xendit-android/build.gradle | 4 ++++ 4 files changed, 45 insertions(+) create mode 100644 jacoco.gradle diff --git a/.github/workflows/sdk_test.yml b/.github/workflows/sdk_test.yml index 5f605a7..9a4d3a3 100644 --- a/.github/workflows/sdk_test.yml +++ b/.github/workflows/sdk_test.yml @@ -58,6 +58,9 @@ jobs: echo "Free space:" df -h + - name: Run Coverage + run: ./gradlew :xendit-android:jacocoReport + - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -72,6 +75,16 @@ jobs: emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none script: ./gradlew :xendit-android:connectedCheck --stacktrace + - name: Add coverage to PR + id: jacoco + uses: madrapps/jacoco-report@v1.7.1 + with: + paths: | + ${{ github.workspace }}/**/build/reports/coverage/androidTest/debug/connected/report.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 + # notify-slack: # needs: unit-test # runs-on: ubuntu-latest diff --git a/build.gradle b/build.gradle index 340aa48..1c34b00 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,8 @@ buildscript { classpath 'com.android.tools.build:gradle:8.2.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0' + //Jacoco Plugin + classpath "org.jacoco:org.jacoco.core:0.8.10" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/jacoco.gradle b/jacoco.gradle new file mode 100644 index 0000000..06c5389 --- /dev/null +++ b/jacoco.gradle @@ -0,0 +1,26 @@ +apply plugin: 'jacoco' + +task jacocoReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') { + reports { + csv { enabled false } // change if needed + xml { enabled true } // change if needed + html { + enabled true + destination file("${buildDir}/coverage-report") + } + } + + // Setup the .class, source, and execution directories + final fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*'] + + // Include this if you use Kotlin + final kotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) + final javacTree = fileTree(dir: "${project.buildDir}/intermediates/javac/debug", excludes: fileFilter) + final mainSrc = "${project.projectDir}/src/main/java" + + sourceDirectories.setFrom files([mainSrc]) + classDirectories.setFrom files([kotlinTree, javacTree]) + executionData.setFrom fileTree(dir: project.buildDir, includes: [ + 'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec' + ]) +} \ No newline at end of file diff --git a/xendit-android/build.gradle b/xendit-android/build.gradle index 44fd68f..283a755 100644 --- a/xendit-android/build.gradle +++ b/xendit-android/build.gradle @@ -1,6 +1,8 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'signing' +apply plugin: 'jacoco' +apply from: "$project.rootDir/jacoco.gradle" group 'com.xendit' version '4.2.2' @@ -49,6 +51,8 @@ android { } debug { minifyEnabled false + enableUnitTestCoverage true + testCoverageEnabled true consumerProguardFiles 'proguard-rules.pro' buildConfigField('long', 'VERSION_CODE', "${defaultConfig.versionCode}") buildConfigField('String','VERSION_NAME', "\"${defaultConfig.versionName}\"")