McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Build Paystack Subscriptions in iOS with Swift
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build Paystack Subscriptions in iOS with Swift

Subscribe a user by calling your backend to initialize a transaction with a Paystack plan code. Open the authorization URL in WKWebView. After checkout, Paystack creates the subscription and your backend receives the webhook. Check subscription status from iOS by calling your backend API — not Paystack directly.

In this article

  1. 1. Important: App Store Guidelines
  2. 2. Subscription Model in Swift
  3. 3. SwiftUI Subscription View
  4. 4. Learn More

Important: App Store Guidelines

If you distribute on the App Store and sell digital subscriptions consumed within the app, Apple requires in-app purchases via StoreKit. Paystack subscriptions are appropriate for:

  • Enterprise/B2B apps distributed outside the App Store
  • Apps with subscriptions for physical goods or services
  • TestFlight builds and internal distribution
  • Web-based SaaS where iOS is a companion app

Check the current App Store Review Guidelines before launching.

Subscription Model in Swift

// Models/SubscriptionStatus.swift
struct SubscriptionStatus: Codable {
    let active: Bool
    let planName: String?
    let nextBillingDate: String?
    let subscriptionCode: String?
    let emailToken: String?

    enum CodingKeys: String, CodingKey {
        case active
        case planName = "plan_name"
        case nextBillingDate = "next_billing_date"
        case subscriptionCode = "subscription_code"
        case emailToken = "email_token"
    }
}

// SubscriptionService.swift
extension PaymentService {
    func getSubscriptionStatus(email: String) async throws -> SubscriptionStatus {
        let encodedEmail = email.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? email
        guard let url = URL(string: "(baseURL)/api/subscription/status?email=(encodedEmail)") else {
            throw URLError(.badURL)
        }
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(SubscriptionStatus.self, from: data)
    }

    func cancelSubscription(code: String, token: String) async throws {
        guard let url = URL(string: "(baseURL)/api/subscription/cancel") else {
            throw URLError(.badURL)
        }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode([
            "subscription_code": code,
            "email_token": token
        ])
        let (_, _) = try await URLSession.shared.data(for: request)
    }
}

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

SwiftUI Subscription View

// SubscriptionView.swift
import SwiftUI

@MainActor
class SubscriptionViewModel: ObservableObject {
    @Published var status: SubscriptionStatus?
    @Published var loading = false
    @Published var error: String?

    func load(email: String) {
        loading = true
        Task {
            do {
                status = try await PaymentService.shared.getSubscriptionStatus(email: email)
            } catch {
                self.error = error.localizedDescription
            }
            loading = false
        }
    }

    func cancel() {
        guard let code = status?.subscriptionCode,
              let token = status?.emailToken else { return }
        loading = true
        Task {
            do {
                try await PaymentService.shared.cancelSubscription(code: code, token: token)
                status = SubscriptionStatus(active: false, planName: nil,
                                            nextBillingDate: nil,
                                            subscriptionCode: nil, emailToken: nil)
            } catch {
                self.error = "Cancellation failed: (error.localizedDescription)"
            }
            loading = false
        }
    }
}

struct SubscriptionView: View {
    let userEmail: String
    @StateObject private var viewModel = SubscriptionViewModel()

    var body: some View {
        Group {
            if viewModel.loading {
                ProgressView("Loading...")
            } else if let status = viewModel.status {
                if status.active {
                    VStack(alignment: .leading, spacing: 16) {
                        Label("Active Subscription", systemImage: "checkmark.seal.fill")
                            .font(.headline)
                            .foregroundColor(.green)

                        if let planName = status.planName {
                            Text("Plan: (planName)")
                        }
                        if let nextDate = status.nextBillingDate {
                            Text("Next billing: (nextDate)")
                                .foregroundColor(.secondary)
                        }

                        Button("Cancel Subscription", role: .destructive) {
                            viewModel.cancel()
                        }
                        .padding(.top)
                    }
                    .padding()
                } else {
                    VStack(spacing: 16) {
                        Text("No active subscription")
                        NavigationLink("Choose a Plan") {
                            PlanSelectionView(userEmail: userEmail)
                        }
                        .buttonStyle(.borderedProminent)
                    }
                }
            }
        }
        .onAppear { viewModel.load(email: userEmail) }
        .alert("Error", isPresented: Binding(
            get: { viewModel.error != nil },
            set: { if !$0 { viewModel.error = nil } }
        )) {
            Button("OK", role: .cancel) {}
        } message: {
            Text(viewModel.error ?? "")
        }
    }
}

Learn More

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Subscription checkout in iOS uses the same WKWebView flow as one-time payment with a plan code on the backend.
  • ✓After checkout, Paystack manages renewals automatically. iOS does not trigger billing.
  • ✓Check subscription status via your backend. Display plan name, next billing date, and status in SwiftUI.
  • ✓Use @EnvironmentObject or @StateObject for subscription state that needs to be shared across views.
  • ✓Remember App Store guidelines: selling digital content through an iOS app requires using StoreKit/IAP unless exempt.
  • ✓Paystack subscriptions are ideal for B2B apps, enterprise apps, and services delivered outside the App Store.

Frequently Asked Questions

How does the iOS app know when a subscription is active?
The backend receives the subscription.create webhook and marks the user as subscribed. The iOS app calls a backend status endpoint on app launch and after checkout. Use @StateObject with async/await to load and observe the subscription state in your SwiftUI views.
Can I use Paystack subscriptions if my iOS app is on the App Store?
Only if the subscription is for a physical service or an external platform. Apple requires StoreKit/IAP for digital content subscriptions within App Store apps. For B2B apps, enterprise distribution, or companion apps for web services, Paystack may be appropriate.
How do I implement a paywall in SwiftUI with Paystack?
Create a SubscriptionViewModel that loads status from your backend. In your premium feature views, check viewModel.status?.active. If false, show a PlanSelectionView that starts the subscription checkout flow. Use @EnvironmentObject to share the subscription status across the app without passing it manually.
How do I cancel a Paystack subscription from iOS?
Call your backend cancel endpoint, which calls POST /subscription/disable on Paystack. You need the subscription_code and email_token from the subscription.create webhook. Store these on your backend and expose them in the subscription status API.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in iOS with Swift

Accept Paystack payments in a native iOS app with Swift. This guide covers backend-driven transaction initialization, loading the Paystack checkout in WKWebView, detecting the callback redirect, and integrating with SwiftUI.

Read more

Handle Paystack Webhooks in iOS with Swift

Paystack webhooks go to your server. This guide explains building the backend webhook handler and delivering payment events to an iOS Swift app via Apple Push Notification service (APNs) or Firebase Cloud Messaging.

Read more

Handling subscription.create, subscription.disable and invoice Events

A complete walkthrough of every Paystack subscription and invoice webhook event. Covers subscription.create, subscription.not_renew, subscription.disable, invoice.create, invoice.payment_failed, and invoice.update with production-ready Node.js handlers.

Read more

Paystack Subscription Not Charging

Your Paystack subscription exists but recurring charges are not happening. The subscription might be disabled, the authorization code might not be reusable, the card might have expired, the plan amount might have changed, or the subscription might be in arrears. This guide walks through every cause with a systematic debug checklist.

Read more

Paystack Card Not Supported for Recurring

When Paystack says a card is not supported for recurring charges, the card type or issuing bank blocks tokenization. This guide covers why it happens, which card types are affected, how to handle it gracefully, and alternative billing strategies.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.