Skip to content

Return mandate in PaymentOption #10853

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

Closed
wants to merge 7 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.CreateFinancialConnectionsSessionForDeferredPaymentParams
import com.stripe.android.model.CreateFinancialConnectionsSessionParams
import com.stripe.android.model.Customer
Expand Down Expand Up @@ -449,6 +450,13 @@ abstract class AbsFakeStripeRepository : StripeRepository {
TODO("Not yet implemented")
}

override suspend fun listShippingAddresses(
clientSecret: String,
requestOptions: ApiRequest.Options
): Result<ConsumerShippingAddresses> {
TODO("Not yet implemented")
}

override suspend fun deletePaymentDetails(
clientSecret: String,
paymentDetailsId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_CLIENT
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.CreateFinancialConnectionsSessionForDeferredPaymentParams
import com.stripe.android.model.CreateFinancialConnectionsSessionParams
import com.stripe.android.model.Customer
Expand Down Expand Up @@ -82,6 +83,7 @@ import com.stripe.android.model.parsers.CardMetadataJsonParser
import com.stripe.android.model.parsers.ConsumerPaymentDetailsJsonParser
import com.stripe.android.model.parsers.ConsumerPaymentDetailsShareJsonParser
import com.stripe.android.model.parsers.ConsumerSessionJsonParser
import com.stripe.android.model.parsers.ConsumerShippingAddressesParser
import com.stripe.android.model.parsers.CustomerJsonParser
import com.stripe.android.model.parsers.ElementsSessionJsonParser
import com.stripe.android.model.parsers.FinancialConnectionsSessionJsonParser
Expand Down Expand Up @@ -1512,6 +1514,25 @@ class StripeApiRepository @JvmOverloads internal constructor(
)
}

override suspend fun listShippingAddresses(
clientSecret: String,
requestOptions: ApiRequest.Options
): Result<ConsumerShippingAddresses> {
return fetchStripeModelResult(
apiRequestFactory.createPost(
listShippingAddresses,
requestOptions,
mapOf(
"request_surface" to "android_payment_element",
"credentials" to mapOf(
"consumer_session_client_secret" to clientSecret
),
)
),
ConsumerShippingAddressesParser
)
}

