Skip to content

stripe-samples/billing-ios-sdk

Repository files navigation

Stripe BillingSDK (iOS) — Experimental Preview

An iOS Swift Package that helps you sell and manage subscriptions and feature access (entitlements) with Stripe Billing.

  • Fetch and render Buy Buttons
  • Present the Customer Portal for subscription management
  • Read active entitlements and gate features
  • Local caching with refresh and offline fallback

For a detailed guide on how to use this, please visit the Stripe docs.

Disclaimer

  • This SDK is an experimental preview.
  • It requires your Stripe account to be opted in to private preview APIs.

Example backend and ExampleApp

  • example-backend/

    • Node.js Express server that exposes /authenticate.
    • Requires STRIPE_SECRET_KEY and STRIPE_CUSTOMER_ID in the environment.
    • Uses a private preview Stripe API version (for example 2025-07-30.basil).
    • Run:
      • cd example-backend
      • npm install
      • npm run dev
  • ExampleApp/

    • SwiftUI app showing end-to-end usage: Buy Button, Entitlements, and Customer Portal.
    • Update ExampleApp/Constant.swift with your values (stripePublishableKey, stripeCustomerId, stripeBuyButtonId, backend URL, and entitlement lookup keys).
    • Open ExampleApp.xcodeproj and run the app. Ensure the backend is running.

Installing the SDK

  • Open your project in Xcode
  • Select FileAdd Package Dependencies
  • Type https://github.com/stripe-samples/billing-ios-sdk into the Search or Enter Package URL field
  • Select BillingSDK and click Add Package

Core API

Initialize the SDK

import BillingSDK

let configuration = BillingSDK.Configuration(
  // Your Stripe publishable key
  publishableKey: "pk_test_…",
  maximumStaleEntitlementsDuration: TimeInterval(60 * 5)
)
let billing = BillingSDK(configuration: configuration)

Provide a customer session provider

  • Called by the SDK when it needs to authenticate.
  • Return nil when the user isn’t logged in; return your backend payload when they are.
billing.setCustomerSessionProvider {
  // Call your backend (example uses POST /authenticate) and return a UBCustomerSessionDetails
  // The endpoint path and auth are up to you; the app provides a closure that calls it.
  return UBCustomerSessionDetails(
      customer: "customer_id",
      clientSecret: "client_secret",
      expiresAt: Date()
  )
}

Buy Button

let buyButton = try await billing.getBuyButton(id: "buy_btn_…")
// SwiftUI
buyButton.view()
// You can also render your own buy button based on the BuyButton data model.

Entitlements

let entitlements = try await billing.getActiveEntitlements(forceRefresh: true)
let hasPremium = try await billing.hasEntitlement(lookupKey: "premium")

billing.onEntitlementsChanged { updated in
  // Update UI as entitlements change
}

Customer Portal

let portal = try await billing.getCustomerPortal()
// UIKit: present in-app Safari (preferred)
portal.presentCustomerPortal(from: presentingViewController)
// Or open externally
portal.redirectToCustomerPortal()

Behavior notes:

  • If no session is present, getActiveEntitlements returns an empty array.
  • Buy button can be rendered without a session, this will result in a new Stripe Customer getting created.
  • Customer Portal require a session and will error if unauthenticated.

Reset

To clear the cache and reset the SDK to its initial state, call .reset():

await billing.reset()

Backend

Backend responsibilities

Your backend should:

  1. Verify the user is authenticated
  2. Create or retrieve the Stripe Customer ID for the user
  3. Create a Customer Session with the required components enabled
  4. Return the session details to your app

For example:

import 'dotenv/config';
import express from 'express';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
  apiVersion: '2025-07-30.basil',
});

const app = express();
app.use(express.json());

app.post('/authenticate', async (req, res) => {
  try {
    // Replace with your auth; return 401 if the user is not logged in
    const hasSessionPresent = req.body.hasSessionPresent !== false;
    if (!hasSessionPresent) {
      res.status(401).json({ error: { type: 'no_session_present' } });
      return;
    }

    const customerSession = await stripe.customerSessions.create({
      customer: process.env.STRIPE_CUSTOMER_ID || '',
      components: {
        buy_button: { enabled: true },
        active_entitlements: { enabled: true },
        customer_portal: { enabled: true },
      } as any,
    });

    res.json({
      clientSecret: customerSession.client_secret,
      expiresAt: customerSession.expires_at, // seconds since epoch
      customer: customerSession.customer as string,
    });
  } catch (err) {
    res.status(500).json({ error: { message: 'Internal server error' } });
  }
});

app.listen(3000);

About

Billing iOS SDK and Example App

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages