Skip to content
Draft
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
@@ -0,0 +1,26 @@
package com.stripe.android.model

import androidx.annotation.RestrictTo
import com.stripe.android.core.model.StripeModel
import dev.drewhamilton.poko.Poko
import kotlinx.parcelize.Parcelize

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Poko
@Parcelize
class CheckoutSessionResponse(
val currency: String,
val amount: Long,
val mode: Mode,
val setupFutureUsage: StripeIntent.Usage?,
val captureMethod: PaymentIntent.CaptureMethod,
val paymentMethodOptionsJsonString: String?,
val paymentMethodTypes: List<String>,
val onBehalfOf: String?,
val intent: StripeIntent?,
val paymentMethods: List<PaymentMethod>?,
) : StripeModel {
enum class Mode {
Payment, Subscription,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ sealed interface ElementsSessionParams : Parcelable {

val type: String
val clientSecret: String?
val checkoutSessionId: String?
val customerSessionClientSecret: String?
val legacyCustomerEphemeralKey: String?
val mobileSessionId: String?
Expand Down Expand Up @@ -48,6 +49,9 @@ sealed interface ElementsSessionParams : Parcelable {

override val sellerDetails: SellerDetails?
get() = null

override val checkoutSessionId: String?
get() = null
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand All @@ -74,6 +78,9 @@ sealed interface ElementsSessionParams : Parcelable {

override val sellerDetails: SellerDetails?
get() = null

override val checkoutSessionId: String?
get() = null
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand All @@ -91,6 +98,7 @@ sealed interface ElementsSessionParams : Parcelable {
override val sellerDetails: SellerDetails? = null,
override val link: Link = Link(),
override val countryOverride: String? = null,
override val checkoutSessionId: String? = null,
) : ElementsSessionParams {

override val clientSecret: String?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stripe.android.model

import androidx.annotation.RestrictTo
import dev.drewhamilton.poko.Poko

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Poko
class PaymentPageConfirmParams(
private val paymentMethodId: String,
private val expectedPaymentMethodType: String?,
private val expectedAmount: Long,
private val clientAttributionMetadata: ClientAttributionMetadata,
private val returnUrl: String,
private val passiveCaptchaToken: String?,
) {
fun asMap(): Map<String, Any?> = mapOf(
"payment_method" to paymentMethodId,
"expected_payment_method_type" to expectedPaymentMethodType,
"expected_amount" to expectedAmount,
"client_attribution_metadata" to clientAttributionMetadata.toParamMap(),
"return_url" to returnUrl,
"passive_captcha_token" to passiveCaptchaToken,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.stripe.android.model.parsers

import com.stripe.android.core.model.StripeJsonUtils.optString
import com.stripe.android.core.model.parsers.ModelJsonParser
import com.stripe.android.core.model.parsers.ModelJsonParser.Companion.jsonArrayToList
import com.stripe.android.model.CheckoutSessionResponse
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.StripeIntent
import org.json.JSONObject

internal class CheckoutSessionResponseJsonParser : ModelJsonParser<CheckoutSessionResponse> {
override fun parse(json: JSONObject): CheckoutSessionResponse {
val currency = json.getString("currency")
val amount = json.getJSONObject("total_summary").getLong("total")
val mode = when (json.get("mode")) {
"payment" -> CheckoutSessionResponse.Mode.Payment
"subscription" -> {
if (json.has("invoice")) {
CheckoutSessionResponse.Mode.Payment
} else {
CheckoutSessionResponse.Mode.Subscription
}
}
else -> throw IllegalArgumentException("Invalid mode")
}
val setupFutureUsage = StripeIntent.Usage.fromCode(
optString(json, "setup_future_usage")
)
val captureMethod = PaymentIntent.CaptureMethod.fromCode(
optString(json, "capture_method")
)
val paymentMethodOptionsJsonString = optString(json, "payment_method_options")
val paymentMethodTypes = jsonArrayToList(
json.optJSONArray("payment_method_types")
)
val onBehalfOf = optString(json, "on_behalf_of")

val intent = when (mode) {
CheckoutSessionResponse.Mode.Payment -> {
json.optJSONObject("payment_intent")?.let { PaymentIntentJsonParser().parse(it) }
}
CheckoutSessionResponse.Mode.Subscription -> {
json.optJSONObject("setup_intent")?.let { SetupIntentJsonParser().parse(it) }
}
}

val paymentMethodsJson = json.optJSONObject("customer")?.optJSONArray("payment_methods")
val paymentMethods = paymentMethodsJson?.let { pmsJson ->
(0 until pmsJson.length()).mapNotNull { index ->
PaymentMethodJsonParser().parse(pmsJson.optJSONObject(index))
}
} ?: emptyList()

return CheckoutSessionResponse(
currency = currency,
amount = amount,
mode = mode,
setupFutureUsage = setupFutureUsage,
captureMethod = captureMethod,
paymentMethodOptionsJsonString = null, // TODO:
paymentMethodTypes = paymentMethodTypes,
onBehalfOf = onBehalfOf,
intent = intent,
paymentMethods = paymentMethods,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import com.stripe.android.core.version.StripeSdkVersion
import com.stripe.android.exception.CardException
import com.stripe.android.model.BankStatuses
import com.stripe.android.model.CardMetadata
import com.stripe.android.model.CheckoutSessionResponse
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams
Expand All @@ -71,6 +72,7 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodMessage
import com.stripe.android.model.PaymentMethodUpdateParams
import com.stripe.android.model.PaymentPageConfirmParams
import com.stripe.android.model.RadarSessionWithHCaptcha
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.ShippingInformation
Expand All @@ -82,6 +84,7 @@ import com.stripe.android.model.StripeIntent
import com.stripe.android.model.Token
import com.stripe.android.model.TokenParams
import com.stripe.android.model.parsers.CardMetadataJsonParser
import com.stripe.android.model.parsers.CheckoutSessionResponseJsonParser
import com.stripe.android.model.parsers.ConfirmationTokenJsonParser
import com.stripe.android.model.parsers.ConsumerPaymentDetailsJsonParser
import com.stripe.android.model.parsers.ConsumerPaymentDetailsShareJsonParser
Expand Down Expand Up @@ -113,6 +116,7 @@ import java.io.IOException
import java.net.HttpURLConnection
import java.security.Security
import java.util.Locale
import java.util.UUID
import javax.inject.Inject
import javax.inject.Named
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -1671,6 +1675,41 @@ class StripeApiRepository @JvmOverloads internal constructor(
)
}

override suspend fun fetchPaymentPage(
checkoutSessionId: String,
options: ApiRequest.Options,
): Result<CheckoutSessionResponse> {
return fetchStripeModelResult(
apiRequest = apiRequestFactory.createPost(
url = fetchPaymentPageUrl(checkoutSessionId),
options = options,
// TODO: We should get this without hard coding.
params = mapOf(
"browser_locale" to "en-US",
"browser_timezone" to "America/Denver",
"eid" to UUID.randomUUID().toString(),
"redirect_type" to "embedded",
)
),
jsonParser = CheckoutSessionResponseJsonParser(),
)
}

override suspend fun confirmPaymentPage(
checkoutSessionId: String,
params: PaymentPageConfirmParams,
options: ApiRequest.Options
): Result<CheckoutSessionResponse> {
return fetchStripeModelResult(
apiRequest = apiRequestFactory.createPost(
url = confirmPaymentPageUrl(checkoutSessionId),
options = options,
params = params.asMap(),
),
jsonParser = CheckoutSessionResponseJsonParser()
)
}

private suspend fun retrieveElementsSession(
params: ElementsSessionParams,
options: ApiRequest.Options,
Expand All @@ -1692,6 +1731,7 @@ class StripeApiRepository @JvmOverloads internal constructor(
this["type"] = params.type
this["mobile_app_id"] = params.appId
params.clientSecret?.let { this["client_secret"] = it }
params.checkoutSessionId?.let { this["checkout_session_id"] = it }
params.locale.let { this["locale"] = it }
params.customerSessionClientSecret?.let { this["customer_session_client_secret"] = it }
params.legacyCustomerEphemeralKey?.let { this["legacy_customer_ephemeral_key"] = it }
Expand Down Expand Up @@ -2323,6 +2363,18 @@ class StripeApiRepository @JvmOverloads internal constructor(
return "https://merchant-ui-api.stripe.com/elements/$path"
}

private fun fetchPaymentPageUrl(
checkoutSessionId: String,
): String {
return getApiUrl("payment_pages/$checkoutSessionId/init")
}

private fun confirmPaymentPageUrl(
checkoutSessionId: String,
): String {
return getApiUrl("payment_pages/$checkoutSessionId/confirm")
}

private fun createExpandParam(expandFields: List<String>): Map<String, List<String>> {
return expandFields.takeIf { it.isNotEmpty() }?.let {
mapOf("expand" to it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.core.networking.StripeResponse
import com.stripe.android.model.BankStatuses
import com.stripe.android.model.CardMetadata
import com.stripe.android.model.CheckoutSessionResponse
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmSetupIntentParams
import com.stripe.android.model.ConfirmationToken
Expand All @@ -29,6 +30,7 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodMessage
import com.stripe.android.model.PaymentMethodUpdateParams
import com.stripe.android.model.PaymentPageConfirmParams
import com.stripe.android.model.RadarSessionWithHCaptcha
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.ShippingInformation
Expand Down Expand Up @@ -445,6 +447,19 @@ interface StripeRepository {
requestOptions: ApiRequest.Options
): Result<ConsumerPaymentDetails>

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
suspend fun fetchPaymentPage(
checkoutSessionId: String,
options: ApiRequest.Options,
): Result<CheckoutSessionResponse>

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
suspend fun confirmPaymentPage(
checkoutSessionId: String,
params: PaymentPageConfirmParams,
options: ApiRequest.Options,
): Result<CheckoutSessionResponse>

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun buildPaymentUserAgent(attribution: Set<String> = emptySet()): String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.google.android.material.snackbar.Snackbar
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.example.samples.ui.shared.BuyButton
import com.stripe.android.paymentsheet.example.samples.ui.shared.CompletedPaymentAlertDialog
Expand Down Expand Up @@ -90,8 +91,12 @@ internal class CompleteFlowActivity : AppCompatActivity() {
return
}

paymentSheet.presentWithPaymentIntent(
paymentIntentClientSecret = paymentInfo.clientSecret,
PaymentConfiguration.init(
context = application,
publishableKey = "pk_test_51M5XCTE8QcndKTcNxIe9EDvsTcqKboIq3NgptdIc2u429ml2hfGyeJtE9x3qZG6tio1vFlJHXz81EfsQdqHuIzZE008MHWQPkO",
)
paymentSheet.presentWithCheckoutSession(
checkoutSessionId = "cs_test_a1fOHLv82fV9RsggFiwTpFja9n7DRbLfD7ME0rAd7cTuJCtK9art1aEMfE",
configuration = paymentInfo.paymentSheetConfig,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal fun ClientAttributionMetadata.Companion.create(
val paymentIntentCreationFlow = when (initializationMode) {
is PaymentElementLoader.InitializationMode.CryptoOnramp,
is PaymentElementLoader.InitializationMode.DeferredIntent -> PaymentIntentCreationFlow.Deferred
is PaymentElementLoader.InitializationMode.CheckoutSession, // TODO:
is PaymentElementLoader.InitializationMode.PaymentIntent,
is PaymentElementLoader.InitializationMode.SetupIntent -> PaymentIntentCreationFlow.Standard
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ internal sealed class IntegrationMetadata : Parcelable {

@Parcelize
object CryptoOnramp : IntegrationMetadata()

@Parcelize
data class CheckoutSession(val id: String) : IntegrationMetadata()
}
Loading
Loading