Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add konbini to PaymentSheet. #7308

Merged
merged 8 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### PaymentSheet
* [ADDED][7302](https://github.com/stripe/stripe-android/pull/7302) PaymentSheet now supports Alma for PaymentIntents in private beta.
* [ADDED][7191](https://github.com/stripe/stripe-android/pull/7191) `PaymentSheet.GooglePayConfiguration` now takes an optional `amount` and `label`. The `amount` will be displayed in Google Pay for SetupIntents, while `label` will be displayed for both PaymentIntents and SetupIntents.
* [ADDED][7308](https://github.com/stripe/stripe-android/pull/7308) PaymentSheet now supports Konbini for PaymentIntents.

### Payments
* [ADDED][7191](https://github.com/stripe/stripe-android/pull/7191) `GooglePayLauncher` now takes an optional `label` when presenting Google Pay for PaymentIntents, and an optional `amount` and `label` when presenting for SetupIntents.
Expand Down
18 changes: 18 additions & 0 deletions payments-core/api/payments-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -3789,6 +3789,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum,
public static final field GrabPay Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Ideal Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Klarna Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Konbini Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Link Lcom/stripe/android/model/PaymentMethod$Type;
public static final field MobilePay Lcom/stripe/android/model/PaymentMethod$Type;
public static final field Netbanking Lcom/stripe/android/model/PaymentMethod$Type;
Expand Down Expand Up @@ -4524,6 +4525,14 @@ public final class com/stripe/android/model/PaymentMethodOptionsParams$Card$Crea
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/PaymentMethodOptionsParams$Konbini$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/PaymentMethodOptionsParams$Konbini;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/model/PaymentMethodOptionsParams$Konbini;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/PaymentMethodOptionsParams$USBankAccount : com/stripe/android/model/PaymentMethodOptionsParams {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
Expand Down Expand Up @@ -6043,6 +6052,14 @@ public final class com/stripe/android/model/StripeIntent$NextActionData$DisplayB
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/StripeIntent$NextActionData$DisplayKonbiniDetails$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/StripeIntent$NextActionData$DisplayKonbiniDetails;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/model/StripeIntent$NextActionData$DisplayKonbiniDetails;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/model/StripeIntent$NextActionData$DisplayOxxoDetails : com/stripe/android/model/StripeIntent$NextActionData {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
Expand Down Expand Up @@ -6244,6 +6261,7 @@ public final class com/stripe/android/model/StripeIntent$NextActionType : java/l
public static final field BlikAuthorize Lcom/stripe/android/model/StripeIntent$NextActionType;
public static final field CashAppRedirect Lcom/stripe/android/model/StripeIntent$NextActionType;
public static final field DisplayBoletoDetails Lcom/stripe/android/model/StripeIntent$NextActionType;
public static final field DisplayKonbiniDetails Lcom/stripe/android/model/StripeIntent$NextActionType;
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 UpiAwaitNotification Lcom/stripe/android/model/StripeIntent$NextActionType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ internal class ConfirmPaymentIntentParamsFactory(
PaymentMethod.Type.Blik.code -> {
optionsParams
}
PaymentMethod.Type.Konbini.code -> {
optionsParams
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking this for a future refactor.

}
PaymentMethod.Type.Link.code -> {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ abstract class StripeIntentResult<out T : StripeIntent> internal constructor(
StripeIntent.NextActionType.BlikAuthorize,
StripeIntent.NextActionType.DisplayOxxoDetails,
StripeIntent.NextActionType.DisplayBoletoDetails,
StripeIntent.NextActionType.DisplayKonbiniDetails,
StripeIntent.NextActionType.UpiAwaitNotification,
StripeIntent.NextActionType.VerifyWithMicrodeposits -> {
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ constructor(
is StripeIntent.NextActionData.DisplayBoletoDetails -> {
StripeIntent.NextActionType.DisplayBoletoDetails
}
is StripeIntent.NextActionData.DisplayKonbiniDetails -> {
StripeIntent.NextActionType.DisplayKonbiniDetails
}
is StripeIntent.NextActionData.VerifyWithMicrodeposits -> {
StripeIntent.NextActionType.VerifyWithMicrodeposits
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,13 @@ constructor(
isVoucher = true,
requiresMandate = false,
hasDelayedSettlement = true,
),
Konbini(
code = "konbini",
isReusable = false,
isVoucher = true,
requiresMandate = false,
hasDelayedSettlement = true,
);

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For paymentsheet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.model

import android.os.Parcelable
import androidx.annotation.RestrictTo
import kotlinx.parcelize.Parcelize

sealed class PaymentMethodOptionsParams(
Expand Down Expand Up @@ -73,6 +74,22 @@ sealed class PaymentMethodOptionsParams(
}
}

@Parcelize
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Konbini(
private val confirmationNumber: String
) : PaymentMethodOptionsParams(PaymentMethod.Type.Konbini) {
override fun createTypeParams(): List<Pair<String, Any?>> {
return listOf(
PARAM_CONFIRMATION_NUMBER to confirmationNumber
)
}

internal companion object {
const val PARAM_CONFIRMATION_NUMBER = "confirmation_number"
}
}

@Parcelize
data class WeChatPay(
var appId: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ data class SetupIntent internal constructor(
is StripeIntent.NextActionData.DisplayBoletoDetails -> {
StripeIntent.NextActionType.DisplayBoletoDetails
}
is StripeIntent.NextActionData.DisplayKonbiniDetails -> {
StripeIntent.NextActionType.DisplayKonbiniDetails
}
is StripeIntent.NextActionData.VerifyWithMicrodeposits -> {
StripeIntent.NextActionType.VerifyWithMicrodeposits
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ sealed interface StripeIntent : StripeModel {
VerifyWithMicrodeposits("verify_with_microdeposits"),
UpiAwaitNotification("upi_await_notification"),
CashAppRedirect("cashapp_handle_redirect_or_display_qr_code"),
DisplayBoletoDetails("boleto_display_details");
DisplayBoletoDetails("boleto_display_details"),
DisplayKonbiniDetails("konbini_display_details");

@Keep
override fun toString(): String {
Expand Down Expand Up @@ -197,6 +198,15 @@ sealed interface StripeIntent : StripeModel {
val hostedVoucherUrl: String? = null,
) : NextActionData()

@Parcelize
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class DisplayKonbiniDetails(
/**
* URL of a webpage containing the voucher for this payment.
*/
Comment on lines +204 to +206
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* URL of a webpage containing the voucher for this payment.
*/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could expose this for bindings. I just copied what the others did.

val hostedVoucherUrl: String? = null,
) : NextActionData()

/**
* Contains instructions for authenticating by redirecting your customer to another
* page or application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class NextActionDataParser : ModelJsonParser<StripeIntent.NextActionDat
val parser = when (nextActionType) {
StripeIntent.NextActionType.DisplayOxxoDetails -> DisplayOxxoDetailsJsonParser()
StripeIntent.NextActionType.DisplayBoletoDetails -> DisplayBoletoDetailsJsonParser()
StripeIntent.NextActionType.DisplayKonbiniDetails -> DisplayKonbiniDetailsJsonParser()
StripeIntent.NextActionType.RedirectToUrl -> RedirectToUrlParser()
StripeIntent.NextActionType.UseStripeSdk -> SdkDataJsonParser()
StripeIntent.NextActionType.AlipayRedirect -> AlipayRedirectParser()
Expand Down Expand Up @@ -66,6 +67,21 @@ internal class NextActionDataParser : ModelJsonParser<StripeIntent.NextActionDat
}
}

private class DisplayKonbiniDetailsJsonParser :
ModelJsonParser<StripeIntent.NextActionData.DisplayKonbiniDetails> {
override fun parse(
json: JSONObject
): StripeIntent.NextActionData.DisplayKonbiniDetails {
return StripeIntent.NextActionData.DisplayKonbiniDetails(
hostedVoucherUrl = optString(json, FIELD_HOSTED_VOUCHER_URL)
)
}

private companion object {
private const val FIELD_HOSTED_VOUCHER_URL = "hosted_voucher_url"
}
}

internal class RedirectToUrlParser :
ModelJsonParser<StripeIntent.NextActionData.RedirectToUrl> {
override fun parse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import javax.inject.Inject
import javax.inject.Singleton

/**
* [PaymentAuthenticator] for [NextActionData.DisplayOxxoDetails], redirects to
* [PaymentAuthenticator] for [NextActionData.DisplayBoletoDetails], redirects to
* [WebIntentAuthenticator] or [NoOpIntentAuthenticator] based on whether if there is a
* hostedVoucherUrl set.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.stripe.android.payments.core.authentication

import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.model.StripeIntent
import com.stripe.android.model.StripeIntent.NextActionData
import com.stripe.android.view.AuthActivityStarterHost
import javax.inject.Inject
import javax.inject.Singleton

/**
* [PaymentAuthenticator] for [NextActionData.DisplayKonbiniDetails], redirects to
* [WebIntentAuthenticator] or [NoOpIntentAuthenticator] based on whether if there is a
* hostedVoucherUrl set.
*/
@Singleton
internal class KonbiniAuthenticator @Inject constructor(
private val webIntentAuthenticator: WebIntentAuthenticator,
private val noOpIntentAuthenticator: NoOpIntentAuthenticator
) : PaymentAuthenticator<StripeIntent>() {
override suspend fun performAuthentication(
host: AuthActivityStarterHost,
authenticatable: StripeIntent,
requestOptions: ApiRequest.Options
) {
(authenticatable.nextActionData as NextActionData.DisplayKonbiniDetails).let { detailsData ->
if (detailsData.hostedVoucherUrl == null) {
noOpIntentAuthenticator.authenticate(
host,
authenticatable,
requestOptions
)
} else {
webIntentAuthenticator.authenticate(
host,
authenticatable,
requestOptions
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ internal class WebIntentAuthenticator @Inject constructor(
returnUrl = null
shouldCancelIntentOnUserNavigation = false
}
is StripeIntent.NextActionData.DisplayKonbiniDetails -> {
// nextActionData.hostedVoucherUrl will never be null as AuthenticatorRegistry won't direct it here
authUrl = nextActionData.hostedVoucherUrl.takeIf { it!!.isNotEmpty() }
?: throw IllegalArgumentException("null hostedVoucherUrl for DisplayKonbiniDetails")
returnUrl = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providing a return URL would cause this to be opened in a Chrome Custom Tab instead of a web view. Should we do that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what the other vouchers do, so I just kept with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably start using CCT.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you peaceful if I leave it like this for this PR and submit a new PR to update all voucher based LPMs to switch to CCT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or rather, what's the motivation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe for one-click auth to be enabled, we need CCT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think some payment methods are using CCT while others are using webview right? For example, bancontact uses CCT.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These wouldn't have auth though, you'd need to take them to a different app, or to a physical location, so they wouldn't benefit from that.

shouldCancelIntentOnUserNavigation = false
}
is StripeIntent.NextActionData.CashAppRedirect -> {
authUrl = nextActionData.mobileAuthUrl
returnUrl = defaultReturnUrl.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.stripe.android.model.StripeIntent.NextActionData
import com.stripe.android.payments.DefaultReturnUrl
import com.stripe.android.payments.core.authentication.BoletoAuthenticator
import com.stripe.android.payments.core.authentication.DefaultPaymentAuthenticatorRegistry
import com.stripe.android.payments.core.authentication.KonbiniAuthenticator
import com.stripe.android.payments.core.authentication.OxxoAuthenticator
import com.stripe.android.payments.core.authentication.PaymentAuthenticator
import com.stripe.android.payments.core.authentication.WebIntentAuthenticator
Expand Down Expand Up @@ -57,6 +58,14 @@ internal abstract class AuthenticationModule {
oxxoAuthenticator: OxxoAuthenticator
): PaymentAuthenticator<StripeIntent>

@IntentAuthenticatorMap
@Binds
@IntoMap
@IntentAuthenticatorKey(NextActionData.DisplayKonbiniDetails::class)
abstract fun bindsKonbiniAuthenticator(
konbiniAuthenticator: KonbiniAuthenticator
): PaymentAuthenticator<StripeIntent>

@IntentAuthenticatorMap
@Binds
@IntoMap
Expand Down
16 changes: 16 additions & 0 deletions payments-ui-core/api/payments-ui-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,22 @@ public final class com/stripe/android/ui/core/elements/KlarnaCountrySpec$Compani
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/stripe/android/ui/core/elements/KonbiniConfirmationNumberSpec$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field $stable I
public static final field INSTANCE Lcom/stripe/android/ui/core/elements/KonbiniConfirmationNumberSpec$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/ui/core/elements/KonbiniConfirmationNumberSpec;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/ui/core/elements/KonbiniConfirmationNumberSpec;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/stripe/android/ui/core/elements/KonbiniConfirmationNumberSpec$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/stripe/android/ui/core/elements/LayoutSpec$Companion {
public final fun create ()Lcom/stripe/android/ui/core/elements/LayoutSpec;
public final fun create ([Lcom/stripe/android/ui/core/elements/FormItemSpec;)Lcom/stripe/android/ui/core/elements/LayoutSpec;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="m4.867,6a2.227,2.227 45,0 1,1.16 -1.867c0.547,-0.4 0.72,-0.533 0.72,-0.88s-0.173,-0.587 -0.493,-0.587 -0.533,0.24 -0.587,0.573l-0.813,0a1.333,1.333 0,0 1,1.4 -1.24c1,0 1.333,0.6 1.333,1.187s-0.227,0.813 -0.92,1.36c-0.48,0.333 -0.667,0.56 -0.733,0.733l1.813,0l-0.107,0.72z" />
<path
android:fillColor="#FF000000"
android:pathData="m9.893,6l0,-0.88l-1.773,0l0,-0.747l1.627,-2.293l0.92,0l0,2.347l0.467,0l0,0.693l-0.467,0l0,0.88zM9.893,3.733c0,-0.427 0,-0.667 0,-0.92a17.107,17.107 0,0 1,-1.04 1.613l1.027,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m12,1.333l0,5.333l-8,0l0,-5.333zM12,0l-8,0a1.333,1.333 0,0 0,-1.333 1.333l0,5.333a1.333,1.333 0,0 0,1.333 1.333l8,0a1.333,1.333 0,0 0,1.333 -1.333l0,-5.333a1.333,1.333 0,0 0,-1.333 -1.333z" />
<path
android:fillColor="#FF000000"
android:pathData="m10.667,10.667l0,4l-5.333,0l0,-4zM10.667,9.333l-5.333,0a1.333,1.333 0,0 0,-1.333 1.333l0,4a1.333,1.333 0,0 0,1.333 1.333l5.333,0a1.333,1.333 0,0 0,1.333 -1.333l0,-4a1.333,1.333 0,0 0,-1.333 -1.333z" />
<path
android:fillColor="#FF000000"
android:pathData="m7.333,10l1.333,0l0,5.187l-1.333,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m1.093,5.333l2.707,0l0,2.667l-2.707,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m1.093,2.667l2.52,0l0,1.36l-2.52,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m12.413,2.667l2.52,0l0,1.36l-2.52,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m12.133,5.333l2.987,0l0,2.667l-2.987,0z" />
<path
android:fillColor="#FF000000"
android:pathData="m14.667,1.333l-2.667,0l0,1.333l2.667,0l0,12l-13.333,0l0,-12l2.667,0l0,-1.333l-2.667,0a1.333,1.333 0,0 0,-1.333 1.333l0,12a1.333,1.333 0,0 0,1.333 1.333l13.333,0a1.333,1.333 0,0 0,1.333 -1.333l0,-12a1.333,1.333 0,0 0,-1.333 -1.333z" />
</vector>
4 changes: 4 additions & 0 deletions payments-ui-core/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,8 @@
<string name="stripe_setup_button_label">Set up</string>
<!-- Label for UPI ID number field on form -->
<string name="stripe_upi_id_label">UPI ID</string>
<!-- Label for Konbini Payment Method -->
<string name="stripe_paymentsheet_payment_method_konbini">Konbini</string>
Comment on lines +80 to +81
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in the no-translate file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is translated differently in Japanese.

<!-- Label for Konbini Confirmation Number -->
<string name="stripe_konbini_confirmation_number_label">Phone</string>
</resources>
21 changes: 21 additions & 0 deletions payments-ui-core/src/main/assets/lpms.json
Original file line number Diff line number Diff line change
Expand Up @@ -1065,5 +1065,26 @@
"allowed_country_codes": ["BR"]
}
]
},
{
"type": "konbini",
"async": false,
"fields": [
{
"type": "name",
"api_path": {
"v1": "billing_details[name]"
}
},
{
"type": "email",
"api_path": {
"v1": "billing_details[email]"
}
},
{
"type": "konbini_confirmation_number"
}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,9 @@ internal val BoletoRequirement = PaymentMethodRequirements(
siRequirements = setOf(Delayed),
confirmPMFromCustomer = true,
)

internal val KonbiniRequirement = PaymentMethodRequirements(
piRequirements = setOf(Delayed),
siRequirements = null,
confirmPMFromCustomer = null,
)
Loading