Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Build a subscriptions integration


Build a subscriptions integration

Create and manage subscriptions to accept recurring payments.

Checkout

Elements

Mobile

Learn how to sell fixed-price subscriptions. You’ll use the Mobile Payment Element to create a custom payment form that you embed in your app.

Note

If you’re selling digital products or services that are consumed within your app (for example, subscriptions, in-game currencies, game levels, access to premium content, or unlocking a full version), you must use Apple’s in-app purchase APIs. This rule has some exceptions, including one-to-one personal services and apps based in specific regions. See the App Store review guidelines for more information.

Build your subscription

This guide shows you how to:

  • Model your business by building a product catalog.
  • Create a registration process to add customers.
  • Create subscriptions and collect payment information.
  • Test and monitor the status of payments and subscriptions.
  • Let customers change their plan or cancel the subscription.
  • Learn how to use flexible billing mode to access enhanced billing behavior and additional features.

How to model it on Stripe

Subscriptions simplify your billing by automatically creating Invoices and PaymentIntents for you. To create and activate a subscription, you need to first create a Product to model what’s being sold, and a Price which determines the interval and amount to charge. You also need either a customer-configured Account object or a Customer object to store PaymentMethods used to make each recurring payment.

API object definitions

Set up Stripe

The Stripe Android SDK is open source and fully documented.

To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

build.gradle.kts

Select a language

Kotlin

Groovy

No results

plugins {
 id("com.android.application")
}

android { ... }

dependencies {
 // ...

 // Stripe Android SDK
 implementation("com.stripe:stripe-android:23.20.0")
 // Include the financial connections SDK to support US bank account as a payment method
 implementation("com.stripe:financial-connections:23.20.0")
}

Note

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

Select a language

Kotlin

Java

No results

import com.stripe.android.PaymentConfiguration

class MyApp : Application() {
 override fun onCreate() {
 super.onCreate()
 PaymentConfiguration.init(
 applicationContext,
 "pk_test_TYooMQauvdEDq54NiTphI7jx"
 )
 }
}

Note

Use your test keys while you test and develop, and your live mode keys when you publish your app.

And then install the Stripe CLI. The CLI provides webhook testing and you can run it to make API calls to Stripe. This guide shows how to use the CLI to set up a pricing model in a later section.

Install the Stripe CLI with npm:

Command Line

npm install -g @stripe/cli@latest

After installation, log in to your Stripe account:

Command Line

stripe login

After you install the CLI, you can also install agent tooling, or set up autocompletion.

Note

For more installation options for Windows, macOS, Linux, and Docker, see the Stripe CLI readme on GitHub.

Create the pricing model Stripe CLI or Dashboard

Recurring pricing models represent the products or services you sell, how much they cost, what currency you accept for payments, and the service period for subscriptions. To build the pricing model, create products (what you sell) and prices (how much and how often to charge for your products).

This example uses flat-rate pricing with two different service-level options: Basic and Premium. For each service-level option, you need to create a product and a recurring price. To add a one-time charge for something like a setup fee, create a third product with a one-time price.

Each product bills at monthly intervals. The price for the Basic product is 5 USD. The price for the Premium product is 15 USD. See the flat rate pricing guide for an example with three tiers.

Go to the Add a product page and create two products. Add one price for each product, each with a monthly recurring billing period:

  • Premium product: Premium service with extra features
  • Price: Flat rate | 15 USD
  • Basic product: Basic service with minimum features
  • Price: Flat rate | 5 USD

After you create the prices, record the price IDs so you can use them in other steps. Price IDs look like this: price_G0FvDp6vZvdwRZ.

When you’re ready, use the Copy to live mode button at the top right of the page to clone your product from a sandbox to live mode.

Create the customer Client and Server

Stripe needs a customer for each subscription. In your application front end, collect any necessary information from your users and pass it to the backend.

You might want to use a Network library to send network requests to your backend. This document uses okhttp, but you can use any library that work best in your project.

