Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stripe Payment API integration to Jetpack Compose

I can't figure it out, how to impalement Stripe API integration into an Compose app

Here is Stripe provided snippet of code

    class CheckoutActivity : AppCompatActivity() {
  lateinit var paymentSheet: PaymentSheet

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    paymentSheet = PaymentSheet(this, ::onPaymentSheetResult)
  }

  fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
    // implemented in the next steps
  }
}

In my case I am out of ideas where to put paymentSheet = PaymentSheet(this, ::onPaymentSheetResult) in compose code as it shows that: None of the following functions can be called with the arguments supplied.

(ComponentActivity, PaymentSheetResultCallback) defined in com.stripe.android.paymentsheet.PaymentSheet

(Fragment, PaymentSheetResultCallback) defined in com.stripe.android.paymentsheet.PaymentSheet

class MainActivity : ComponentActivity() {
    lateinit var paymentSheet: PaymentSheet
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PayTheme {
                LoginUi()
            }
        }

        fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
            // implemented in the next steps
        }
    }
}
like image 809
Tomee Avatar asked Sep 07 '25 21:09

Tomee


1 Answers

First of all, you can check the stripe compose sample on github stripe-android (ComposeExampleActivity.kt).

Add stripe dependency

implementation "com.stripe:stripe-android:20.17.0"

Initialize stripe PaymentConfiguration in the Application class

@HiltAndroidApp
class BookstoreApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        PaymentConfiguration.init(applicationContext, BuildConfig.STRIPE_PUBLISHABLE_KEY)
    }
}

Stripe provides many ways to implement payments in the app. Let's consider payment confirmation using PaymentSheetContract and PaymentLauncher.

Example #1: Confirm payment using PaymentSheetContract

In this case, we should use rememberLauncherForActivityResult() with PaymentSheetContract() to launch stripe payment form.

PaymentScreen.kt (Compose)

@Composable
fun PaymentScreen(
    viewModel: PaymentViewModel = hiltViewModel()
) {
    val stripeLauncher = rememberLauncherForActivityResult(
        contract = PaymentSheetContract(),
        onResult = {
            viewModel.handlePaymentResult(it)
        }
    )
    val clientSecret by viewModel.clientSecret.collectAsStateWithLifecycle()
    clientSecret?.let {
        val args = PaymentSheetContract.Args.createPaymentIntentArgs(it)
        stripeLauncher.launch(args)
        viewModel.onPaymentLaunched()
    }
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Button(
            onClick = {
                viewModel.makePayment()
            }
        ) {
            Text(text = "Confirm payment")
        }
    }
}

PaymentViewModel.kt

@HiltViewModel
class PaymentViewModel @Inject constructor(
    private val repository: PaymentRepository
) : ViewModel() {
    private val _clientSecret = MutableStateFlow<String?>(null)
    val clientSecret = _clientSecret.asStateFlow()

    fun makePayment() {
        val paymentIntent = repository.createPaymentIntent()
        _clientSecret.update { paymentIntent.clientSecret }
    }

    fun onPaymentLaunched() {
        _clientSecret.update { null }
    }

    fun handlePaymentResult(result: PaymentSheetResult) {
        when(result) {
            PaymentSheetResult.Canceled -> TODO()
            PaymentSheetResult.Completed -> TODO()
            is PaymentSheetResult.Failed -> TODO()
        }
    }
}

Example #2: Confirm payment using PaymentLauncher

In this case, we should use rememberLauncherForActivityResult() with PaymentSheetContract() to launch stripe payment form.

PaymentScreen.kt (Compose)

@Composable
fun PaymentScreen(
    viewModel: PaymentViewModel = hiltViewModel()
) {
    val paymentLauncher = rememberPaymentLauncher(viewModel::handlePaymentResult)
    val confirmPaymentParams by viewModel.confirmPaymentParams.collectAsStateWithLifecycle()
    confirmPaymentParams?.let { payment ->
        paymentLauncher.confirm(payment)
        viewModel.onPaymentLaunched()
    }
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Button(
            onClick = {
                viewModel.makePayment()
            }
        ) {
            Text(text = "Confirm payment")
        }
    }
}

