Skip to content

Tolu/ece/shop pay webview #10877

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

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
4 changes: 1 addition & 3 deletions .idea/codestyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
Expand All @@ -20,7 +21,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.google.android.material.snackbar.Snackbar
import com.stripe.android.paymentelement.WalletButtonsPreview
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.WalletConfiguration
import com.stripe.android.paymentsheet.example.R
import com.stripe.android.paymentsheet.example.samples.ui.shared.BuyButton
import com.stripe.android.paymentsheet.example.samples.ui.shared.CompletedPaymentAlertDialog
Expand All @@ -40,14 +43,21 @@ internal class CustomFlowActivity : AppCompatActivity() {

private val viewModel by viewModels<CustomFlowViewModel>()

@OptIn(WalletButtonsPreview::class)
@SuppressWarnings("LongMethod")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {

val flowController = rememberPaymentSheetFlowController(
paymentOptionCallback = viewModel::handlePaymentOptionChanged,
paymentResultCallback = viewModel::handlePaymentSheetResult,
walletHandlers = WalletConfiguration.Handlers(
shippingMethodUpdateHandler = viewModel::handleShippingMethodUpdate,
shippingContactUpdateHandler = viewModel::handleShippingContactUpdate,
paymentRequestPaymentMethodInitParamsHandler = viewModel::handlePaymentMethodInit
)
)

PaymentSheetExampleTheme {
Expand Down Expand Up @@ -84,23 +94,26 @@ internal class CustomFlowActivity : AppCompatActivity() {
if (uiState.isError) {
ErrorScreen(onRetry = viewModel::retry)
} else {
Receipt(
isLoading = uiState.isProcessing,
cartState = uiState.cartState,
) {
PaymentMethodSelector(
isEnabled = uiState.isPaymentMethodButtonEnabled,
paymentMethodLabel = paymentMethodLabel,
paymentMethodPainter = uiState.paymentOption?.iconPainter,
onClick = flowController::presentPaymentOptions,
)
BuyButton(
buyButtonEnabled = uiState.isBuyButtonEnabled,
onClick = {
viewModel.handleBuyButtonPressed()
flowController.confirm()
}
)
Column {
flowController.WalletButtons()
Receipt(
isLoading = uiState.isProcessing,
cartState = uiState.cartState,
) {
PaymentMethodSelector(
isEnabled = uiState.isPaymentMethodButtonEnabled,
paymentMethodLabel = paymentMethodLabel,
paymentMethodPainter = uiState.paymentOption?.iconPainter,
onClick = flowController::presentPaymentOptions,
)
BuyButton(
buyButtonEnabled = uiState.isBuyButtonEnabled,
onClick = {
viewModel.handleBuyButtonPressed()
flowController.confirm()
}
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.extensions.jsonBody
import com.github.kittinunf.fuel.core.requests.suspendable
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentMethodInitParamsHandler
import com.stripe.android.paymentsheet.PaymentRequestShippingContactUpdateHandler
import com.stripe.android.paymentsheet.PaymentRequestShippingRateUpdateHandler
import com.stripe.android.paymentsheet.PaymentSheetResult
import com.stripe.android.paymentsheet.WalletConfiguration
import com.stripe.android.paymentsheet.WalletConfiguration.SelectedPartialAddress
import com.stripe.android.paymentsheet.WalletConfiguration.SelectedShippingRate
import com.stripe.android.paymentsheet.example.samples.model.CartState
import com.stripe.android.paymentsheet.example.samples.networking.ExampleCheckoutRequest
import com.stripe.android.paymentsheet.example.samples.networking.ExampleCheckoutResponse
Expand All @@ -20,9 +26,33 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.updateAndGet
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import com.github.kittinunf.result.Result as ApiResult

@Serializable
data class FetchedItem(
val id: String,
val name: String,
val amount: Int
)

@Serializable
data class ShippingRateResponseItem(
val id: String,
val displayName: String,
val amount: Int,
val deliveryEstimate: String
)

@Serializable
data class ItemsResponse(
val items: List<FetchedItem>,
val total: Int,
val customerSessionClientSecret: String = ""
)

internal class CustomFlowViewModel(
application: Application,
) : AndroidViewModel(application) {
Expand All @@ -32,6 +62,18 @@ internal class CustomFlowViewModel(
)
val state: StateFlow<CustomFlowViewState> = _state

private val _addressState = MutableStateFlow(
value = SelectedPartialAddress(
city = "San Francisco",
state = "CA",
postalCode = "94107",
country = "US"
)
)

private var lineItems: List<FetchedItem> = emptyList()
private var amountTotal: Int = 0

init {
viewModelScope.launch(Dispatchers.IO) {
prepareCheckout()
Expand Down Expand Up @@ -128,6 +170,162 @@ internal class CustomFlowViewModel(
}
}

fun handleShippingMethodUpdate(rate: SelectedShippingRate, handler: PaymentRequestShippingRateUpdateHandler) {
viewModelScope.launch(Dispatchers.IO) {
try {
val lineItems = fetchItems()
val rates = fetchShippingRates(_addressState.value.state, _addressState.value.country)
handler.onUpdate(
update = WalletConfiguration.PaymentRequestShippingRateUpdate.accepted(
lineItems = lineItems.items.map {
WalletConfiguration.LineItem(
name = it.name,
amount = it.amount
)
},
shippingRates = rates.map {
WalletConfiguration.ShippingRate(
id = it.id,
amount = it.amount,
displayName = it.displayName,
deliveryEstimate = WalletConfiguration.DeliveryEstimate.Text(it.deliveryEstimate)
)
}
)
)
} catch (e: Throwable) {
handler.onUpdate(
update = WalletConfiguration.PaymentRequestShippingRateUpdate.rejected(
error = e.message ?: "Unknown error"
)
)
}
}
}

fun handleShippingContactUpdate(address: SelectedPartialAddress, handler: PaymentRequestShippingContactUpdateHandler) {
viewModelScope.launch(Dispatchers.IO) {
_addressState.value = address
val lineItems = fetchItems()
val rates = fetchShippingRates(
provinceCode = _addressState.value.state.takeIf { it.isNotBlank() } ?: "CA",
countryCode = _addressState.value.country.takeIf { it.isNotBlank() } ?: "US"
)
handler.onUpdate(
update = WalletConfiguration.PaymentRequestShippingContactUpdate(
lineItems = lineItems.items.map {
WalletConfiguration.LineItem(
name = it.name,
amount = it.amount
)
},
shippingRates = rates.map {
WalletConfiguration.ShippingRate(
id = it.id,
amount = it.amount,
displayName = it.displayName,
deliveryEstimate = WalletConfiguration.DeliveryEstimate.Text(it.deliveryEstimate)
)
}
)
)
}
}

fun handlePaymentMethodInit(handler: PaymentMethodInitParamsHandler) {
viewModelScope.launch(Dispatchers.IO) {
val lineItems = fetchItems()
val rates = fetchShippingRates(
provinceCode = _addressState.value.state.takeIf { it.isNotBlank() } ?: "CA",
countryCode = _addressState.value.country.takeIf { it.isNotBlank() } ?: "US"
)

handler(
WalletConfiguration.PaymentMethodInitParams(
lineItems = lineItems.items.map {
WalletConfiguration.LineItem(
name = it.name,
amount = it.amount
)
},
shippingRates = rates.map {
WalletConfiguration.ShippingRate(
id = it.id,
amount = it.amount,
displayName = it.displayName,
deliveryEstimate = WalletConfiguration.DeliveryEstimate.Text(it.deliveryEstimate)
)
}
)
)
}
}

private suspend fun fetchItems(): ItemsResponse {
val url = "https://unexpected-dune-list.glitch.me/items"

println("🛒 Fetching items from: $url")

return try {
val apiResult = Fuel.get(url)
.suspendable()
.awaitModel(ItemsResponse.serializer())

when (apiResult) {
is com.github.kittinunf.result.Result.Success -> {
val jsonString = apiResult.value

jsonString
}
is com.github.kittinunf.result.Result.Failure -> {
println("❌ Failed to fetch items: ${apiResult.error}")
useDefaultItems()
}
}
} catch (error: Exception) {
println("❌ Failed to fetch items: $error")
useDefaultItems()
}
}

private fun useDefaultItems(): ItemsResponse {
// Use default values on error - matching the Swift code
return ItemsResponse(
items = listOf(
FetchedItem(name = "Golden Potato", amount = 500, id = ""),
FetchedItem(name = "Silver Potato", amount = 345, id = "")
),
total = 1045
)
}

private suspend fun fetchShippingRates(provinceCode: String, countryCode: String): List<ShippingRateResponseItem> {
val url = "https://unexpected-dune-list.glitch.me/shipping?state=$provinceCode&country=$countryCode"

println("📦 Fetching shipping rates from: $url")

return try {
val apiResult = Fuel.get(url)
.suspendable()
.awaitModel(ListSerializer(ShippingRateResponseItem.serializer()))

when (apiResult) {
is com.github.kittinunf.result.Result.Success -> {
val rates = apiResult.value
println("✅ Fetched ${rates.size} shipping rates")
rates
}
is com.github.kittinunf.result.Result.Failure -> {
println("❌ Failed to fetch shipping rates: ${apiResult.error}")
throw Exception("Failed to fetch shipping rates: ${apiResult.error}")
}
}
} catch (error: Exception) {
println("❌ Failed to fetch shipping rates: $error")
throw error
}
}

companion object {
const val backendUrl = "https://stripe-mobile-payment-sheet.glitch.me"
}
Expand Down
48 changes: 48 additions & 0 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,14 @@ public final class com/stripe/android/paymentelement/confirmation/linkinline/Lin
public synthetic fun newArray (I)[Ljava/lang/Object;
}

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

public final class com/stripe/android/paymentelement/embedded/content/DefaultEmbeddedConfigurationHandler$Arguments$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/paymentelement/embedded/content/DefaultEmbeddedConfigurationHandler$Arguments;
Expand Down Expand Up @@ -3234,3 +3242,43 @@ public final class com/stripe/android/paymentsheet/verticalmode/ComposableSingle
public final fun getLambda-5$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
}

public final class com/stripe/android/shoppay/ComposableSingletons$ShopPayActivityKt {
public static final field INSTANCE Lcom/stripe/android/shoppay/ComposableSingletons$ShopPayActivityKt;
public fun <init> ()V
public final fun getLambda-1$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-2$paymentsheet_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$paymentsheet_release ()Lkotlin/jvm/functions/Function3;
}

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

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

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

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

1 change: 1 addition & 0 deletions paymentsheet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation libs.androidx.lifecycleCompose
implementation libs.androidx.savedState
implementation libs.androidx.viewModel
implementation("androidx.webkit:webkit:1.14.0")

// DI
implementation libs.dagger
Expand Down
Loading