build.gradle

dependencies {
 ...
 implementation "com.squareup.okhttp3:okhttp:4.12.0"
}

If you need to collect address details, the Address Element enables you to collect a shipping or billing address for your customers. For more information on the Address Element, visit the Address Element page.

RegisterView.kt

import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject

@Composable
fun RegisterView() {
 var email by remember { mutableStateOf("") }
 Column {
 OutlinedTextField(value = email,
 label = { Text(text = "Email") },
 onValueChange = { email = it })
 Button(onClick = {
 val body = JSONObject().put("email", email).toString()
 .toRequestBody("application/json".toMediaType())
 val request =
 Request.Builder().url("http://10.0.2.2:4567/create-customer").post(body).build()
 CoroutineScope(Dispatchers.IO).launch {
 OkHttpClient().newCall(request).execute().use { response ->
 if (response.isSuccessful) {
 println(JSONObject(response.body!!.string()).get("customer"))
 }
 }
 }
 }) {
 Text(text = "Submit")
 }
 }
}

On the server, create the Stripe customer object.

Use the Accounts v2 API to represent customers

The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.

To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.

For most use cases, we recommend modeling your customers as customer-configured Account objects instead of using Customer objects.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Create the subscription Client and Server

Note

If you want to render the Payment Element without first creating a subscription, see Collect payment details before creating an Intent.

Let your new customer choose a plan and then create the subscription—in this guide, they choose between Basic and Premium.

In your app, pass the selected price ID and the ID of the customer record to the backend.

PricesView.kt

import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject

fun createSubscription(priceId: String, customerAccountId: String): SubscriptionResponse? {
 val body = JSONObject()
 .put("priceId", priceId)
 .put("customerAccountId", customerAccountId).toString()
 .toRequestBody("application/json".toMediaType())
 val request =
 Request.Builder().url("http://10.0.2.2:4567/create-subscription").post(body).build()
 OkHttpClient().newCall(request).execute().use { response ->
 if (response.isSuccessful) {
 // SubscriptionsResponse is data class conforming to the expected response from your backend.
 // It should include the client_secret, as discussed below.
 return Gson().fromJson(response.body!!.string(), SubscriptionResponse::class.java)
 }
 }
 return null
}

On the backend, create the subscription with status incomplete using payment_behavior=default_incomplete. Then return the client_secret from the subscription’s first payment intent to the frontend to complete payment by expanding the confirmation_secret on the latest invoice of the subscription.

To enable improved subscription behavior, set billing_mode[type] to flexible. You must use Stripe API version 2025-06-30.basil or later.

Set save_default_payment_method to on_subscription to save the payment method as the default for a subscription when a payment succeeds. Saving a default payment method increases the success rate of future subscription payments.

The following example creates a Subscription and expands the confirmation_secret from its latest invoice in the response. After the subscription is created, read subscription.latest_invoice.confirmation_secret.client_secret and return that value to the front end to confirm the payment.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Note

If you’re using a multi-currency Price, use the currency parameter to tell the Subscription which of the Price’s currencies to use. (If you omit the currency parameter, then the Subscription uses the Price’s default currency.)

The Subscription is now inactive and awaiting payment. The following example response highlights the minimum fields to store, but you can store whatever your application frequently accesses.

