├── sample ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable-xxxhdpi │ │ │ ├── failed.png │ │ │ ├── success.png │ │ │ ├── googlepay_button_background_image.9.png │ │ │ └── googlepay_button_no_shadow_background_image.9.png │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── googlepay_strings.xml │ │ │ ├── styles.xml │ │ │ └── strings.xml │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-hdpi │ │ │ ├── googlepay_button_background_image.9.png │ │ │ └── googlepay_button_no_shadow_background_image.9.png │ │ ├── drawable-mdpi │ │ │ ├── googlepay_button_background_image.9.png │ │ │ └── googlepay_button_no_shadow_background_image.9.png │ │ ├── drawable-xhdpi │ │ │ ├── googlepay_button_background_image.9.png │ │ │ └── googlepay_button_no_shadow_background_image.9.png │ │ ├── drawable-xxhdpi │ │ │ ├── googlepay_button_background_image.9.png │ │ │ └── googlepay_button_no_shadow_background_image.9.png │ │ ├── drawable │ │ │ ├── googlepay_button_background.xml │ │ │ ├── ic_check_green_24dp.xml │ │ │ ├── googlepay_button_overlay.xml │ │ │ ├── ic_error_red_24dp.xml │ │ │ ├── button_bg.xml │ │ │ └── googlepay_button_content.xml │ │ ├── drawable-v21 │ │ │ ├── googlepay_button_background.xml │ │ │ └── googlepay_button_no_shadow_background.xml │ │ └── layout │ │ │ ├── bottom_sheet_layout.xml │ │ │ ├── googlepay_button.xml │ │ │ ├── activity_main.xml │ │ │ └── activity_collect_card_info.xml │ │ ├── java │ │ └── com │ │ │ └── mastercard │ │ │ └── gateway │ │ │ └── android │ │ │ └── sampleapp │ │ │ ├── utils │ │ │ ├── RegionInfo.kt │ │ │ ├── Ui.kt │ │ │ ├── PaymentOptionLabelResolver.kt │ │ │ ├── SimpleTextChangedWatcher.kt │ │ │ ├── PaymentsClientWrapper.kt │ │ │ ├── DeviceProvider.kt │ │ │ ├── PaymentOptionsParser.kt │ │ │ ├── BaseUrlInterceptor.kt │ │ │ ├── PaymentOptionsSheet.kt │ │ │ └── AuthAndBrowserHandler.kt │ │ │ ├── SampleApplication.kt │ │ │ ├── di │ │ │ ├── GooglePayModule.kt │ │ │ └── NetworkModule.kt │ │ │ ├── viewmodel │ │ │ ├── MainViewModel.kt │ │ │ ├── CollectCardInfoViewModel.kt │ │ │ └── ProcessPaymentViewModel.kt │ │ │ ├── repo │ │ │ ├── Repository.kt │ │ │ └── PrefsRepository.kt │ │ │ ├── api │ │ │ └── MerchantService.kt │ │ │ ├── MainActivity.java │ │ │ ├── CollectCardInfoActivity.java │ │ │ └── ProcessPaymentActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── gateway-android ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ ├── robolectric.properties │ │ │ └── mockito-extensions │ │ │ │ └── org.mockito.plugins.MockMaker │ │ └── java │ │ │ └── com │ │ │ └── mastercard │ │ │ └── gateway │ │ │ └── android │ │ │ └── sdk │ │ │ ├── TestApplication.java │ │ │ ├── TestGatewaySSLContextProvider.java │ │ │ ├── GatewayBrowserPaymentActivityTest.java │ │ │ ├── Gateway3DSecureActivityTest.java │ │ │ └── GatewayTest.java │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── mastercard │ │ │ │ └── gateway │ │ │ │ └── android │ │ │ │ └── sdk │ │ │ │ ├── GatewayRequest.java │ │ │ │ ├── Gateway3DSecureActivity.java │ │ │ │ ├── GatewayBrowserPaymentActivity.java │ │ │ │ ├── Logger.java │ │ │ │ ├── GatewayGooglePayCallback.java │ │ │ │ ├── GatewayException.java │ │ │ │ ├── GatewayCallback.java │ │ │ │ ├── GatewayTLSSocketFactory.java │ │ │ │ ├── GatewaySSLContextProvider.java │ │ │ │ └── BaseGatewayPaymentActivity.java │ │ └── res │ │ │ ├── values │ │ │ └── strings.xml │ │ │ └── layout │ │ │ └── activity_3dsecure.xml │ ├── release │ │ └── java │ │ │ └── com │ │ │ └── mastercard │ │ │ └── gateway │ │ │ └── android │ │ │ └── sdk │ │ │ └── BaseLogger.java │ └── debug │ │ └── java │ │ └── com │ │ └── mastercard │ │ └── gateway │ │ └── android │ │ └── sdk │ │ └── BaseLogger.java ├── proguard.pro └── build.gradle ├── settings.gradle ├── .gitignore ├── payment-flow.png ├── sample-configuration.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .travis.yml ├── gradle.properties ├── RELEASE_NOTES.md ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE.txt /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /gateway-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':gateway-android' 2 | -------------------------------------------------------------------------------- /gateway-android/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | sdk=28 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | -------------------------------------------------------------------------------- /gateway-android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /payment-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/payment-flow.png -------------------------------------------------------------------------------- /sample-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample-configuration.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxxhdpi/failed.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxxhdpi/success.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FF5B5D 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/googlepay_button_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-hdpi/googlepay_button_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/googlepay_button_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-mdpi/googlepay_button_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/googlepay_button_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xhdpi/googlepay_button_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/RegionInfo.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | data class RegionInfo( 4 | val name: String, 5 | val prefix: String 6 | ) -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/googlepay_button_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxhdpi/googlepay_button_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/googlepay_button_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxxhdpi/googlepay_button_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/googlepay_button_no_shadow_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-hdpi/googlepay_button_no_shadow_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/googlepay_button_no_shadow_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-mdpi/googlepay_button_no_shadow_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/googlepay_button_no_shadow_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxhdpi/googlepay_button_no_shadow_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/googlepay_button_no_shadow_background_image.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard-Gateway/gateway-android-sdk/HEAD/sample/src/main/res/drawable-xxxhdpi/googlepay_button_no_shadow_background_image.9.png -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/SampleApplication.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | 7 | @HiltAndroidApp 8 | class SampleApplication : Application() -------------------------------------------------------------------------------- /sample/src/main/res/drawable/googlepay_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gateway-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 11 20:12:39 IST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/googlepay_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/googlepay_button_no_shadow_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gateway-android/src/test/java/com/mastercard/gateway/android/sdk/TestApplication.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import android.app.Application; 4 | 5 | 6 | public class TestApplication extends Application { 7 | 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_check_green_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayRequest.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | class GatewayRequest { 7 | 8 | String url; 9 | Gateway.Method method; 10 | 11 | Map extraHeaders = new HashMap<>(); 12 | 13 | GatewayMap payload; 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/Ui.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import android.view.View 4 | 5 | object Ui { 6 | 7 | @JvmStatic 8 | fun show(v: View?) { 9 | v?.visibility = View.VISIBLE 10 | } 11 | 12 | @JvmStatic 13 | fun hide(v: View?) { 14 | v?.visibility = View.GONE 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gateway-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3-D Secure Authentication 4 | Browser Payment 5 | Missing summary status 6 | Missing 3-D Secure Id 7 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/PaymentOptionLabelResolver.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | object PaymentOptionLabelResolver { 4 | 5 | fun labelFor(option: String?): String = when (option) { 6 | "CARD" -> "Card" 7 | "KNET" -> "KNET" 8 | "BENEFIT" -> "Benefit" 9 | "QPAY" -> "QPAY" 10 | "OMAN" -> "Oman Net" 11 | else -> option ?: "Pay" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/src/main/res/values/googlepay_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Google Pay 4 | Buy with Google Pay 5 | Save to Google Pay 6 | Add to Google Pay 7 | 8 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/googlepay_button_overlay.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_error_red_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gateway-android/src/release/java/com/mastercard/gateway/android/sdk/BaseLogger.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | 4 | import javax.net.ssl.HttpsURLConnection; 5 | 6 | 7 | class BaseLogger implements Logger { 8 | 9 | @Override 10 | public void logRequest(HttpsURLConnection c, String data) { 11 | // no-op 12 | } 13 | 14 | @Override 15 | public void logResponse(HttpsURLConnection c, String data) { 16 | // no-op 17 | } 18 | 19 | @Override 20 | public void logDebug(String message) { 21 | // no-op 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/SimpleTextChangedWatcher.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | 4 | import android.text.Editable 5 | import android.text.TextWatcher 6 | 7 | class SimpleTextChangedWatcher(private val onAnyChange: Runnable) : TextWatcher { 8 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} 9 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { 10 | onAnyChange.run() 11 | } 12 | override fun afterTextChanged(s: Editable?) {} 13 | } 14 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/di/GooglePayModule.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.di 2 | 3 | import android.content.Context 4 | import com.mastercard.gateway.android.sampleapp.utils.PaymentsClientWrapper 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ActivityComponent 9 | import dagger.hilt.android.qualifiers.ActivityContext 10 | 11 | @Module 12 | @InstallIn(ActivityComponent::class) // Not SingletonComponent! 13 | object GooglePayModule { 14 | 15 | @Provides 16 | fun providePaymentsClientWrapper(@ActivityContext context: Context): PaymentsClientWrapper { 17 | return PaymentsClientWrapper(context); 18 | } 19 | } -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\e036307\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/Gateway3DSecureActivity.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | public class Gateway3DSecureActivity extends BaseGatewayPaymentActivity { 8 | 9 | private static final String DEFAULT_TITLE = "3D Secure"; 10 | 11 | @NonNull @Override protected String gatewayHost() { return "3dsecure"; } 12 | 13 | @NonNull @Override protected String getDefaultTitle() { return DEFAULT_TITLE; } 14 | 15 | @Override 16 | protected void onGatewayRedirect(@NonNull Uri uri) { 17 | // Expected form: gatewaysdk://3dsecure?acsResult=... 18 | String result = getQueryParam(uri, "acsResult"); 19 | complete(EXTRA_GATEWAY_RESULT, result); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayBrowserPaymentActivity.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | public class GatewayBrowserPaymentActivity extends BaseGatewayPaymentActivity { 8 | 9 | private static final String DEFAULT_TITLE = "Payment"; 10 | 11 | @NonNull @Override protected String gatewayHost() { return "browserpayment"; } 12 | 13 | @NonNull @Override protected String getDefaultTitle() { return DEFAULT_TITLE; } 14 | 15 | @Override 16 | protected void onGatewayRedirect(@NonNull Uri uri) { 17 | // Expected form: gatewaysdk://paymentbrowser?orderResult=... 18 | String result = getQueryParam(uri, "orderResult"); 19 | complete(EXTRA_GATEWAY_RESULT, result); 20 | } 21 | } -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/PaymentsClientWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import android.content.Context 4 | import com.google.android.gms.wallet.IsReadyToPayRequest 5 | import com.google.android.gms.wallet.PaymentsClient 6 | import com.google.android.gms.wallet.Wallet 7 | import com.google.android.gms.wallet.Wallet.WalletOptions 8 | import com.google.android.gms.wallet.WalletConstants 9 | import com.google.android.gms.tasks.Task 10 | 11 | 12 | class PaymentsClientWrapper(context: Context?) { 13 | val client: PaymentsClient 14 | 15 | init { 16 | val walletOptions = WalletOptions.Builder() 17 | .setEnvironment(WalletConstants.ENVIRONMENT_TEST) 18 | .build() 19 | 20 | this.client = Wallet.getPaymentsClient(context!!, walletOptions) 21 | } 22 | 23 | fun isReadyToPay(request: IsReadyToPayRequest?): Task { 24 | return client.isReadyToPay(request!!) 25 | } 26 | } -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/Logger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mastercard 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mastercard.gateway.android.sdk; 18 | 19 | 20 | import javax.net.ssl.HttpsURLConnection; 21 | 22 | interface Logger { 23 | void logRequest(HttpsURLConnection c, String data); 24 | void logResponse(HttpsURLConnection c, String data); 25 | void logDebug(String message); 26 | } 27 | -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayGooglePayCallback.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | 4 | import com.google.android.gms.common.api.Status; 5 | import com.google.android.gms.wallet.PaymentData; 6 | 7 | import org.json.JSONObject; 8 | 9 | public interface GatewayGooglePayCallback { 10 | 11 | /** 12 | * Called when payment data is returned from GooglePay 13 | * 14 | * @param paymentData A json object containing details about the payment 15 | * @see PaymentData 16 | */ 17 | void onReceivedPaymentData(JSONObject paymentData); 18 | 19 | /** 20 | * Called when a user cancels a GooglePay transaction 21 | */ 22 | void onGooglePayCancelled(); 23 | 24 | /** 25 | * Called when an error occurs during a GooglePay transaction 26 | * 27 | * @param status The corresponding status object of the request 28 | */ 29 | void onGooglePayError(Status status); 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | android: 4 | components: 5 | - tools 6 | - platform-tools 7 | - tools 8 | - build-tools-28.0.3 9 | - android-29 10 | - extra-google-m2repository 11 | - extra-android-m2repository 12 | 13 | # workaround for license accepting issue 14 | before_install: 15 | - yes | sdkmanager "platforms;android-29" 16 | 17 | # dependency caching 18 | before_cache: 19 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 20 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 21 | cache: 22 | directories: 23 | - $HOME/.m2 24 | - $HOME/.gradle/caches/ 25 | - $HOME/.gradle/wrapper/ 26 | 27 | script: travis_retry ./gradlew clean --refresh-dependencies gateway-android:lintRelease gateway-android:testReleaseUnitTest gateway-android:assembleRelease gateway-android:androidSourcesJar gateway-android:androidJavadocsJar gateway-android:generatePomFileForAarPublication 28 | 29 | deploy: 30 | provider: script 31 | skip_cleanup: true 32 | script: ./gradlew gateway-android:bintrayUpload 33 | on: 34 | tags: true 35 | -------------------------------------------------------------------------------- /gateway-android/proguard.pro: -------------------------------------------------------------------------------- 1 | ## proguard 2 | -dontwarn java.lang.invoke.* 3 | -dontwarn **$$Lambda$* 4 | 5 | ## GSON ## 6 | # Gson uses generic type information stored in a class file when working with fields. Proguard 7 | # removes such information by default, so configure it to keep all of it. 8 | -keepattributes Signature 9 | 10 | # For using GSON @Expose annotation 11 | -keepattributes *Annotation* 12 | 13 | # Gson specific classes 14 | -dontwarn sun.misc.** 15 | #-keep class com.google.gson.stream.** { *; } 16 | 17 | # Prevent proguard from stripping interface information from TypeAdapterFactory, 18 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 19 | -keep class * implements com.google.gson.TypeAdapterFactory 20 | -keep class * implements com.google.gson.JsonSerializer 21 | -keep class * implements com.google.gson.JsonDeserializer 22 | 23 | 24 | # keep api contract and enums 25 | -keep class com.mastercard.gateway.android.sdk.api.** { *; } 26 | -keep enum com.mastercard.gateway.android.sdk.** { *; } 27 | 28 | # Optional libraries will warn on missing classes 29 | -dontwarn io.reactivex.** -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/DeviceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import dagger.hilt.android.qualifiers.ApplicationContext 7 | import java.util.Calendar 8 | import java.util.TimeZone 9 | import javax.inject.Inject 10 | import javax.inject.Singleton 11 | 12 | @Singleton 13 | class DeviceInfoProvider @Inject constructor( 14 | @ApplicationContext private val context: Context 15 | ){ 16 | 17 | @RequiresApi(Build.VERSION_CODES.N) 18 | fun getLanguage(): String { 19 | return context.resources.configuration.locales[0].language ?: "" 20 | } 21 | 22 | fun getScreenWidth(): Int { 23 | return context.resources.displayMetrics.widthPixels 24 | } 25 | 26 | fun getScreenHeight(): Int { 27 | return context.resources.displayMetrics.heightPixels 28 | } 29 | 30 | fun getTimezoneOffsetInMinutes(): Int { 31 | val now = Calendar.getInstance() 32 | val tz = TimeZone.getDefault() 33 | return tz.getOffset(now.timeInMillis) / 60000 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/bottom_sheet_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.mastercard.gateway.android.sampleapp.repo.PrefsRepository 6 | import com.mastercard.gateway.android.sampleapp.utils.RegionInfo 7 | import com.mastercard.gateway.android.sdk.Gateway 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.launch 10 | import javax.inject.Inject 11 | 12 | @HiltViewModel 13 | class MainViewModel @Inject constructor(private val prefsRepo: PrefsRepository) : ViewModel() { 14 | 15 | val merchantId = prefsRepo.getMerchantId() 16 | val region = prefsRepo.getRegion() 17 | val merchantServerLink = prefsRepo.getServerUrl() 18 | 19 | val regions: List = Gateway.Region.entries.map { RegionInfo(it.name, it.prefix) } 20 | 21 | 22 | fun saveSessionData(merchantId: String, region: String, link: String) { 23 | viewModelScope.launch { 24 | prefsRepo.saveMerchantId(merchantId) 25 | prefsRepo.saveRegion(region) 26 | prefsRepo.saveServerUrl(link) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/PaymentOptionsParser.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import com.mastercard.gateway.android.sdk.GatewayMap 4 | 5 | object PaymentOptionsParser { 6 | 7 | object Constants { 8 | const val CARD = "CARD" 9 | } 10 | 11 | @JvmStatic 12 | fun extractTypes(options: GatewayMap?): List { 13 | val result = options?.get("result") as? String ?: return emptyList() 14 | if (!result.equals("SUCCESS", ignoreCase = true)) return emptyList() 15 | 16 | val paymentTypes: Map<*, *> = when (val pt = options["paymentTypes"]) { 17 | is GatewayMap -> pt 18 | is Map<*, *> -> pt 19 | else -> return emptyList() 20 | } 21 | 22 | val out = linkedSetOf() 23 | 24 | if (paymentTypes["card"] != null) out += Constants.CARD 25 | 26 | (paymentTypes["browserPayment"] as? List<*>)?.forEach { item -> 27 | val type = when (item) { 28 | is Map<*, *> -> item["type"] as? String 29 | is GatewayMap -> item["type"] as? String 30 | else -> null 31 | } 32 | if (!type.isNullOrBlank()) out += type 33 | } 34 | 35 | return out.toList() 36 | } 37 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=true 2 | org.gradle.configureondemand=false 3 | org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 4 | android.useAndroidX=true 5 | android.enableJetifier=true 6 | 7 | # ===== Coordinates ===== 8 | GROUP=com.mastercard.gateway 9 | POM_ARTIFACT_ID=gateway-android 10 | VERSION_NAME=1.1.9 11 | 12 | # ===== POM Info ===== 13 | POM_NAME=Mastercard Payment Gateway Android SDK 14 | POM_DESCRIPTION=The official Android SDK for Mastercard Payment Gateway Services (MPGS) 15 | POM_INCEPTION_YEAR=2020 16 | POM_URL=https://github.com/Mastercard-Gateway/gateway-android-sdk 17 | 18 | # ===== License ===== 19 | POM_LICENCE_NAME=Apache-2.0 20 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 21 | POM_LICENCE_DIST=repo 22 | 23 | # ===== SCM (Source Control) ===== 24 | POM_SCM_URL=https://github.com/Mastercard-Gateway/gateway-android-sdk 25 | POM_SCM_CONNECTION=scm:git:git://github.com/Mastercard-Gateway/gateway-android-sdk.git 26 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/Mastercard-Gateway/gateway-android-sdk.git 27 | 28 | # ===== Developer Info ===== 29 | POM_DEVELOPER_ID=mastercard 30 | POM_DEVELOPER_NAME=Mastercard MPGS Team 31 | POM_DEVELOPER_EMAIL=simplify_mobile_team@mastercard.com 32 | POM_DEVELOPER_URL=https://www.mastercard.com 33 | 34 | # ===== Vanniktech Plugin Flags ===== 35 | mavenCentralPublishing=true 36 | signAllPublications=true -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mastercard 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mastercard.gateway.android.sdk; 18 | 19 | public class GatewayException extends Exception { 20 | 21 | int statusCode; 22 | GatewayMap error; 23 | 24 | public GatewayException() { 25 | } 26 | 27 | public GatewayException(String message) { 28 | super(message); 29 | } 30 | 31 | 32 | public int getStatusCode() { 33 | return statusCode; 34 | } 35 | 36 | public void setStatusCode(int statusCode) { 37 | this.statusCode = statusCode; 38 | } 39 | 40 | public GatewayMap getErrorResponse() { 41 | return error; 42 | } 43 | 44 | public void setErrorResponse(GatewayMap error) { 45 | this.error = error; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/googlepay_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 17 | 24 | 25 | 31 | 32 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/BaseUrlInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import android.util.Log 4 | import com.mastercard.gateway.android.sampleapp.repo.PrefsRepository 5 | import com.mastercard.gateway.android.sampleapp.viewmodel.ProcessPaymentViewModel 6 | import okhttp3.HttpUrl 7 | import okhttp3.Interceptor 8 | import okhttp3.Response 9 | import java.util.Locale 10 | import javax.inject.Inject 11 | 12 | class BaseUrlInterceptor @Inject constructor( 13 | private val prefsRepository: PrefsRepository 14 | ) : Interceptor { 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | val originalRequest = chain.request() 17 | val originalUrl = originalRequest.url 18 | 19 | // Fetch latest user-configurable values 20 | val region = prefsRepository.getRegion() 21 | val version = ProcessPaymentViewModel.API_VERSION 22 | 23 | val newBaseUrl = HttpUrl.Builder() 24 | .scheme("https") 25 | .host("${region.lowercase(Locale.ROOT)}.gateway.mastercard.com") 26 | .addPathSegment("api") 27 | .addPathSegment("rest") 28 | .addPathSegment("version") 29 | .addPathSegment(version) 30 | .build() 31 | 32 | // Extract the remaining path after baseUrl 33 | val newFullUrl = newBaseUrl.newBuilder() 34 | for (segment in originalUrl.pathSegments) { 35 | newFullUrl.addPathSegment(segment) 36 | } 37 | 38 | val updatedRequest = originalRequest.newBuilder() 39 | .url(newFullUrl.build()) 40 | .build() 41 | 42 | Log.e("URL_INTERCEPTOR", "Final URL: ${updatedRequest.url}") 43 | 44 | return chain.proceed(updatedRequest) 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/repo/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.repo 2 | 3 | import com.mastercard.gateway.android.sampleapp.api.MerchantService 4 | import com.mastercard.gateway.android.sdk.GatewayMap 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | class Repository @Inject constructor( 12 | private val merchantService: MerchantService 13 | ) { 14 | 15 | suspend fun createSession(payload: GatewayMap): GatewayMap = 16 | withContext(Dispatchers.IO) { 17 | merchantService.createSession(payload) 18 | } 19 | 20 | suspend fun initiateAuthentication( 21 | orderId: String, 22 | transactionId: String, 23 | payload: GatewayMap 24 | ): GatewayMap = 25 | withContext(Dispatchers.IO) { 26 | merchantService.initiateAuthentication(orderId, transactionId, payload) 27 | } 28 | 29 | suspend fun submitTransaction( 30 | orderId: String, 31 | transactionId: String, 32 | payload: GatewayMap 33 | ): GatewayMap = 34 | withContext(Dispatchers.IO) { 35 | merchantService.submitTransaction(orderId, transactionId, payload) 36 | } 37 | 38 | suspend fun inquirePaymentOptions(): GatewayMap = 39 | withContext(Dispatchers.IO) { 40 | merchantService.inquirePaymentOptions() 41 | } 42 | 43 | suspend fun initiateBrowserPayment( 44 | orderId: String, 45 | transactionId: String, 46 | request: GatewayMap 47 | ): GatewayMap = 48 | withContext(Dispatchers.IO) { 49 | merchantService.initiateBrowserPayment(orderId, transactionId, request) 50 | } 51 | } -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Mastercard 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mastercard.gateway.android.sdk; 18 | 19 | 20 | public interface GatewayCallback { 21 | 22 | /** 23 | * Callback on a successful call to the Gateway API 24 | * 25 | * @param response A response map 26 | */ 27 | default void onSuccess(GatewayMap response) { 28 | } 29 | 30 | /** 31 | * Callback executed when error thrown during call to Gateway API 32 | * 33 | * @param throwable The exception thrown 34 | */ 35 | default void onError(Throwable throwable) { 36 | } 37 | 38 | /** 39 | * Callback method when webview-based authentication is complete. 40 | * 41 | * @param result A response map containing the result 42 | * @param requestCode Request code identifying the flow 43 | */ 44 | default void onComplete(GatewayMap result, int requestCode) { 45 | } 46 | 47 | /** 48 | * Callback when a user cancels the flow (typically on back press). 49 | * 50 | * @param requestCode Request code identifying the flow 51 | */ 52 | default void onCancel(int requestCode) { 53 | } 54 | } -------------------------------------------------------------------------------- /gateway-android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.vanniktech.maven.publish' 3 | 4 | android { 5 | 6 | compileSdk 35 7 | namespace = "com.mastercard.gateway.android" 8 | 9 | defaultConfig { 10 | minSdkVersion 23 11 | targetSdk 35 12 | versionName libraryVersionName 13 | consumerProguardFiles 'proguard.pro' 14 | buildConfigField "String", "SDK_VERSION", "\"${versionName}\"" 15 | } 16 | 17 | compileOptions { 18 | sourceCompatibility JavaVersion.VERSION_1_8 19 | targetCompatibility JavaVersion.VERSION_1_8 20 | } 21 | 22 | lintOptions { 23 | abortOnError false 24 | } 25 | 26 | buildFeatures { 27 | buildConfig true 28 | dataBinding true 29 | } 30 | 31 | buildTypes { 32 | debug 33 | release 34 | } 35 | 36 | testOptions { 37 | unitTests { 38 | includeAndroidResources = true 39 | } 40 | } 41 | } 42 | 43 | // define an 'optional' dependency 44 | configurations { 45 | optional 46 | implementation.extendsFrom optional 47 | } 48 | 49 | dependencies { 50 | implementation fileTree(include: ['*.jar'], dir: 'libs') 51 | 52 | // required 53 | implementation 'androidx.appcompat:appcompat:1.1.0' 54 | implementation 'com.google.code.gson:gson:2.13.2' 55 | implementation 'com.google.android.material:material:1.12.0' 56 | 57 | // optional for rx java 58 | optional 'io.reactivex.rxjava2:rxjava:2.2.5' 59 | 60 | // optional for google pay 61 | optional 'androidx.legacy:legacy-support-v4:1.0.0' 62 | optional 'com.google.android.gms:play-services-wallet:19.5.0' 63 | 64 | compileOnly 'androidx.annotation:annotation:1.1.0' 65 | 66 | testImplementation 'junit:junit:4.13.2' 67 | testImplementation 'org.robolectric:robolectric:4.16' 68 | testImplementation 'org.mockito:mockito-core:5.20.0' 69 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.di 2 | 3 | import com.mastercard.gateway.android.sampleapp.api.MerchantService 4 | import com.mastercard.gateway.android.sampleapp.repo.PrefsRepository 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import okhttp3.OkHttpClient 10 | import okhttp3.logging.HttpLoggingInterceptor 11 | import retrofit2.Retrofit 12 | import retrofit2.converter.gson.GsonConverterFactory 13 | import java.util.concurrent.TimeUnit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object NetworkModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideOkHttpClient(): OkHttpClient { 23 | val timeoutValue = 500L 24 | val httpInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) 25 | 26 | return OkHttpClient.Builder() 27 | .addInterceptor(httpInterceptor) 28 | .readTimeout(timeoutValue, TimeUnit.SECONDS) 29 | .connectTimeout(timeoutValue, TimeUnit.SECONDS) 30 | .writeTimeout(timeoutValue, TimeUnit.SECONDS) 31 | .build() 32 | } 33 | 34 | @Provides 35 | @Singleton 36 | fun provideMerchantRetrofit( 37 | prefsRepository: PrefsRepository, 38 | okHttpClient: OkHttpClient 39 | ): Retrofit { 40 | val merchantUrl = prefsRepository.getServerUrl() 41 | return Retrofit.Builder() 42 | .baseUrl(merchantUrl) 43 | .client(okHttpClient) 44 | .addConverterFactory(GsonConverterFactory.create()) 45 | .build() 46 | } 47 | 48 | @Provides 49 | @Singleton 50 | fun provideMerchantService(retrofit: Retrofit): MerchantService { 51 | return retrofit.create(MerchantService::class.java) 52 | } 53 | } -------------------------------------------------------------------------------- /gateway-android/src/main/res/layout/activity_3dsecure.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 13 | 14 | 19 | 20 | 21 | 22 | 26 | 27 | 31 | 32 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/repo/PrefsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.repo 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class PrefsRepository @Inject constructor( 11 | @ApplicationContext context: Context 12 | ) { 13 | 14 | private val prefs: SharedPreferences = 15 | context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 16 | 17 | companion object { 18 | private const val PREFS_NAME = "app_prefs" 19 | 20 | private const val KEY_MERCHANT_ID = "merchant_id" 21 | private const val KEY_REGION = "region" 22 | private const val KEY_SERVER_URL = "server_url" 23 | private const val KEY_PAYMENT_TYPE = "payment_type" 24 | 25 | private const val DEFAULT_STRING = "" 26 | } 27 | 28 | fun saveMerchantId(merchantId: String) = prefs.edit { putString(KEY_MERCHANT_ID, merchantId) } 29 | fun saveRegion(region: String) = prefs.edit { putString(KEY_REGION, region) } 30 | fun saveServerUrl(url: String) = prefs.edit { putString(KEY_SERVER_URL, url) } 31 | fun savePaymentType(type: String) = prefs.edit { putString(KEY_PAYMENT_TYPE, type) } 32 | fun getMerchantId(): String = prefs.getString(KEY_MERCHANT_ID, DEFAULT_STRING) ?: DEFAULT_STRING 33 | fun getRegion(): String = prefs.getString(KEY_REGION, DEFAULT_STRING) ?: DEFAULT_STRING 34 | fun getServerUrl(): String = prefs.getString(KEY_SERVER_URL, DEFAULT_STRING) ?: DEFAULT_STRING 35 | fun getPaymentType(): String = prefs.getString(KEY_PAYMENT_TYPE, DEFAULT_STRING) ?: DEFAULT_STRING 36 | 37 | private inline fun SharedPreferences.edit( 38 | commit: Boolean = false, 39 | action: SharedPreferences.Editor.() -> Unit 40 | ) { 41 | val editor = edit() 42 | action(editor) 43 | if (commit) editor.commit() else editor.apply() 44 | } 45 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/api/MerchantService.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.api 2 | 3 | import androidx.annotation.Keep 4 | import com.mastercard.gateway.android.sdk.GatewayMap 5 | import retrofit2.http.Body 6 | import retrofit2.http.GET 7 | import retrofit2.http.POST 8 | import retrofit2.http.PUT 9 | import retrofit2.http.Query 10 | 11 | @Keep 12 | @JvmSuppressWildcards 13 | interface MerchantService { 14 | 15 | @POST(CREATE_SESSION_ENDPOINT) 16 | suspend fun createSession(@Body payload: GatewayMap): GatewayMap 17 | @POST(PAYMENT_OPTIONS_INQUIRY_ENDPOINT) 18 | suspend fun inquirePaymentOptions(): GatewayMap 19 | 20 | @PUT(START_AUTHENTICATION_ENDPOINT) 21 | suspend fun initiateAuthentication( 22 | @Query(ORDER_ID_PARAM) orderId: String, 23 | @Query(TRANSACTION_ID_PARAM) transactionId: String, 24 | @Body payload: GatewayMap 25 | ): GatewayMap 26 | 27 | @PUT(START_BROWSER_PAYMENT_ENDPOINT) 28 | suspend fun initiateBrowserPayment( 29 | @Query(ORDER_ID_PARAM) orderId: String, 30 | @Query(TRANSACTION_ID_PARAM) transactionId: String, 31 | @Body payload: GatewayMap 32 | ): GatewayMap 33 | 34 | @PUT(SUBMIT_TRANSACTION_ENDPOINT) 35 | suspend fun submitTransaction( 36 | @Query(ORDER_ID_PARAM) orderId: String, 37 | @Query(TRANSACTION_ID_PARAM) transactionId: String, 38 | @Body payload: GatewayMap 39 | ): GatewayMap 40 | 41 | companion object { 42 | // Query keys (keep consistent & reusable) 43 | const val ORDER_ID_PARAM = "orderId" 44 | const val TRANSACTION_ID_PARAM = "transactionId" 45 | 46 | // Endpoints 47 | const val CREATE_SESSION_ENDPOINT = "session.php" 48 | const val SUBMIT_TRANSACTION_ENDPOINT = "transaction.php" 49 | const val START_AUTHENTICATION_ENDPOINT = "start-authentication.php" 50 | 51 | const val PAYMENT_OPTIONS_INQUIRY_ENDPOINT = "payment-options-inquiry.php" 52 | const val START_BROWSER_PAYMENT_ENDPOINT = "start-browser-payment.php" 53 | } 54 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 41 | 42 | 46 | 47 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/utils/PaymentOptionsSheet.kt: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.util.TypedValue 6 | import android.view.View 7 | import android.widget.Button 8 | import android.widget.LinearLayout 9 | import com.google.android.material.bottomsheet.BottomSheetDialog 10 | import com.mastercard.gateway.android.sampleapp.R 11 | 12 | class PaymentOptionsSheet( 13 | context: Context, 14 | sheetContent: View 15 | ) { 16 | 17 | fun interface Listener { 18 | fun onOptionClicked(optionType: String) 19 | } 20 | 21 | private val dialog = BottomSheetDialog(context).apply { setContentView(sheetContent) } 22 | private val buttonContainer: LinearLayout = 23 | sheetContent.findViewById(R.id.dynamicButtonContainer) 24 | 25 | fun show(types: List, listener: Listener) { 26 | buttonContainer.removeAllViews() 27 | types.forEach { type -> 28 | val btn = buildButton(buttonContainer.context, type).apply { 29 | setOnClickListener { 30 | listener.onOptionClicked(type) 31 | dialog.dismiss() 32 | } 33 | } 34 | buttonContainer.addView(btn) 35 | } 36 | dialog.show() 37 | } 38 | 39 | private fun buildButton(ctx: Context, type: String): Button { 40 | val marginPx = TypedValue.applyDimension( 41 | TypedValue.COMPLEX_UNIT_DIP, 10f, ctx.resources.displayMetrics 42 | ).toInt() 43 | 44 | val lp = LinearLayout.LayoutParams( 45 | LinearLayout.LayoutParams.MATCH_PARENT, 46 | LinearLayout.LayoutParams.WRAP_CONTENT 47 | ).apply { setMargins(0, 0, 0, marginPx) } 48 | 49 | return Button(ctx).apply { 50 | layoutParams = lp 51 | setBackgroundResource(R.color.coral) 52 | setTextColor(Color.WHITE) 53 | tag = type 54 | text = PaymentOptionLabelResolver.labelFor(type) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /gateway-android/src/debug/java/com/mastercard/gateway/android/sdk/BaseLogger.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import javax.net.ssl.HttpsURLConnection; 11 | 12 | class BaseLogger implements Logger { 13 | 14 | @Override 15 | public void logRequest(HttpsURLConnection c, String data) { 16 | String log = "REQUEST: " + c.getRequestMethod() + " " + c.getURL().toString(); 17 | 18 | if (data != null) { 19 | log += "\n-- Data: " + data; 20 | } 21 | 22 | // log request headers 23 | Map> properties = c.getRequestProperties(); 24 | Set keys = properties.keySet(); 25 | for (String key : keys) { 26 | List values = properties.get(key); 27 | for (String value : values) { 28 | log += "\n-- " + key + ": " + value; 29 | } 30 | } 31 | 32 | String[] parts = log.split("\n"); 33 | for (String part : parts) { 34 | logDebug(part); 35 | } 36 | } 37 | 38 | @Override 39 | public void logResponse(HttpsURLConnection c, String data) { 40 | String log = "RESPONSE: "; 41 | 42 | // log response headers 43 | Map> headers = c.getHeaderFields(); 44 | Set keys = headers.keySet(); 45 | 46 | int i = 0; 47 | for (String key : keys) { 48 | List values = headers.get(key); 49 | for (String value : values) { 50 | if (i == 0 && key == null) { 51 | log += value; 52 | 53 | if (data != null && data.length() > 0) { 54 | log += "\n-- Data: " + data; 55 | } 56 | } else { 57 | log += "\n-- " + (key == null ? "" : key + ": ") + value; 58 | } 59 | i++; 60 | } 61 | } 62 | 63 | log += "\n-- Cipher Suite: " + c.getCipherSuite(); 64 | 65 | String[] parts = log.split("\n"); 66 | for (String part : parts) { 67 | logDebug(part); 68 | } 69 | } 70 | 71 | @Override 72 | public void logDebug(String message) { 73 | Log.d(Gateway.class.getSimpleName(), message); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gateway-android/src/test/java/com/mastercard/gateway/android/sdk/TestGatewaySSLContextProvider.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | import org.robolectric.annotation.Config; 8 | 9 | import java.security.KeyStore; 10 | import java.security.cert.X509Certificate; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertNotNull; 14 | import static org.junit.Assert.assertTrue; 15 | import static org.mockito.ArgumentMatchers.any; 16 | import static org.mockito.Mockito.doReturn; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.spy; 19 | 20 | @RunWith(RobolectricTestRunner.class) 21 | @Config(manifest = Config.NONE) 22 | public class TestGatewaySSLContextProvider { 23 | 24 | GatewaySSLContextProvider trustProvider; 25 | 26 | @Before 27 | public void setUp() throws Exception { 28 | trustProvider = spy(new GatewaySSLContextProvider()); 29 | } 30 | 31 | @Test 32 | public void testCreateSslKeystoreContainsInternalCertificate() throws Exception { 33 | doReturn(mock(X509Certificate.class)).when(trustProvider).readCertificate(any()); 34 | 35 | KeyStore keyStore = trustProvider.createKeyStore(); 36 | 37 | assertTrue(keyStore.containsAlias("gateway.mastercard.com.ca_entrust")); 38 | assertTrue(keyStore.containsAlias("gateway.mastercard.com.ca_digicert")); 39 | } 40 | 41 | @Test 42 | public void testReadingRootEntrustCertificateWorksAsExpected() throws Exception { 43 | X509Certificate certificate = trustProvider.readCertificate(GatewaySSLContextProvider.ROOT_CERTIFICATE_ENTRUST); 44 | String expectedSerialNo = "1246989352"; 45 | 46 | assertNotNull(certificate); 47 | assertEquals(expectedSerialNo, certificate.getSerialNumber().toString()); 48 | } 49 | 50 | @Test 51 | public void testReadingRootDigiCertificateWorksAsExpected() throws Exception { 52 | X509Certificate certificate = trustProvider.readCertificate(GatewaySSLContextProvider.ROOT_CERTIFICATE_DIGICERT); 53 | String expectedSerialNo = "4293743540046975378534879503202253541"; 54 | 55 | assertNotNull(certificate); 56 | assertEquals(expectedSerialNo, certificate.getSerialNumber().toString()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | 9 | ## [1.1.9] - 2025-11-21 10 | ### Added 11 | * Enabled support for Browser Payment flow, including Ottu integration. 12 | * Added a loader inside WebView to show progress until content is fully loaded. 13 | * Added full Kotlin support in the SDK and sample application. 14 | 15 | ### Changed 16 | * Refactored minimal SDK code to support both 3DS and Browser Payment flows. 17 | * Replaced Gateway3DSecureCallback with unified GatewayCallback. 18 | * Refactored and partially rewrote the sample application. 19 | * Updated Gradle plugin to com.android.tools.build:gradle:8.13.1 20 | * Increased minSdkVersion from 19 → 23. 21 | * Cleaned up code related to JCenter. 22 | * Updated and added new unit tests. 23 | * Updated libraries to address CVE vulnerabilities. 24 | 25 | [1.1.9-beta01] - 2025-09-04 26 | ### Added 27 | - Enabled support for Browser Payment flow, including Ottu integration. 28 | - Added a loader inside WebView to show progress until content is fully loaded. 29 | - Added full Kotlin support in the SDK and sample application. 30 | 31 | ### Changed 32 | - Refactored minimal SDK code to support both 3DS and Browser Payment flows. 33 | - Replaced Gateway3DSecureCallback with unified GatewayCallback. 34 | - Refactored and partially rewrote the sample application. 35 | - Updated Gradle plugin to com.android.tools.build:gradle:8.4.0. 36 | - Increased minSdkVersion from 19 → 21. 37 | - Cleaned up code related to JCenter. 38 | - Updated and added new unit tests. 39 | 40 | ## [1.1.8] - 2025-03-25 41 | ### Added 42 | - Updated Gson library to version 2.12.1 to address security vulnerabilities. 43 | 44 | ## [1.1.7] - 2025-02-21 45 | ### Added 46 | - DigiCert updated. New Expiry Jan 15, 2038 47 | 48 | ## [1.1.6] - 2024-02-08 49 | ### Added 50 | - Saudi region (KSA) URL 51 | 52 | ## [1.1.5] - 2022-12-28 53 | ### Changed 54 | - Pinned certificate updated. New Expiry December 2030 55 | 56 | ## [1.1.4] - 2020-03-26 57 | ### Fixed 58 | - Issue where WebView was not displaying the 3DS HTML on apps targeting API >=29 59 | ### Changed 60 | - SDK and sample app now targeting API 29 61 | - Migrated from legacy Android support libraries to Jetpack 62 | 63 | ## [1.1.3] - 2020-02-14 64 | ### Added 65 | - China region (CN) URL 66 | ### Changed 67 | - Enabled TLSv1.2 support for API <21 68 | 69 | ## [1.1.2] - 2020-02-04 70 | ### Added 71 | - India region (IN) URL 72 | -------------------------------------------------------------------------------- /gateway-android/src/main/java/com/mastercard/gateway/android/sdk/GatewayTLSSocketFactory.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.Socket; 6 | import java.net.UnknownHostException; 7 | 8 | import javax.net.ssl.SSLContext; 9 | import javax.net.ssl.SSLSocket; 10 | import javax.net.ssl.SSLSocketFactory; 11 | 12 | /** 13 | * Custom SSL socket factory required to enable TLSv1.2 on KitKat devices and below 14 | */ 15 | class GatewayTLSSocketFactory extends SSLSocketFactory { 16 | 17 | private SSLSocketFactory internalSSLSocketFactory; 18 | 19 | public GatewayTLSSocketFactory(SSLContext context) { 20 | internalSSLSocketFactory = context.getSocketFactory(); 21 | } 22 | 23 | @Override 24 | public String[] getDefaultCipherSuites() { 25 | return internalSSLSocketFactory.getDefaultCipherSuites(); 26 | } 27 | 28 | @Override 29 | public String[] getSupportedCipherSuites() { 30 | return internalSSLSocketFactory.getSupportedCipherSuites(); 31 | } 32 | 33 | @Override 34 | public Socket createSocket() throws IOException { 35 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); 36 | } 37 | 38 | @Override 39 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 40 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); 41 | } 42 | 43 | @Override 44 | public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 45 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); 46 | } 47 | 48 | @Override 49 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { 50 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); 51 | } 52 | 53 | @Override 54 | public Socket createSocket(InetAddress host, int port) throws IOException { 55 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); 56 | } 57 | 58 | @Override 59 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 60 | return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); 61 | } 62 | 63 | private Socket enableTLSOnSocket(Socket socket) { 64 | if(socket != null && (socket instanceof SSLSocket)) { 65 | ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"}); 66 | } 67 | return socket; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /gateway-android/src/test/java/com/mastercard/gateway/android/sdk/GatewayBrowserPaymentActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sdk; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.mockito.ArgumentMatchers.any; 6 | import static org.mockito.ArgumentMatchers.anyInt; 7 | import static org.mockito.ArgumentMatchers.eq; 8 | import static org.mockito.Mockito.doNothing; 9 | import static org.mockito.Mockito.spy; 10 | import static org.mockito.Mockito.verify; 11 | 12 | import android.app.Activity; 13 | import android.content.Intent; 14 | import android.net.Uri; 15 | 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | import org.mockito.ArgumentCaptor; 19 | import org.robolectric.RobolectricTestRunner; 20 | import org.robolectric.annotation.Config; 21 | 22 | @RunWith(RobolectricTestRunner.class) 23 | @Config(application = TestApplication.class) 24 | public class GatewayBrowserPaymentActivityTest { 25 | 26 | @Test 27 | public void testGetDefaultTitle() { 28 | GatewayBrowserPaymentActivity activity = new GatewayBrowserPaymentActivity(); 29 | assertEquals("Payment", activity.getDefaultTitle()); 30 | } 31 | 32 | @Test 33 | public void testGatewayHost() { 34 | GatewayBrowserPaymentActivity activity = new GatewayBrowserPaymentActivity(); 35 | assertEquals("browserpayment", activity.gatewayHost()); 36 | } 37 | 38 | @Test 39 | public void testOnGatewayRedirectCallsCompleteWithOrderResult() { 40 | Uri testUri = Uri.parse("gatewaysdk://browserpayment?irrelevant=foo&orderResult=success123"); 41 | 42 | GatewayBrowserPaymentActivity activity = spy(new GatewayBrowserPaymentActivity()); 43 | 44 | // Prevent Android internals 45 | doNothing().when(activity).finish(); 46 | doNothing().when(activity).setResult(anyInt(), any(Intent.class)); 47 | 48 | activity.onGatewayRedirect(testUri); 49 | 50 | ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); 51 | verify(activity).setResult(eq(Activity.RESULT_OK), captor.capture()); 52 | verify(activity).finish(); 53 | 54 | Intent captured = captor.getValue(); 55 | assertNotNull(captured); 56 | assertEquals("success123", captured.getStringExtra(BaseGatewayPaymentActivity.EXTRA_GATEWAY_RESULT)); 57 | } 58 | 59 | @Test 60 | public void testOnGatewayRedirectHandlesMissingOrderResult() { 61 | Uri testUri = Uri.parse("gatewaysdk://browserpayment?foo=bar"); 62 | 63 | GatewayBrowserPaymentActivity activity = spy(new GatewayBrowserPaymentActivity()); 64 | 65 | doNothing().when(activity).finish(); 66 | doNothing().when(activity).setResult(anyInt(), any(Intent.class)); 67 | 68 | activity.onGatewayRedirect(testUri); 69 | 70 | ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); 71 | verify(activity).setResult(eq(Activity.RESULT_OK), captor.capture()); 72 | verify(activity).finish(); 73 | 74 | Intent captured = captor.getValue(); 75 | assertNotNull(captured); 76 | // if missing, getQueryParam returns null → complete(key, null) 77 | assertEquals(null, captured.getStringExtra(BaseGatewayPaymentActivity.EXTRA_GATEWAY_RESULT)); 78 | } 79 | } -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'org.jetbrains.kotlin.android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'dagger.hilt.android.plugin' 5 | 6 | android { 7 | compileSdk 35 8 | namespace = "com.mastercard.gateway.android.sampleapp" 9 | 10 | defaultConfig { 11 | applicationId 'com.mastercard.gateway.android.sampleapp' 12 | minSdkVersion 23 13 | targetSdk 35 14 | versionCode 1 15 | versionName '1.0.0' 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | 23 | multiDexEnabled true 24 | } 25 | 26 | buildFeatures { 27 | buildConfig true 28 | dataBinding true 29 | } 30 | 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | dataBinding { 39 | enabled = true 40 | } 41 | 42 | lintOptions { 43 | abortOnError false 44 | } 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_1_8 47 | targetCompatibility JavaVersion.VERSION_1_8 48 | } 49 | kotlinOptions { 50 | jvmTarget = '1.8' 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation fileTree(include: ['*.jar'], dir: 'libs') 56 | implementation 'com.google.code.gson:gson:2.13.2' 57 | implementation 'androidx.appcompat:appcompat:1.1.0' 58 | implementation 'com.google.android.material:material:1.1.0' 59 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 60 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 61 | implementation 'com.google.android.gms:play-services-wallet:19.5.0' 62 | implementation 'com.github.esnaultdev:MaterialValues:v1.1.1' 63 | 64 | implementation project(':gateway-android') 65 | 66 | // retrofit 67 | implementation 'com.squareup.retrofit2:retrofit:3.0.0' 68 | // gson converter 69 | implementation 'com.squareup.retrofit2:converter-gson:3.0.0' 70 | implementation("com.squareup.retrofit2:adapter-rxjava2:3.0.0") 71 | implementation 'com.squareup.okhttp3:logging-interceptor:5.3.0' 72 | 73 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" 74 | implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2' 75 | implementation "androidx.datastore:datastore-preferences:1.0.0" 76 | 77 | implementation "com.google.dagger:hilt-android:2.57.2" 78 | kapt "com.google.dagger:hilt-compiler:2.57.2" 79 | 80 | implementation "androidx.hilt:hilt-common:1.1.0" 81 | implementation "androidx.hilt:hilt-work:1.1.0" // Optional for WorkManager 82 | kapt "androidx.hilt:hilt-compiler:1.1.0" 83 | implementation "androidx.hilt:hilt-work:1.1.0" 84 | implementation "androidx.work:work-runtime:2.9.0" 85 | 86 | // LifeCycled 87 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7") 88 | implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.7") 89 | 90 | // Espresso testing 91 | androidTestImplementation 'junit:junit:4.13.2' 92 | androidTestImplementation('androidx.test.espresso:espresso-core:3.7.0') { 93 | exclude module: 'support-annotations' 94 | } 95 | androidTestImplementation('androidx.test:runner:1.1.0') { 96 | exclude module: 'support-annotations' 97 | } 98 | } -------------------------------------------------------------------------------- /sample/src/main/res/drawable/googlepay_button_content.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 49 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Gateway SDK Sample App 3 | 4 | None 5 | 6 | Configuration: 7 | Merchant Id 8 | Region 9 | Api 10 | Currency 11 | Amount 12 | Select region 13 | Heroku Test Server URL 14 | Merchant Server Enabled 15 | Process a Payment 16 | 17 | Name on card 18 | Card number 19 | Expiry month (MM) 20 | Expiry year (YY) 21 | CVV 22 | Continue 23 | - or - 24 | 25 | 26 | result 27 | Your payment was successful 28 | Card info not collected 29 | Error processing your payment 30 | 3DS authentication failed 31 | 3DS authentication unavailable 32 | 3DS authentication rejected 33 | 3DS authentication pending 34 | Browser payment authentication declined 35 | Browser payment authentication pending 36 | Browser payment authentication canceled 37 | Unable to update session 38 | Unable to create session 39 | Unable to complete the payment 40 | Steps 41 | Status 42 | 1. Create Session 43 | 44 | 45 | 2. Payment Option Inquiry 46 | 3. Select Payment Option 47 | 4. Collect Card Info 48 | 49 | 5. Update Session with Payer Data 50 | 6. Initiate Authentication 51 | 7. Authenticate 3D Secure Payment 52 | Process Payment 53 | 8. Process Payment 54 | 55 | 4. Initiate Browser Payment 56 | 5. Authenticate Browser Payment 57 | 58 | Process Payment for $1 59 | Payment Option 60 | Select an option to process the payment 61 | Confirm Payment Details 62 | $1.00 63 | Confirm and Pay 64 | Done 65 | Browser Payment Authentication Cancelled 66 | 67 | 68 | -------------------------------------------------------------------------------- /sample/src/main/java/com/mastercard/gateway/android/sampleapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mastercard.gateway.android.sampleapp; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | 8 | import androidx.appcompat.app.AlertDialog; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import androidx.databinding.DataBindingUtil; 11 | import androidx.lifecycle.ViewModelProvider; 12 | 13 | import com.mastercard.gateway.android.sampleapp.databinding.ActivityMainBinding; 14 | import com.mastercard.gateway.android.sampleapp.utils.RegionInfo; 15 | import com.mastercard.gateway.android.sampleapp.utils.SimpleTextChangedWatcher; 16 | import com.mastercard.gateway.android.sampleapp.viewmodel.MainViewModel; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Objects; 21 | 22 | import dagger.hilt.android.AndroidEntryPoint; 23 | 24 | @AndroidEntryPoint 25 | public class MainActivity extends AppCompatActivity { 26 | 27 | ActivityMainBinding binding; 28 | private MainViewModel viewModel; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | 34 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 35 | viewModel = new ViewModelProvider(this).get(MainViewModel.class); 36 | 37 | // Watcher that triggers enableButtons + saveSessionData 38 | SimpleTextChangedWatcher watcher = new SimpleTextChangedWatcher(() -> { 39 | enableButtons(); 40 | viewModel.saveSessionData( 41 | Objects.requireNonNull(binding.merchantId.getText()).toString(), 42 | Objects.requireNonNull(binding.region.getText()).toString(), 43 | Objects.requireNonNull(binding.merchantServerLink.getText()).toString() 44 | ); 45 | }); 46 | 47 | binding.merchantId.setText(viewModel.getMerchantId()); 48 | binding.merchantId.addTextChangedListener(watcher); 49 | 50 | binding.region.setText(viewModel.getRegion()); 51 | binding.region.addTextChangedListener(watcher); 52 | binding.region.setOnFocusChangeListener((v, hasFocus) -> { 53 | if (hasFocus) { 54 | binding.region.clearFocus(); 55 | showRegionPicker(); 56 | } 57 | }); 58 | 59 | binding.merchantServerLink.setText(viewModel.getMerchantServerLink()); 60 | binding.merchantServerLink.addTextChangedListener(watcher); 61 | 62 | binding.processPaymentButton.setOnClickListener(v -> 63 | goTo(ProcessPaymentActivity.class) 64 | ); 65 | 66 | enableButtons(); 67 | } 68 | 69 | void goTo(Class klass) { 70 | Intent i = new Intent(this, klass); 71 | startActivity(i); 72 | } 73 | 74 | void enableButtons() { 75 | boolean enabled = !TextUtils.isEmpty(binding.merchantId.getText()) 76 | && !TextUtils.isEmpty(binding.region.getText()); 77 | 78 | binding.processPaymentButton.setEnabled(enabled); 79 | } 80 | 81 | void showRegionPicker() { 82 | List regionsWithExtra = new ArrayList<>(); 83 | regionsWithExtra.add(new RegionInfo(getString(R.string.none), "")); 84 | regionsWithExtra.addAll(viewModel.getRegions()); 85 | 86 | String[] items; 87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 88 | items = regionsWithExtra.stream() 89 | .map(RegionInfo::getName) 90 | .toArray(String[]::new); 91 | } else { 92 | items = null; 93 | } 94 | 95 | new AlertDialog.Builder(this) 96 | .setTitle(R.string.main_select_region) 97 | .setItems(items, (dialog, which) -> { 98 | if (which == 0) { 99 | binding.region.setText(""); 100 | } else { 101 | assert items != null; 102 | binding.region.setText(items[which]); 103 | } 104 | dialog.cancel(); 105 | }) 106 | .show(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gateway Android SDK 2 | **This Mobile SDK supports 3-D Secure and Browser Payment** If you require EMV 3DS support, please obtain the version 2 Mobile SDK by following these instructions: https://na.gateway.mastercard.com/api/documentation/integrationGuidelines/mobileSDK/emv3DSsdk.html 3 | 4 | [![Download](https://api.bintray.com/packages/mpgs/Android/gateway-android-sdk/images/download.svg)](https://bintray.com/mpgs/Android/gateway-android-sdk/_latestVersion) 5 | [![Build Status](https://travis-ci.org/Mastercard-Gateway/gateway-android-sdk.svg?branch=master)](https://travis-ci.org/Mastercard-Gateway/gateway-android-sdk) 6 | 7 | Our Android SDK allows you to easily integrate payments into your Android app. By updating a session directly with the Gateway, you avoid the risk of handling sensitive card details on your server. This sample app demonstrates the basics of installing and configuring the SDK to complete a simple payment. 8 | 9 | For more information, visit the [**Gateway Android SDK Wiki**](https://github.com/Mastercard-Gateway/gateway-android-sdk/wiki) to find details about the basic transaction lifecycle and 3-D Secure support. 10 | 11 | 12 | ## Scope 13 | 14 | The primary responsibility of this SDK is to eliminate the need for card details to pass thru your merchant service while collecting card information from a mobile device. The Gateway provides this ability by exposing an API call to update a session with card information. This is an "unathenticated" call in the sense that you are not required to provide your private API credentials. It is important to retain your private API password in a secure location and NOT distribute it within your mobile app. 15 | 16 | Once you have updated a session with card information from the app, you may then perform a variety of operations using this session from your secure server. Some of these operations include creating an authorization or payment, creating a card token to save card information for a customer, etc. Refer to your gateway integration guide for more details on how a Session can be used in your application. 17 | 18 | 19 | ## Installation 20 | 21 | This library is hosted in the maven central. To import the Android SDK, include it as a dependency in your build.gradle file. Be sure to replace `X.X.X` with the version number in the shield above. (Minimum supported Android SDK version 23) 22 | 23 | ```groovy 24 | implementation 'com.mastercard.gateway:gateway-android:X.X.X' 25 | ``` 26 | 27 | [**Release Notes**](https://github.com/Mastercard-Gateway/gateway-android-sdk/wiki/Release-Notes) 28 | 29 | 30 | ## Configuration 31 | 32 | In order to use the SDK, you must initialize the Gateway object with your merchant ID and your gateway's region. If you are unsure about which region to select, please direct your inquiry to your gateway support team. 33 | 34 | ```java 35 | Gateway gateway = new Gateway(); 36 | gateway.setMerchantId("YOUR_MERCHANT_ID"); 37 | gateway.setRegion(Gateway.Region.YOUR_REGION); 38 | ``` 39 | 40 | 41 | ## Basic Implementation 42 | 43 | Using an existing Session Id, you may pass card information directly to the `Gateway` object: 44 | 45 | ```java 46 | // The GatewayMap object provides support for building a nested map structure using key-based dot(.) notation. 47 | // Each parameter is similarly defined in your online integration guide. 48 | GatewayMap request = new GatewayMap() 49 | .set("sourceOfFunds.provided.card.nameOnCard", nameOnCard) 50 | .set("sourceOfFunds.provided.card.number", cardNumber) 51 | .set("sourceOfFunds.provided.card.securityCode", cardCvv) 52 | .set("sourceOfFunds.provided.card.expiry.month", cardExpiryMM) 53 | .set("sourceOfFunds.provided.card.expiry.year", cardExpiryYY); 54 | 55 | gateway.updateSession(sessionId, apiVersion, request, callback); 56 | ``` 57 | 58 | 59 | ## Rx-Enabled 60 | 61 | You may optionally include the **[RxJava2]** library in your project and utilize the appropriate methods provided in the `Gateway` class. 62 | 63 | ```java 64 | Single single = gateway.updateSession(sessionId, apiVersion, request); 65 | ``` 66 | 67 | 68 | --- 69 | 70 | # Sample App 71 | 72 | Included in this project is a sample app that demonstrates how to take a payment using the SDK. This sample app requires a running instance of our **[Gateway Test Merchant Server]**. Follow the instructions for that project and copy the resulting URL of the instance you create. 73 | 74 | 75 | ## Configuration 76 | 77 | To configure the sample app, compile and run the app on your device. There are three fields which must be completed in order for the sample app to operate correctly: 78 | 79 | ![Sample app configuration](./sample-configuration.png) 80 | 81 | 1. The merchant id should have the prefix 'TEST' 82 | 1. The region options include ASIA_PACIFIC, EUROPE, NORTH_AMERICA, INDIA, CHINA, or MTF 83 | 1. To find the Heroku test server URL, consult the **[Gateway Test Merchant Server]** (ex: https://{your-app-name}.herokuapp.com) 84 | 85 | 86 | 87 | [RxJava2]: https://github.com/ReactiveX/RxJava 88 | [Gateway Test Merchant Server]: https://github.com/Mastercard-Gateway/gateway-test-merchant-server 89 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 19 | 20 | 25 | 26 | 27 | 28 | 34 | 35 | 41 | 42 | 53 | 54 | 64 | 65 | 70 | 71 | 72 | 73 | 82 | 83 | 88 | 89 | 90 | 91 | 99 | 100 | 107 | 108 | 109 | 110 |