From 883f3f52882bbf8046f3ed39738fb395e56afcb6 Mon Sep 17 00:00:00 2001 From: Chen Cen <79880926+ccen-stripe@users.noreply.github.com> Date: Thu, 29 Apr 2021 12:29:09 -0700 Subject: [PATCH] support wechatpay (#3653) * support wechatpay * resolve comments * rename * move WeChatPayNextAction * lint and dump --- stripe/api/stripe.api | 65 ++++++++++++++++- .../com/stripe/android/PaymentController.kt | 27 +++++++ .../main/java/com/stripe/android/Stripe.kt | 37 ++++++++++ .../java/com/stripe/android/StripeApiBeta.kt | 2 +- .../main/java/com/stripe/android/StripeKtx.kt | 34 +++++++++ .../stripe/android/StripePaymentController.kt | 19 +++++ .../com/stripe/android/model/PaymentMethod.kt | 3 +- .../model/PaymentMethodCreateParams.kt | 16 ++++- .../model/PaymentMethodOptionsParams.kt | 17 +++++ .../com/stripe/android/model/StripeIntent.kt | 6 +- .../java/com/stripe/android/model/WeChat.kt | 2 +- .../android/model/WeChatPayNextAction.kt | 9 +++ .../model/parsers/NextActionDataParser.kt | 33 +++++++++ .../java/com/stripe/android/ApiKeyFixtures.kt | 1 + .../java/com/stripe/android/ApiVersionTest.kt | 4 +- .../android/PaymentMethodEndToEndTest.kt | 14 ++++ .../java/com/stripe/android/StripeKtxTest.kt | 50 +++++++++++++ .../model/ConfirmPaymentIntentParamsTest.kt | 26 +++++++ .../android/model/PaymentIntentFixtures.kt | 70 +++++++++++++++++++ .../stripe/android/model/PaymentIntentTest.kt | 29 ++++++++ 20 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 stripe/src/main/java/com/stripe/android/model/WeChatPayNextAction.kt diff --git a/stripe/api/stripe.api b/stripe/api/stripe.api index 626e61a7a14..5609e8864fe 100644 --- a/stripe/api/stripe.api +++ b/stripe/api/stripe.api @@ -736,6 +736,9 @@ public final class com/stripe/android/Stripe { public final fun confirmSetupIntentSynchronous (Lcom/stripe/android/model/ConfirmSetupIntentParams;)Lcom/stripe/android/model/SetupIntent; public final fun confirmSetupIntentSynchronous (Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;)Lcom/stripe/android/model/SetupIntent; public static synthetic fun confirmSetupIntentSynchronous$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/SetupIntent; + public final fun confirmWeChatPayPayment (Lcom/stripe/android/model/ConfirmPaymentIntentParams;Lcom/stripe/android/ApiResultCallback;)V + public final fun confirmWeChatPayPayment (Lcom/stripe/android/model/ConfirmPaymentIntentParams;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V + public static synthetic fun confirmWeChatPayPayment$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmPaymentIntentParams;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;ILjava/lang/Object;)V public final fun createAccountToken (Lcom/stripe/android/model/AccountParams;Lcom/stripe/android/ApiResultCallback;)V public final fun createAccountToken (Lcom/stripe/android/model/AccountParams;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V public final fun createAccountToken (Lcom/stripe/android/model/AccountParams;Ljava/lang/String;Ljava/lang/String;Lcom/stripe/android/ApiResultCallback;)V @@ -872,7 +875,7 @@ public final class com/stripe/android/Stripe$Companion { } public final class com/stripe/android/StripeApiBeta : java/lang/Enum { - public static final field WechatPayV1 Lcom/stripe/android/StripeApiBeta; + public static final field WeChatPayV1 Lcom/stripe/android/StripeApiBeta; public final fun getCode ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lcom/stripe/android/StripeApiBeta; public static fun values ()[Lcom/stripe/android/StripeApiBeta; @@ -941,6 +944,8 @@ public final class com/stripe/android/StripeKtxKt { public static synthetic fun confirmPaymentIntent$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmPaymentIntentParams;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final fun confirmSetupIntent (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun confirmSetupIntent$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmSetupIntentParams;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun confirmWeChatPayPayment (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmPaymentIntentParams;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun confirmWeChatPayPayment$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/ConfirmPaymentIntentParams;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final fun createAccountToken (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/AccountParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createAccountToken$default (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/AccountParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final fun createBankAccountToken (Lcom/stripe/android/Stripe;Lcom/stripe/android/model/BankAccountTokenParams;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -3080,6 +3085,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum, public static final field SepaDebit Lcom/stripe/android/model/PaymentMethod$Type; public static final field Sofort Lcom/stripe/android/model/PaymentMethod$Type; public static final field Upi Lcom/stripe/android/model/PaymentMethod$Type; + public static final field WeChatPay Lcom/stripe/android/model/PaymentMethod$Type; public final field code Ljava/lang/String; public final field isReusable Z public fun describeContents ()I @@ -3184,6 +3190,9 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/ public static final fun createP24 (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createPayPal ()Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createPayPal (Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createWeChatPay ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createWeChatPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createWeChatPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public fun describeContents ()I public fun equals (Ljava/lang/Object;)Z public final fun getTypeCode ()Ljava/lang/String; @@ -3353,6 +3362,10 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Companion public final fun createPayPal ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createPayPal (Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static synthetic fun createPayPal$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createWeChatPay ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createWeChatPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createWeChatPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static synthetic fun createWeChatPay$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; } public class com/stripe/android/model/PaymentMethodCreateParams$Creator : android/os/Parcelable$Creator { @@ -3578,6 +3591,32 @@ public class com/stripe/android/model/PaymentMethodOptionsParams$Card$Creator : public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/model/PaymentMethodOptionsParams$WeChatPay : com/stripe/android/model/PaymentMethodOptionsParams { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public static final field Companion Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay$Companion; + public static final field PARAM_APP_ID Ljava/lang/String; + public static final field PARAM_CLIENT Ljava/lang/String; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay; + public static synthetic fun copy$default (Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getAppId ()Ljava/lang/String; + public fun hashCode ()I + public final fun setAppId (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public class com/stripe/android/model/PaymentMethodOptionsParams$WeChatPay$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/PaymentMethodOptionsParams$WeChatPay; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/model/PersonTokenParams : com/stripe/android/model/TokenParams { public static final field CREATOR Landroid/os/Parcelable$Creator; public fun ()V @@ -4929,6 +4968,7 @@ public final class com/stripe/android/model/StripeIntent$NextActionType : java/l public static final field DisplayOxxoDetails Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field RedirectToUrl Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field UseStripeSdk Lcom/stripe/android/model/StripeIntent$NextActionType; + public static final field WeChatPayRedirect Lcom/stripe/android/model/StripeIntent$NextActionType; public final fun getCode ()Ljava/lang/String; public fun toString ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lcom/stripe/android/model/StripeIntent$NextActionType; @@ -5079,6 +5119,29 @@ public class com/stripe/android/model/WeChat$Creator : android/os/Parcelable$Cre public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/model/WeChatPayNextAction : com/stripe/android/model/StripeModel { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public final fun component1 ()Lcom/stripe/android/model/PaymentIntent; + public final fun component2 ()Lcom/stripe/android/model/WeChat; + public final fun copy (Lcom/stripe/android/model/PaymentIntent;Lcom/stripe/android/model/WeChat;)Lcom/stripe/android/model/WeChatPayNextAction; + public static synthetic fun copy$default (Lcom/stripe/android/model/WeChatPayNextAction;Lcom/stripe/android/model/PaymentIntent;Lcom/stripe/android/model/WeChat;ILjava/lang/Object;)Lcom/stripe/android/model/WeChatPayNextAction; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getPaymentIntent ()Lcom/stripe/android/model/PaymentIntent; + public final fun getWeChat ()Lcom/stripe/android/model/WeChat; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public class com/stripe/android/model/WeChatPayNextAction$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/WeChatPayNextAction; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/WeChatPayNextAction; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public abstract class com/stripe/android/model/wallets/Wallet : com/stripe/android/model/StripeModel { public synthetic fun (Lcom/stripe/android/model/wallets/Wallet$Type;Lkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/stripe/src/main/java/com/stripe/android/PaymentController.kt b/stripe/src/main/java/com/stripe/android/PaymentController.kt index 839cec3f5d4..10ec3e6f976 100644 --- a/stripe/src/main/java/com/stripe/android/PaymentController.kt +++ b/stripe/src/main/java/com/stripe/android/PaymentController.kt @@ -10,6 +10,8 @@ import com.stripe.android.model.ConfirmStripeIntentParams import com.stripe.android.model.PaymentIntent import com.stripe.android.model.Source import com.stripe.android.model.StripeIntent +import com.stripe.android.model.WeChat +import com.stripe.android.model.WeChatPayNextAction import com.stripe.android.networking.ApiRequest import com.stripe.android.view.AuthActivityStarter @@ -52,6 +54,31 @@ internal interface PaymentController { requestOptions: ApiRequest.Options ): PaymentIntentResult + /** + * Confirm the Stripe Intent for WeChat Pay, return WeChat Pay params from intent's next action + * + * @param confirmPaymentIntentParams params to confirm the intent + * @param requestOptions options for [ApiRequest] + * @return the [WeChatPayNextAction] object encapsulating [PaymentIntent] and [WeChat] + * + * @throws AuthenticationException failure to properly authenticate yourself (check your key) + * @throws InvalidRequestException your request has invalid parameters + * @throws APIConnectionException failure to connect to Stripe's API + * @throws APIException any other type of problem (for instance, a temporary issue with Stripe's servers) + * @throws IllegalArgumentException if the payment intent's next action data is not for WeChat Pay + */ + @Throws( + AuthenticationException::class, + InvalidRequestException::class, + APIConnectionException::class, + APIException::class, + IllegalArgumentException::class + ) + suspend fun confirmWeChatPay( + confirmPaymentIntentParams: ConfirmPaymentIntentParams, + requestOptions: ApiRequest.Options + ): WeChatPayNextAction + suspend fun startAuth( host: AuthActivityStarter.Host, clientSecret: String, diff --git a/stripe/src/main/java/com/stripe/android/Stripe.kt b/stripe/src/main/java/com/stripe/android/Stripe.kt index 580ee63bc2c..19eda209d1e 100644 --- a/stripe/src/main/java/com/stripe/android/Stripe.kt +++ b/stripe/src/main/java/com/stripe/android/Stripe.kt @@ -37,6 +37,7 @@ import com.stripe.android.model.StripeFileParams import com.stripe.android.model.StripeModel import com.stripe.android.model.Token import com.stripe.android.model.TokenParams +import com.stripe.android.model.WeChatPayNextAction import com.stripe.android.networking.ApiRequest import com.stripe.android.networking.StripeApiRepository import com.stripe.android.networking.StripeRepository @@ -258,6 +259,42 @@ class Stripe internal constructor( } } + /** + * Confirm a [PaymentIntent] for WeChat Pay. Extract params from [WeChatPayNextAction] to pass to WeChat Pay SDK. + * @see WeChat Pay Documentation + * + * WeChat Pay API is still in beta, create a [Stripe] instance with [StripeApiBeta.WeChatPayV1] to enable this API. + * + * @param confirmPaymentIntentParams [ConfirmPaymentIntentParams] used to confirm the + * [PaymentIntent] + * @param stripeAccountId Optional, the Connect account to associate with this request. + * By default, will use the Connect account that was used to instantiate the [Stripe] object, if specified. + * @param callback a [ApiResultCallback] to receive the result or error + * + * Possible callback errors: + * [AuthenticationException] failure to properly authenticate yourself (check your key) + * [InvalidRequestException] your request has invalid parameters + * [APIConnectionException] failure to connect to Stripe's API + * [APIException] any other type of problem (for instance, a temporary issue with Stripe's servers) + * [InvalidRequestException] if the payment intent's next action data is not for WeChat Pay + */ + @JvmOverloads + fun confirmWeChatPayPayment( + confirmPaymentIntentParams: ConfirmPaymentIntentParams, + stripeAccountId: String? = this.stripeAccountId, + callback: ApiResultCallback + ) { + executeAsync(callback) { + paymentController.confirmWeChatPay( + confirmPaymentIntentParams, + ApiRequest.Options( + apiKey = publishableKey, + stripeAccount = stripeAccountId + ) + ) + } + } + /** * Confirm and, if necessary, authenticate a [PaymentIntent]. * Used for [automatic confirmation](https://stripe.com/docs/payments/payment-intents/quickstart#automatic-confirmation-flow) flow. diff --git a/stripe/src/main/java/com/stripe/android/StripeApiBeta.kt b/stripe/src/main/java/com/stripe/android/StripeApiBeta.kt index 19b480f28c7..76a2269c8dc 100644 --- a/stripe/src/main/java/com/stripe/android/StripeApiBeta.kt +++ b/stripe/src/main/java/com/stripe/android/StripeApiBeta.kt @@ -4,5 +4,5 @@ package com.stripe.android * Enums of beta headers allowed to be override when initializing [Stripe]. */ enum class StripeApiBeta(val code: String) { - WechatPayV1("wechat_pay_beta=v1"); + WeChatPayV1("wechat_pay_beta=v1"); } diff --git a/stripe/src/main/java/com/stripe/android/StripeKtx.kt b/stripe/src/main/java/com/stripe/android/StripeKtx.kt index b6c1f768c44..bc69875f084 100644 --- a/stripe/src/main/java/com/stripe/android/StripeKtx.kt +++ b/stripe/src/main/java/com/stripe/android/StripeKtx.kt @@ -26,6 +26,7 @@ import com.stripe.android.model.StripeFile import com.stripe.android.model.StripeFileParams import com.stripe.android.model.StripeModel import com.stripe.android.model.Token +import com.stripe.android.model.WeChatPayNextAction import com.stripe.android.networking.ApiRequest /** @@ -568,6 +569,39 @@ suspend fun Stripe.confirmSetupIntent( ) } +/** + * Suspend function to confirm a [PaymentIntent] for WeChat Pay. Extract params from [WeChatPayNextAction] to pass to WeChat Pay SDK. + * @see WeChat Pay Documentation + * + * WeChat Pay API is still in beta, create a [Stripe] instance with [StripeApiBeta.WeChatPayV1] to enable this API. + * + * @param confirmPaymentIntentParams [ConfirmPaymentIntentParams] used to confirm the + * [PaymentIntent] + * @param stripeAccountId Optional, the Connect account to associate with this request. + * By default, will use the Connect account that was used to instantiate the [Stripe] object, if specified. + * + * @throws AuthenticationException failure to properly authenticate yourself (check your key) + * @throws InvalidRequestException your request has invalid parameters + * @throws APIConnectionException failure to connect to Stripe's API + * @throws APIException any other type of problem (for instance, a temporary issue with Stripe's servers) + */ +suspend fun Stripe.confirmWeChatPayPayment( + confirmPaymentIntentParams: ConfirmPaymentIntentParams, + stripeAccountId: String? = this.stripeAccountId, +): WeChatPayNextAction { + return runCatching { + paymentController.confirmWeChatPay( + confirmPaymentIntentParams, + ApiRequest.Options( + apiKey = publishableKey, + stripeAccount = stripeAccountId + ) + ) + }.getOrElse { + throw StripeException.create(it) + } +} + /** * Suspend function to confirm a [PaymentIntent] object. * diff --git a/stripe/src/main/java/com/stripe/android/StripePaymentController.kt b/stripe/src/main/java/com/stripe/android/StripePaymentController.kt index b559ecd4ddd..2099207fe14 100644 --- a/stripe/src/main/java/com/stripe/android/StripePaymentController.kt +++ b/stripe/src/main/java/com/stripe/android/StripePaymentController.kt @@ -20,6 +20,7 @@ import com.stripe.android.model.Stripe3ds2AuthParams import com.stripe.android.model.Stripe3ds2AuthResult import com.stripe.android.model.Stripe3ds2Fingerprint import com.stripe.android.model.StripeIntent +import com.stripe.android.model.WeChatPayNextAction import com.stripe.android.networking.AlipayRepository import com.stripe.android.networking.AnalyticsRequestExecutor import com.stripe.android.networking.AnalyticsRequestFactory @@ -193,6 +194,24 @@ internal class StripePaymentController internal constructor( ) } + override suspend fun confirmWeChatPay( + confirmPaymentIntentParams: ConfirmPaymentIntentParams, + requestOptions: ApiRequest.Options + ): WeChatPayNextAction { + confirmPaymentIntent( + confirmPaymentIntentParams, + requestOptions + ).let { paymentIntent -> + require(paymentIntent.nextActionData is StripeIntent.NextActionData.WeChatPayRedirect) { + "Unable to confirm Payment Intent with WeChatPay SDK" + } + return WeChatPayNextAction( + paymentIntent, + paymentIntent.nextActionData.weChat, + ) + } + } + private suspend fun confirmPaymentIntent( confirmStripeIntentParams: ConfirmPaymentIntentParams, requestOptions: ApiRequest.Options diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentMethod.kt b/stripe/src/main/java/com/stripe/android/model/PaymentMethod.kt index fbed9ec8a49..4598b9f16ef 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentMethod.kt +++ b/stripe/src/main/java/com/stripe/android/model/PaymentMethod.kt @@ -144,7 +144,8 @@ data class PaymentMethod internal constructor( PayPal("paypal", isReusable = false), AfterpayClearpay("afterpay_clearpay", isReusable = false), Netbanking("netbanking", isReusable = false), - Blik("blik", isReusable = false); + Blik("blik", isReusable = false), + WeChatPay("wechat_pay", isReusable = false); override fun toString(): String { return code diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt b/stripe/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt index 37ad3a1ef6f..ae3fd005ae7 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt +++ b/stripe/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt @@ -203,7 +203,8 @@ data class PaymentMethodCreateParams internal constructor( AfterpayClearpay("afterpay_clearpay"), Upi("upi"), Netbanking("netbanking"), - Blik("blik") + Blik("blik"), + WeChatPay("wechat_pay") } @Parcelize @@ -769,5 +770,18 @@ data class PaymentMethodCreateParams internal constructor( metadata = metadata ) } + + @JvmStatic + @JvmOverloads + fun createWeChatPay( + billingDetails: PaymentMethod.BillingDetails? = null, + metadata: Map? = null + ): PaymentMethodCreateParams { + return PaymentMethodCreateParams( + type = Type.WeChatPay, + billingDetails = billingDetails, + metadata = metadata + ) + } } } diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentMethodOptionsParams.kt b/stripe/src/main/java/com/stripe/android/model/PaymentMethodOptionsParams.kt index 8fab450f575..96a5cfc3a49 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentMethodOptionsParams.kt +++ b/stripe/src/main/java/com/stripe/android/model/PaymentMethodOptionsParams.kt @@ -54,4 +54,21 @@ sealed class PaymentMethodOptionsParams( const val PARAM_CODE = "code" } } + + @Parcelize + data class WeChatPay( + var appId: String, + ) : PaymentMethodOptionsParams(PaymentMethod.Type.WeChatPay) { + override fun createTypeParams(): List> { + return listOf( + PARAM_CLIENT to "android", + PARAM_APP_ID to appId + ) + } + + internal companion object { + const val PARAM_CLIENT = "client" + const val PARAM_APP_ID = "app_id" + } + } } diff --git a/stripe/src/main/java/com/stripe/android/model/StripeIntent.kt b/stripe/src/main/java/com/stripe/android/model/StripeIntent.kt index 1af28d46e63..4499a2cf637 100644 --- a/stripe/src/main/java/com/stripe/android/model/StripeIntent.kt +++ b/stripe/src/main/java/com/stripe/android/model/StripeIntent.kt @@ -67,7 +67,8 @@ interface StripeIntent : StripeModel { UseStripeSdk("use_stripe_sdk"), DisplayOxxoDetails("oxxo_display_details"), AlipayRedirect("alipay_handle_redirect"), - BlikAuthorize("blik_authorize"); + BlikAuthorize("blik_authorize"), + WeChatPayRedirect("wechat_pay_redirect_to_android_app"); override fun toString(): String { return code @@ -237,5 +238,8 @@ interface StripeIntent : StripeModel { return this === other } } + + @Parcelize + internal data class WeChatPayRedirect(val weChat: WeChat) : NextActionData() } } diff --git a/stripe/src/main/java/com/stripe/android/model/WeChat.kt b/stripe/src/main/java/com/stripe/android/model/WeChat.kt index 623a58b0038..0575ebba860 100644 --- a/stripe/src/main/java/com/stripe/android/model/WeChat.kt +++ b/stripe/src/main/java/com/stripe/android/model/WeChat.kt @@ -7,7 +7,7 @@ import kotlinx.parcelize.Parcelize */ @Parcelize data class WeChat internal constructor( - val statementDescriptor: String?, + val statementDescriptor: String? = null, val appId: String?, val nonce: String?, val packageValue: String?, diff --git a/stripe/src/main/java/com/stripe/android/model/WeChatPayNextAction.kt b/stripe/src/main/java/com/stripe/android/model/WeChatPayNextAction.kt new file mode 100644 index 00000000000..9d4381e0e92 --- /dev/null +++ b/stripe/src/main/java/com/stripe/android/model/WeChatPayNextAction.kt @@ -0,0 +1,9 @@ +package com.stripe.android.model + +import kotlinx.parcelize.Parcelize + +@Parcelize +data class WeChatPayNextAction internal constructor( + val paymentIntent: PaymentIntent, + val weChat: WeChat, +) : StripeModel diff --git a/stripe/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt b/stripe/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt index 5413a66d524..9afa08c57ae 100644 --- a/stripe/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt +++ b/stripe/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.stripe.android.model.StripeIntent import com.stripe.android.model.StripeJsonUtils import com.stripe.android.model.StripeJsonUtils.optString +import com.stripe.android.model.WeChat import org.json.JSONObject internal class NextActionDataParser : ModelJsonParser { @@ -19,6 +20,7 @@ internal class NextActionDataParser : ModelJsonParser SdkDataJsonParser() StripeIntent.NextActionType.AlipayRedirect -> AlipayRedirectParser() StripeIntent.NextActionType.BlikAuthorize -> BlikAuthorizeParser() + StripeIntent.NextActionType.WeChatPayRedirect -> WeChatPayRedirectParser() else -> return null } return parser.parse(json.optJSONObject(nextActionType.code) ?: JSONObject()) @@ -150,6 +152,37 @@ internal class NextActionDataParser : ModelJsonParser { + override fun parse(json: JSONObject): StripeIntent.NextActionData.WeChatPayRedirect { + return StripeIntent.NextActionData.WeChatPayRedirect( + WeChat( + appId = json.optString(APP_ID), + nonce = json.optString(NONCE_STR), + packageValue = json.optString( + PACKAGE + ), + partnerId = json.optString(PARTNER_ID), + prepayId = json.optString( + PREPAY_ID + ), + timestamp = json.optString(TIMESTAMP), + sign = json.optString(SIGN) + ) + ) + } + + private companion object { + private const val APP_ID = "app_id" + private const val NONCE_STR = "nonce_str" + private const val PACKAGE = "package" + private const val PARTNER_ID = "partner_id" + private const val PREPAY_ID = "prepay_id" + private const val TIMESTAMP = "timestamp" + private const val SIGN = "sign" + } + } + private companion object { private const val FIELD_NEXT_ACTION_TYPE = "type" } diff --git a/stripe/src/test/java/com/stripe/android/ApiKeyFixtures.kt b/stripe/src/test/java/com/stripe/android/ApiKeyFixtures.kt index b8d07bed0da..601b1e90409 100644 --- a/stripe/src/test/java/com/stripe/android/ApiKeyFixtures.kt +++ b/stripe/src/test/java/com/stripe/android/ApiKeyFixtures.kt @@ -24,4 +24,5 @@ internal object ApiKeyFixtures { const val UPI_PUBLISHABLE_KEY = "pk_test_51H7wmsBte6TMTRd4gph9Wm7gnQOKJwdVTCj30AhtB8MhWtlYj6v9xDn1vdCtKYGAE7cybr6fQdbQQtgvzBihE9cl00tOnrTpL9" const val NETBANKING_PUBLISHABLE_KEY = "pk_test_51H7wmsBte6TMTRd4gph9Wm7gnQOKJwdVTCj30AhtB8MhWtlYj6v9xDn1vdCtKYGAE7cybr6fQdbQQtgvzBihE9cl00tOnrTpL9" const val BLIK_PUBLISHABLE_KEY = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6" + const val WECHAT_PAY_PUBLISHABLE_KEY = "pk_test_h0JFD5q63mLThM5JVSbrREmR" } diff --git a/stripe/src/test/java/com/stripe/android/ApiVersionTest.kt b/stripe/src/test/java/com/stripe/android/ApiVersionTest.kt index 35377f9e198..5dde1c703c7 100644 --- a/stripe/src/test/java/com/stripe/android/ApiVersionTest.kt +++ b/stripe/src/test/java/com/stripe/android/ApiVersionTest.kt @@ -12,7 +12,7 @@ class ApiVersionTest { @Test fun `single beta header should have correct code`() { - assertThat(ApiVersion(setOf(StripeApiBeta.WechatPayV1)).code) - .isEqualTo("$API_VERSION_CODE;${StripeApiBeta.WechatPayV1.code}") + assertThat(ApiVersion(setOf(StripeApiBeta.WeChatPayV1)).code) + .isEqualTo("$API_VERSION_CODE;${StripeApiBeta.WeChatPayV1.code}") } } diff --git a/stripe/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt b/stripe/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt index 264bf206c2b..32792f6c683 100644 --- a/stripe/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt +++ b/stripe/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt @@ -324,4 +324,18 @@ internal class PaymentMethodEndToEndTest { assertThat(paymentMethod?.type) .isEqualTo(PaymentMethod.Type.Blik) } + + @Test + fun createPaymentMethod_withWeChatPay_shouldCreateObject() { + val params = PaymentMethodCreateParams.createWeChatPay() + val paymentMethod = + Stripe( + context, + ApiKeyFixtures.WECHAT_PAY_PUBLISHABLE_KEY, + betas = setOf(StripeApiBeta.WeChatPayV1) + ) + .createPaymentMethodSynchronous(params) + assertThat(paymentMethod?.type) + .isEqualTo(PaymentMethod.Type.WeChatPay) + } } diff --git a/stripe/src/test/java/com/stripe/android/StripeKtxTest.kt b/stripe/src/test/java/com/stripe/android/StripeKtxTest.kt index 8f2a109a87d..bc3658a8860 100644 --- a/stripe/src/test/java/com/stripe/android/StripeKtxTest.kt +++ b/stripe/src/test/java/com/stripe/android/StripeKtxTest.kt @@ -13,6 +13,7 @@ import com.stripe.android.model.Source import com.stripe.android.model.StripeFile import com.stripe.android.model.StripeModel import com.stripe.android.model.StripeParamsModel +import com.stripe.android.model.WeChatPayNextAction import com.stripe.android.networking.ApiRequest import com.stripe.android.networking.StripeApiRepository import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -516,6 +517,55 @@ internal class StripeKtxTest { stripe::getAuthenticateSourceResult ) + @Test + fun `When controller returns correct value then confirmWeChatPayPayment should succeed`(): Unit = + testDispatcher.runBlockingTest { + val expectedApiObj = mock() + + whenever( + mockPaymentController.confirmWeChatPay(any(), any()) + ).thenReturn(expectedApiObj) + + val actualObj = stripe.confirmWeChatPayPayment( + mock(), + TEST_STRIPE_ACCOUNT_ID + ) + + assertSame(expectedApiObj, actualObj) + } + + @Test + fun `When controller throws exception then confirmWeChatPayPayment should throw same exception`(): Unit = + testDispatcher.runBlockingTest { + whenever( + mockPaymentController.confirmWeChatPay(any(), any()) + ).thenThrow(mock()) + + assertFailsWith { + stripe.confirmWeChatPayPayment( + mock(), + TEST_STRIPE_ACCOUNT_ID + ) + } + } + + @Test + fun `When nextAction is not for WeChatPay then should throw InvalidRequestException`(): Unit = + // when nextAction is not for WeChatPay, mockPaymentController fails in `require` and + // throws an IllegalArgumentException + testDispatcher.runBlockingTest { + whenever( + mockPaymentController.confirmWeChatPay(any(), any()) + ).thenThrow(mock()) + + assertFailsWith { + stripe.confirmWeChatPayPayment( + mock(), + TEST_STRIPE_ACCOUNT_ID + ) + } + } + private inline fun `Given repository returns non-empty value when calling createAPI then returns correct result`( crossinline repositoryBlock: suspend (RepositoryParam, ApiRequest.Options) -> ApiObject?, diff --git a/stripe/src/test/java/com/stripe/android/model/ConfirmPaymentIntentParamsTest.kt b/stripe/src/test/java/com/stripe/android/model/ConfirmPaymentIntentParamsTest.kt index 611b76d34fc..f432af29744 100644 --- a/stripe/src/test/java/com/stripe/android/model/ConfirmPaymentIntentParamsTest.kt +++ b/stripe/src/test/java/com/stripe/android/model/ConfirmPaymentIntentParamsTest.kt @@ -378,6 +378,32 @@ class ConfirmPaymentIntentParamsTest { ) } + @Test + fun toParamMap_withWeChatPayPaymentMethodOptions_shouldCreateExpectedMap() { + val appId = "appId123456" + assertThat( + ConfirmPaymentIntentParams( + paymentMethodId = "pm_123", + paymentMethodOptions = PaymentMethodOptionsParams.WeChatPay( + appId = appId + ), + clientSecret = CLIENT_SECRET + ).toParamMap() + ).isEqualTo( + mapOf( + "payment_method" to "pm_123", + "payment_method_options" to mapOf( + PaymentMethod.Type.WeChatPay.code to mapOf( + PaymentMethodOptionsParams.WeChatPay.PARAM_CLIENT to "android", + PaymentMethodOptionsParams.WeChatPay.PARAM_APP_ID to appId, + ) + ), + "client_secret" to CLIENT_SECRET, + "use_stripe_sdk" to false + ) + ) + } + @Test fun toParamMap_withReceiptEmail_shouldCreateExpectedMap() { assertThat( diff --git a/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt b/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt index 14e3add429a..2665cceeca8 100644 --- a/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt +++ b/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.kt @@ -957,4 +957,74 @@ internal object PaymentIntentFixtures { ) val PI_REQUIRES_BLIK_AUTHORIZE = PARSER.parse(PI_REQUIRES_BLIK_AUTHORIZE_JSON)!! + + private val PI_REQUIRES_WECHAT_PAY_AUTHORIZE_JSON = JSONObject( + """ + { + "id": "pi_1IlJH7BNJ02ErVOjm37T3OUt", + "object": "payment_intent", + "amount": 1099, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "charges": { + "object": "list", + "data": [ + + ], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_1IlJH7BNJ02ErVOjm37T3OUt" + }, + "client_secret": "pi_1IlJH7BNJ02ErVOjm37T3OUt_secret_vgMExmjvESdtPqddHOSSSDip2", + "confirmation_method": "automatic", + "created": 1619638941, + "currency": "usd", + "customer": null, + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": { + }, + "next_action": { + "type": "wechat_pay_redirect_to_android_app", + "wechat_pay_redirect_to_android_app": { + "app_id": "wx65997d6307c3827d", + "nonce_str": "some_random_string", + "package": "Sign=WXPay", + "partner_id": "wx65997d6307c3827d", + "prepay_id": "test_transaction", + "sign": "8B26124BABC816D7140034DDDC7D3B2F1036CCB2D910E52592687F6A44790D5E", + "timestamp": "1619638941" + } + }, + "on_behalf_of": null, + "payment_method": "pm_1IlJH7BNJ02ErVOjxKQu1wfH", + "payment_method_options": { + "wechat_pay": { + } + }, + "payment_method_types": [ + "wechat_pay" + ], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "requires_action", + "transfer_data": null, + "transfer_group": null + } + """.trimIndent() + ) + + val PI_REQUIRES_WECHAT_PAY_AUTHORIZE = PARSER.parse(PI_REQUIRES_WECHAT_PAY_AUTHORIZE_JSON)!! } diff --git a/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.kt b/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.kt index 5db4d2c1bc7..069d835a8f6 100644 --- a/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.kt +++ b/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.kt @@ -48,6 +48,15 @@ class PaymentIntentTest { .containsExactly("blik") } + @Test + fun parsePaymentIntentWithWeChatPayPaymentMethods() { + val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_WECHAT_PAY_AUTHORIZE + assertThat(paymentIntent.requiresAction()) + .isTrue() + assertThat(paymentIntent.paymentMethodTypes) + .containsExactly("wechat_pay") + } + @Test fun getNextActionData_whenUseStripeSdkWith3ds2() { val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_MASTERCARD_3DS2 @@ -90,6 +99,26 @@ class PaymentIntentTest { .isInstanceOf(StripeIntent.NextActionData.BlikAuthorize::class.java) } + @Test + fun getNextActionData_whenWeChatPay() { + val paymentIntent = PaymentIntentFixtures.PI_REQUIRES_WECHAT_PAY_AUTHORIZE + assertThat(paymentIntent.nextActionData) + .isInstanceOf(StripeIntent.NextActionData.WeChatPayRedirect::class.java) + val weChat = + (paymentIntent.nextActionData as StripeIntent.NextActionData.WeChatPayRedirect).weChat + assertThat(weChat).isEqualTo( + WeChat( + appId = "wx65997d6307c3827d", + nonce = "some_random_string", + packageValue = "Sign=WXPay", + partnerId = "wx65997d6307c3827d", + prepayId = "test_transaction", + timestamp = "1619638941", + sign = "8B26124BABC816D7140034DDDC7D3B2F1036CCB2D910E52592687F6A44790D5E", + ) + ) + } + @Test fun getLastPaymentError_parsesCorrectly() { val lastPaymentError =