{
 "id": "sub_JgRjFjhKbtD2qz",
 "object": "subscription",
 "application_fee_percent": null,
 "automatic_tax": {
 "disabled_reason": null,
 "enabled": false,
 "liability": "null"
 },
 "billing_cycle_anchor": 1623873347,

Update your server endpoint

Add ephemeral key creation to the subscription endpoint and return it in the response:

Select a language

Ruby

Python

Node.js

PHP

Java

Go

.NET

No results

ephemeral_key = Stripe::EphemeralKey.create(
 {customer_account: customer_account_id},
 {stripe_version: '2026-03-25.dahlia'}
)

{
 subscriptionId: subscription.id,
 clientSecret: subscription.latest_invoice.confirmation_secret.client_secret,
 ephemeralKey: ephemeral_key.secret,
 customerAccountId: customer_account_id,
}.to_json

Update your response model

data class SubscriptionsResponse(
 val subscriptionId: String,
 val clientSecret: String,
 val ephemeralKey: String,
 val customerAccountId: String
)

Pass the customer configuration to PaymentSheet

Add the following when configuring PaymentSheet:

PaymentSheet.Configuration(
 primaryButtonLabel = "Subscribe for $15/month",
 merchantDisplayName = "My merchant name",
 customerAccount = PaymentSheet.CustomerConfiguration(
 id = customerAccountId,
 ephemeralKeySecret = ephemeralKey
 )
)

Collect payment information Client

Use the Payment Sheet to collect payment details and activate the subscription. You can customize Elements to match the look and feel of your application.

The Payment Sheet securely collects all necessary payment details for a wide variety of payments methods. Learn about the supported payment methods for Payment Sheet and Subscriptions.

Add the Payment Element to your app

Note

This step shows one way to get started, but you can use any in-app payments integration.

Initialize and present the Mobile Payment Element using the PaymentSheet class.

SubscribeView.kt

import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult
import com.stripe.android.paymentsheet.rememberPaymentSheet

@Composable
fun SubscribeView(clientSecret: String) {
 val paymentSheet = rememberPaymentSheet(::onPaymentSheetResult)

 Button(onClick = {
 paymentSheet.presentWithPaymentIntent(
 clientSecret, PaymentSheet.Configuration(
 primaryButtonLabel = "Subscribe for $15/month",
 merchantDisplayName = "My merchant name",
 // Set `allowsDelayedPaymentMethods` to true if your business handles
 // delayed notification payment methods like US bank accounts.
 allowsDelayedPaymentMethods = true
 )
 )
 }) {
 Text(text = "Subscribe")
 }
}

fun onPaymentSheetResult(paymentSheetResult: PaymentSheetResult) {
 when (paymentSheetResult) {
 is PaymentSheetResult.Canceled -> {
 print("Canceled")
 }

 is PaymentSheetResult.Failed -> {
 print("Error: ${paymentSheetResult.error}")
 }

 is PaymentSheetResult.Completed -> {
 // Display for example, an order confirmation screen
 print("Completed")
 }
 }
}

The Mobile Payment Element renders a sheet that allows your customer to select a payment method. The form automatically collects all necessary payments details for the payment method that they select.

Setting allowsDelayedPaymentMethods to true allows delayed notification payment methods like US bank accounts. For these payment methods, the final payment status isn’t known when the PaymentSheet completes, and instead succeeds or fails later. If you support these types of payment methods, inform the customer their order is confirmed and only fulfill their order (for example, ship their product) when the payment is successful.

You can customize the Payment Element to match the design of your app by using the appearance property your PaymentSheet.Configuration object.

Confirm payment

The Mobile Payment Element creates a PaymentMethod and confirms the incomplete Subscription’s first PaymentIntent, causing a charge to be made. If Strong Customer Authentication (SCA) is required for the payment, the Payment Element handles the authentication process before confirming the PaymentIntent.

Listen for webhooks Server

To complete the integration, you need to process webhooks sent by Stripe. These are events triggered whenever state inside of Stripe changes, such as subscriptions creating new invoices. In your application, set up an HTTP handler to accept a POST request containing the webhook event, and verify the signature of the event:

server.rb

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

During development, use the Stripe CLI to observe webhooks and forward them to your application. Run the following in a new terminal while your development app is running:

Command Line

stripe listen --forward-to localhost:4242/webhook

For production, set up a webhook endpoint URL in the Dashboard, or use the Webhook Endpoints API.

You need to listen to a few events to complete the remaining steps in this guide. See Subscription events for more details about subscription-specific webhooks.

Provision access to your service Client and Server

Now that the subscription is active, give your user access to your service. To do this, listen to the customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted events. These events pass a subscription object which contains a status field indicating whether the subscription is active, past due, or canceled. See the subscription lifecycle for a complete list of statuses.

In your webhook handler:

  1. Verify the subscription status. If it’s active then your user has paid for your product.
  2. Check the product the customer subscribed to and grant access to your service. Checking the product instead of the price gives you more flexibility if you need to change the pricing or billing interval.
  3. Store the product. id , subscription. id and subscription. status in your database along with the customer. id you already saved. Check this record when determining which features to enable for the user in your application.

The state of a subscription might change at any point during its lifetime, even if your application doesn’t directly make any calls to Stripe. For example, a renewal might fail due to an expired credit card, which puts the subscription into a past due state. Or, if you implement the customer portal, a user might cancel their subscription without directly visiting your application. Implementing your handler correctly keeps your application state in sync with Stripe.

Cancel the subscription Client and Server

It’s common to allow customers to cancel their subscriptions. This example adds a cancellation option to the account settings page.

The example collects the subscription ID on the frontend, but your application can get this information from your database for your logged in user.

Account settings with the ability to cancel the subscription

SubscriptionView.kt

fun cancelSubscription(subscriptionId: String): SubscriptionResponse? {
 val body = JSONObject().put("subscriptionId", subscriptionId).toString()
 .toRequestBody("application/json".toMediaType())
 val request =
 Request.Builder().url("http://10.0.2.2:4567/cancel-subscription").post(body).build()
 OkHttpClient().newCall(request).execute().use { response ->
 if (response.isSuccessful) {
 return Gson().fromJson(response.body!!.string(), SubscriptionResponse::class.java)
 }
 }
 return null
}

On the backend, define the endpoint for your app to call.

server.rb

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')

post '/cancel-subscription' do
 content_type 'application/json'
 data = JSON.parse request.body.read

 deleted_subscription = client.v1.subscriptions.cancel(data['subscriptionId'])

 deleted_subscription.to_json
end

Your backend receives a customer.subscription.deleted event.

After the subscription is canceled, update your database to remove the Stripe subscription ID you previously stored, and limit access to your service.

When a subscription is canceled, it can’t be reactivated. Instead, collect updated billing information from your customer, update their default payment method, and create a new subscription with their existing customer record.

Test your integration

Test payment methods

Use the following table to test different payment methods and scenarios.

Payment methodScenarioHow to test
the related setting Direct DebitYour customer successfully pays with the related setting Direct Debit.Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
the related setting Direct DebitYour customer’s payment fails with an account_closed error code.Fill out the form using the account number 111111113 and BSB 000000.
Credit cardThe card payment succeeds and doesn’t require authentication.Fill out the credit card form using the credit card number 4242 4242 4242 4242 with any expiration, CVC, and postal code.
Credit cardThe card payment requires authentication.Fill out the credit card form using the credit card number 4000 0025 0000 3155 with any expiration, CVC, and postal code.
Credit cardThe card is declined with a decline code like insufficient_funds.Fill out the credit card form using the credit card number 4000 0000 0000 9995 with any expiration, CVC, and postal code.
the related setting Direct DebitYour customer successfully pays with the related setting Direct Debit.Fill out the form using the account number the related setting. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
the related setting Direct DebitYour customer’s PaymentIntent status transitions from processing to requires_payment_method.Fill out the form using the account number the related setting.

Monitor events

Set up webhooks to listen to subscription change events, such as upgrades and cancellations. Learn more about subscription webhooks. You can view events in the Dashboard or with the Stripe CLI.

For more details, see testing your Billing integration.

Optional Let customers change their plans Client and Server

Optional Preview a price change Client and Server

Optional Display the customer payment method Client and Server

Disclose Stripe to your customers

Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You’re responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.

Last verified 2026-09-24

Is this helpful?