@Composable
fun rememberPaymentLauncher(
    callback: PaymentLauncher.PaymentResultCallback
): PaymentLauncher {
    val config = PaymentConfiguration.getInstance(LocalContext.current)
    return PaymentLauncher.rememberLauncher(
        publishableKey = config.publishableKey,
        stripeAccountId = config.stripeAccountId,
        callback = callback
    )
}

PaymentViewModel.kt

@HiltViewModel
class PaymentViewModel @Inject constructor(
    private val repository: PaymentRepository
) : ViewModel() {
    private val _confirmPaymentParams = MutableStateFlow<ConfirmPaymentIntentParams?>(null)
    val confirmPaymentParams = _confirmPaymentParams.asStateFlow()

    fun makePayment() {
        val paymentIntent = repository.createPaymentIntent()
        // For example, pay with hardcoded test card
        val configuration = ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams(
            paymentMethodCreateParams = PaymentMethodCreateParams.create(
                card = PaymentMethodCreateParams.Card(
                    number = "4242424242424242",
                    expiryMonth = 1,
                    expiryYear = 24,
                    cvc = "111"
                )
            ),
            clientSecret = paymentIntent.clientSecret
        )
        _confirmPaymentParams.update { configuration }
    }

    fun onPaymentLaunched() {
        _confirmPaymentParams.update { null }
    }

    fun handlePaymentResult(result: PaymentResult) {
        when(result) {
            PaymentResult.Canceled -> TODO()
            PaymentResult.Completed -> TODO()
            is PaymentResult.Failed -> TODO()
        }
    }
}

Data layer

The functions described below should be implemented somewhere on the server side. So, the client should only request some data from payment intent (client_secret for example).

Please, read stripe Accept a payment doc to understand better.
You can also watch youtube video: How to integrate Stripe in Android Studio 2022.

PaymentRepository.kt

class PaymentRepository @Inject constructor(
    private val stripeApiService: StripeApiService,
    private val paymentDao: PaymentDao
) {
    /*
        Create customer before payment (attach to app user)
     */
    suspend fun createCustomer() = withContext(Dispatchers.IO) {
        val customer = stripeApiService.createCustomer()
        // save customer in the database or preferences
        // customerId required to confirm payment
        paymentDao.insertCustomer(customer)
    }

    suspend fun refreshCustomerEphemeralKey() = withContext(Dispatchers.IO) {
        val customer = paymentDao.getCustomer()
        val key = stripeApiService.createEphemeralKey(customer.customerId)
        paymentDao.insertEphemeralKey(key)
    }

    suspend fun createPaymentIntent() = withContext(Dispatchers.IO) {
        val customer = paymentDao.getCustomer()
        refreshCustomerEphemeralKey()
        val paymentIntent = stripeApiService.createPaymentIntent(
            customerId = customer.customerId,
            amount = 1000,
            currency = "usd", // or your currency
            autoPaymentMethodsEnable = true
        )
        return@withContext paymentIntent
    }
}

StripeApiService.kt

private const val SECRET = BuildConfig.STRIPE_SECRET_KEY

interface StripeApiService {

    @Headers(
        "Authorization: Bearer $SECRET",
        "Stripe-Version: 2022-08-01"
    )
    @POST("v1/customers")
    suspend fun createCustomer() : CustomerApiModel

    @Headers(
        "Authorization: Bearer $SECRET",
        "Stripe-Version: 2022-08-01"
    )
    @POST("v1/ephemeral_keys")
    suspend fun createEphemeralKey(
        @Query("customer") customerId: String
    ): EphemeralKeyApiModel

    @Headers(
        "Authorization: Bearer $SECRET"
    )
    @POST("v1/payment_intents")
    suspend fun createPaymentIntent(
        @Query("customer") customerId: String,
        @Query("amount") amount: Int,
        @Query("currency") currency: String,
        @Query("automatic_payment_methods[enabled]") autoPaymentMethodsEnable: Boolean,
    ): PaymentIntentApiModel

}
like image 108
lincollincol Avatar answered Sep 10 '25 06:09

lincollincol