override suspend fun deletePaymentDetails(
clientSecret: String,
paymentDetailsId: String,
Expand Down Expand Up @@ -1911,6 +1932,13 @@ class StripeApiRepository @JvmOverloads internal constructor(
@JvmSynthetic
get() = getApiUrl("consumers/payment_details/list")

/**
* @return `https://api.stripe.com/v1/consumers/shipping_addresses/list`
*/
internal val listShippingAddresses: String
@JvmSynthetic
get() = getApiUrl("consumers/shipping_addresses/list")

/**
* @return `https://api.stripe.com/v1/consumers/payment_details/share`
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.CreateFinancialConnectionsSessionForDeferredPaymentParams
import com.stripe.android.model.CreateFinancialConnectionsSessionParams
import com.stripe.android.model.Customer
Expand Down Expand Up @@ -411,6 +412,12 @@ interface StripeRepository {
requestOptions: ApiRequest.Options
): Result<ConsumerPaymentDetails>

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
suspend fun listShippingAddresses(
clientSecret: String,
requestOptions: ApiRequest.Options
): Result<ConsumerShippingAddresses>

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
suspend fun deletePaymentDetails(
clientSecret: String,
Expand Down
16 changes: 16 additions & 0 deletions payments-model/api/payments-model.api
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,22 @@ public final class com/stripe/android/model/ConsumerSessionSignup$Creator : andr
public synthetic fun newArray (I)[Ljava/lang/Object;
}

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

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

public final class com/stripe/android/model/IncentiveEligibilitySession$DeferredIntent$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/IncentiveEligibilitySession$DeferredIntent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.stripe.android.model

import androidx.annotation.RestrictTo
import com.stripe.android.core.model.StripeModel
import kotlinx.parcelize.Parcelize

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
data class ConsumerShippingAddresses(
val addresses: List<ConsumerShippingAddress>,
) : StripeModel

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
data class ConsumerShippingAddress(
val id: String,
val isDefault: Boolean,
val address: ConsumerPaymentDetails.BillingAddress,
) : StripeModel
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.stripe.android.model.parsers

import androidx.annotation.RestrictTo
import com.stripe.android.core.model.CountryCode
import com.stripe.android.core.model.StripeJsonUtils.optString
import com.stripe.android.core.model.parsers.ModelJsonParser
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerShippingAddress
import com.stripe.android.model.ConsumerShippingAddresses
import org.json.JSONObject

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object ConsumerShippingAddressesParser : ModelJsonParser<ConsumerShippingAddresses> {

override fun parse(json: JSONObject): ConsumerShippingAddresses? {
val shippingAddresses = json.optJSONArray("shipping_addresses") ?: return null
val addresses = (0 until shippingAddresses.length()).mapNotNull { index ->
val jsonObject = shippingAddresses.getJSONObject(index)
parseShippingAddress(jsonObject)
}
return ConsumerShippingAddresses(addresses)
}

private fun parseShippingAddress(json: JSONObject): ConsumerShippingAddress? {
val id = json.optString("id")
val isDefault = json.optBoolean("is_default")
val address = json.optJSONObject("address") ?: return null

return ConsumerShippingAddress(
id = id,
isDefault = isDefault,
address = parseAddress(address),
)
}

private fun parseAddress(json: JSONObject): ConsumerPaymentDetails.BillingAddress {
return ConsumerPaymentDetails.BillingAddress(
name = optString(json, "name"),
line1 = optString(json, "line_1"),
line2 = optString(json, "line_2"),
locality = optString(json, "locality"),
administrativeArea = optString(json, "administrative_area"),
postalCode = optString(json, "postal_code"),
countryCode = optString(json, "country_code")?.let { CountryCode(it) },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.stripe.android.paymentelement.rememberEmbeddedPaymentElement
import com.stripe.android.paymentsheet.ExperimentalCustomerSessionApi
import com.stripe.android.paymentsheet.ExternalPaymentMethodConfirmHandler
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.addresselement.AddressDetails
import com.stripe.android.paymentsheet.addresselement.AddressLauncher
import com.stripe.android.paymentsheet.addresselement.rememberAddressLauncher
import com.stripe.android.paymentsheet.example.Settings
Expand Down Expand Up @@ -340,6 +341,7 @@ internal class PaymentSheetPlaygroundActivity :
ShippingAddressButton(
addressLauncher = addressLauncher,
playgroundState = playgroundState,
address = { flowController.shippingDetails },
)
}

Expand Down Expand Up @@ -542,11 +544,13 @@ internal class PaymentSheetPlaygroundActivity :
private fun ShippingAddressButton(
addressLauncher: AddressLauncher,
playgroundState: PlaygroundState.Payment,
address: () -> AddressDetails?,
) {
val context = LocalContext.current
Button(
onClick = {
val configuration = AddressLauncher.Configuration.Builder()
.address(address())
.googlePlacesApiKey(Settings(context).googlePlacesApiKey)
.appearance(AppearanceStore.state)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,8 @@ internal class PaymentSheetPlaygroundViewModel(
fun onPaymentOptionSelected(paymentOption: PaymentOption?) {
flowControllerState.update { existingState ->
existingState?.copy(
selectedPaymentOption = paymentOption
selectedPaymentOption = paymentOption,
addressDetails = paymentOption?.shippingDetails,
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion paymentsheet/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ConstructorParameterNaming:BankFormScreenState.kt$BankFormScreenState$private val _isProcessing: Boolean = false</ID>
<ID>ConstructorParameterNaming:PaymentOption.kt$PaymentOption$private val _shippingDetails: AddressDetails?</ID>
<ID>CyclomaticComplexMethod:CustomerSheetViewModel.kt$CustomerSheetViewModel$fun handleViewAction(viewAction: CustomerSheetViewAction)</ID>
<ID>CyclomaticComplexMethod:PlaceholderHelper.kt$PlaceholderHelper$@VisibleForTesting internal fun specForPlaceholderField( field: PlaceholderField, placeholderOverrideList: List&lt;IdentifierSpec>, requiresMandate: Boolean, configuration: PaymentSheet.BillingDetailsCollectionConfiguration, )</ID>
<ID>CyclomaticComplexMethod:TransformBankIconCodeToBankIcon.kt$internal fun transformBankIconCodeToBankIcon( iconCode: String?, fallbackIcon: Int, ): Int</ID>
Expand Down Expand Up @@ -40,7 +41,6 @@
<ID>LongMethod:PaymentDetails.kt$@Composable internal fun PaymentDetailsListItem( modifier: Modifier = Modifier, paymentDetails: ConsumerPaymentDetails.PaymentDetails, isClickable: Boolean, isMenuButtonClickable: Boolean, isAvailable: Boolean, isSelected: Boolean, isUpdating: Boolean, onClick: () -> Unit, onMenuButtonClick: () -> Unit )</ID>
<ID>LongMethod:PaymentDetails.kt$@Preview(showBackground = true) @Composable private fun PaymentDetailsListItemPreview()</ID>
<ID>LongMethod:PaymentElementLoader.kt$DefaultPaymentElementLoader$private suspend fun createLinkConfiguration( configuration: CommonConfiguration, customer: CustomerInfo?, elementsSession: ElementsSession, initializationMode: PaymentElementLoader.InitializationMode ): LinkConfiguration?</ID>
<ID>LongMethod:PaymentMethodRowButton.kt$@OptIn(ExperimentalEmbeddedPaymentElementApi::class) @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview private fun ButtonPreview()</ID>
<ID>LongMethod:PaymentMethodVerticalLayoutInteractor.kt$DefaultPaymentMethodVerticalLayoutInteractor.Companion$fun create( viewModel: BaseSheetViewModel, paymentMethodMetadata: PaymentMethodMetadata, customerStateHolder: CustomerStateHolder, bankFormInteractor: BankFormInteractor, ): PaymentMethodVerticalLayoutInteractor</ID>
<ID>LongMethod:PaymentSheetConfigurationKtx.kt$internal fun PaymentSheet.Appearance.parseAppearance()</ID>
<ID>LongMethod:PaymentSheetScreen.kt$@Composable private fun PaymentSheetContent( viewModel: BaseSheetViewModel, headerText: ResolvableString?, walletsState: WalletsState?, walletsProcessingState: WalletsProcessingState?, error: ResolvableString?, currentScreen: PaymentSheetScreen, mandateText: MandateText?, modifier: Modifier )</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.stripe.android.link

import android.os.Parcelable
import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.model.ConsumerShippingAddress
import com.stripe.android.model.PaymentMethod
import kotlinx.parcelize.Parcelize

Expand All @@ -13,6 +15,8 @@ internal sealed class LinkActivityResult : Parcelable {
internal data class Completed(
val linkAccountUpdate: LinkAccountUpdate,
val selectedPayment: LinkPaymentMethod? = null,
val shippingAddress: ConsumerShippingAddress? = null,
val mandate: ResolvableString? = null,
) : LinkActivityResult()

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.ConsumerSignUpConsentAction
import com.stripe.android.model.EmailSource
import com.stripe.android.model.PaymentMethodCreateParams
Expand Down Expand Up @@ -50,6 +51,8 @@ internal class DefaultLinkAccountManager @Inject constructor(
private val _consumerPaymentDetails: MutableStateFlow<ConsumerPaymentDetails?> = MutableStateFlow(null)
override val consumerPaymentDetails: StateFlow<ConsumerPaymentDetails?> = _consumerPaymentDetails.asStateFlow()

override var cachedShippingAddresses: ConsumerShippingAddresses? = null

/**
* The publishable key for the signed in Link account.
*/
Expand Down Expand Up @@ -346,6 +349,15 @@ internal class DefaultLinkAccountManager @Inject constructor(
}
}

override suspend fun listShippingAddresses(): Result<ConsumerShippingAddresses> {
val clientSecret = linkAccountHolder.linkAccount.value?.clientSecret
?: return Result.failure(NoLinkAccountFoundException())
return linkRepository.listShippingAddresses(
consumerSessionClientSecret = clientSecret,
consumerPublishableKey = consumerPublishableKey,
)
}

override suspend fun deletePaymentDetails(paymentDetailsId: String): Result<Unit> {
val clientSecret = linkAccountHolder.linkAccount.value?.clientSecret
?: return Result.failure(NoLinkAccountFoundException())
Expand Down Expand Up @@ -395,6 +407,7 @@ internal class DefaultLinkAccountManager @Inject constructor(
_consumerPaymentDetails.value = null
}
consumerPublishableKey = null
cachedShippingAddresses = null
null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.EmailSource
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.SharePaymentDetails
Expand All @@ -28,6 +29,11 @@ internal interface LinkAccountManager {
*/
val consumerPaymentDetails: StateFlow<ConsumerPaymentDetails?>

/**
* Cached shipping addresses for the current Link account.
*/
var cachedShippingAddresses: ConsumerShippingAddresses?

/**
* Retrieves the Link account associated with the email if it exists.
*
Expand Down Expand Up @@ -119,6 +125,11 @@ internal interface LinkAccountManager {
*/
suspend fun listPaymentDetails(paymentMethodTypes: Set<String>): Result<ConsumerPaymentDetails>

/**
* Fetch all shipping addresses for the signed in consumer.
*/
suspend fun listShippingAddresses(): Result<ConsumerShippingAddresses>

/**
* Delete the payment method from the signed in consumer account.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.stripe.android.link.account

import com.stripe.android.link.LinkAccountUpdate
import com.stripe.android.model.ConsumerShippingAddress

internal val LinkAccountManager.linkAccountUpdate: LinkAccountUpdate
get() = LinkAccountUpdate.Value(linkAccount.value)

internal suspend fun LinkAccountManager.loadDefaultShippingAddress(): ConsumerShippingAddress? {
val shippingAddresses = cachedShippingAddresses ?: listShippingAddresses().getOrNull() ?: return null
cachedShippingAddresses = shippingAddresses
return shippingAddresses.addresses.firstOrNull { it.isDefault } ?: shippingAddresses.addresses.firstOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.stripe.android.model.ConsumerPaymentDetailsUpdateParams
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSessionLookup
import com.stripe.android.model.ConsumerSessionSignup
import com.stripe.android.model.ConsumerShippingAddresses
import com.stripe.android.model.ConsumerSignUpConsentAction
import com.stripe.android.model.EmailSource
import com.stripe.android.model.IncentiveEligibilitySession
Expand Down Expand Up @@ -328,6 +329,21 @@ internal class LinkApiRepository @Inject constructor(
)
}

override suspend fun listShippingAddresses(
consumerSessionClientSecret: String,
consumerPublishableKey: String?
): Result<ConsumerShippingAddresses> {
return stripeRepository.listShippingAddresses(
clientSecret = consumerSessionClientSecret,
requestOptions = consumerPublishableKey?.let {
ApiRequest.Options(it)
} ?: ApiRequest.Options(
publishableKeyProvider(),
stripeAccountIdProvider()
)
)
}

override suspend fun deletePaymentDetails(
paymentDetailsId: String,
consumerSessionClientSecret: String,
Expand Down
Loading
Loading