Android SDK · v1.0.0-beta01

MatchPass SDK

Add M-Pesa pay-per-view passes to any Android app in under an hour. One composable. Zero payment infrastructure. Users pay with their phone — your content unlocks instantly.


How it works

MatchPass sits between your content and your users. When a user taps locked content, show the Paywall composable. It handles everything — phone verification, M-Pesa STK push, pass issuing, and expiry. Your code only needs to respond to onAccessGranted.

User taps locked content
        │
        ▼
MatchPassSDK.Paywall()
        │
        ├─ Existing pass? ──────────────────────► onAccessGranted (silent)
        │
        ├─ Phone known? ─── Yes ───────────────► Confirm screen
        │                    │                         │
        │                    No                        ▼
        │                    │                  M-Pesa STK Push sent
        │                    ▼                         │
        │              Phone entry             User enters PIN on phone
        │                    │                         │
        │              OTP verification        Pass issued automatically
        │                                             │
        └─────────────────────────────────────────────▼
                                              onAccessGranted(grant)

Installation

1. Add JitPack to settings.gradle.kts

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

2. Add the dependency

// app/build.gradle.kts
dependencies {
    implementation("com.github.fatahrez:MatchPassAndroidSDK:1.0.0-beta01")
}
Note: Requires minSdk 24, compileSdk 35, and Jetpack Compose BOM 2024.09.00+.

Quick Start

1. Initialise once in Application.onCreate()

Get your API key from staging.matchpass.africa. Register as an operator — your key is generated instantly and shown once.

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        MatchPassSDK.init(
            context = this,
            config = MatchPassConfig(
                apiKey  = "YOUR_API_KEY",   // from staging.matchpass.africa
                baseUrl = "https://staging.api.matchpass.africa/api/v1/",
                debug   = BuildConfig.DEBUG,
            ),
        )
    }
}

2. Show the paywall when a user taps locked content

// In any composable — show when a user taps locked content
var showPaywall by remember { mutableStateOf(false) }

if (showPaywall) {
    MatchPassSDK.Paywall(
        content = MatchPassContent(
            id            = "arsenalmancity01jul26",
            title         = "Arsenal vs Manchester City",
            price         = "299",
            currency      = "KSh",
            durationHours = 4,
            contentType   = ContentType.MATCH,
        ),
        userPhone       = sessionPhone,          // null → SDK asks; set → skips OTP
        onAccessGranted = { grant ->
            showPaywall = false
            startStream(grant.token)             // pass token to your streaming backend
        },
        onDismiss = { showPaywall = false },
    )
}

3. Check access without UI (for lock icons)

// Use this to show a lock icon or play button on content cards — no UI shown
LaunchedEffect(content.id) {
    when (val result = MatchPassSDK.checkAccess(context, content)) {
        is AccessResult.Granted      -> showPlayButton(result.grant.token)
        is AccessResult.Expired      -> showRenewButton()
        is AccessResult.NotPurchased -> showLockIcon()
        is AccessResult.Error        -> showLockIcon()   // fail closed
    }
}

Content Types

Pass the right ContentType and the SDK picks the correct validation frequency, cache TTL, and pass label automatically.

ContentTypeUse caseCache TTLPass label
MATCHLive sport, single event5 minGame Pass
CHANNELLive channel, time-limited60 sChannel Pass
SEASONFull TV season, rewatch24 hSeason Pass
MOVIEMovie, own permanently30 daysOwn it

Content IDs

Content IDs are how MatchPass links a pass to a piece of content. You own the ID — pick something human-readable and date-stamped for live events so the same fixture in a future season gets a new ID and a fresh pass.

// ✅ Recommended — unique per fixture and date
id = "arsenalmancity01jul26"
id = "chiefspirates03jul26"
id = "boxing-riyadhseason05jul26"

// ✅ Stable IDs for channels and series (content repeats)
id = "ch-supersport1"
id = "series-shaka-ilembe-s1"

// ❌ Avoid — generic IDs cause pass collisions across seasons
id = "match-001"
Note: The backend creates the content record automatically on first purchase — you never need to pre-register content.

API Reference

MatchPassSDK

MethodReturnsDescription
Builder(context)BuilderFluent SDK initialiser. Call once from Application.onCreate().
Paywall(...)Unit (Composable)Show the full purchase flow. Handles phone, OTP, M-Pesa, and pass issuing.
Login(...)Unit (Composable)Standalone phone OTP screen. Call before browsing if your app requires login.
checkAccess(...)AccessResult (suspend)Validate access silently. Use for lock icons and stream gates.
setPhone(...)UnitPre-set a verified phone from your own auth — skips OTP in the paywall.
getStoredPhone(...)String?Read the verified phone stored on this device.
signOut(...)UnitClear the session. Does NOT revoke passes.
getExpiresAt(...)Long?Epoch-ms expiry for a content pass. Use for countdown timers.

MatchPassContent

MatchPassContent(
    id            = "arsenalmancity01jul26",  // your stable content identifier
    title         = "Arsenal vs Man City",
    price         = "299",                    // string, in the currency below
    currency      = "KSh",
    durationHours = 4,                        // how long the pass is valid after purchase
    contentType   = ContentType.MATCH,        // MATCH | CHANNEL | SEASON | MOVIE
)

AccessResult

SubtypeFieldsMeaning
Grantedgrant: MatchPassGrantValid pass — present grant.token to your streaming backend
ExpiredexpiredAt: StringPass found but expired — show renewal CTA
NotPurchasedNo pass exists — show paywall
Errorexception: ...Network or server error — fail closed (show lock)

Session management

// Read the stored phone (null if not logged in)
val phone: String? = MatchPassSDK.getStoredPhone(context)

// Clear session — does NOT revoke passes
MatchPassSDK.signOut(context)

// Get expiry epoch-ms for a specific piece of content (for countdown timers)
val expiresAt: Long? = MatchPassSDK.getExpiresAt(context, contentId)

Pre-set phone from your own auth

If your app has its own login system, pass the verified phone to MatchPass so the paywall skips its OTP step.

// After your own login succeeds, hand the verified phone to MatchPass
MatchPassSDK.setPhone(context, verifiedPhone)

// The next Paywall() call will skip OTP and go straight to M-Pesa

Pass Restore

When a user reinstalls or switches devices, the SDK automatically checks the server for an existing active pass before showing the paywall. If found, onAccessGrantedfires silently — no re-purchase required. This works automatically when the user's phone is known.


Debug & Local Dev

MatchPassSDK.init(
    context = this,
    config = MatchPassConfig(
        apiKey  = "your-staging-key",
        baseUrl = "http://10.0.2.2:8000/api/v1/",  // emulator → host machine localhost
        debug   = true,                              // OTP shown on-screen, verbose logs
    ),
)

In debug mode the OTP code is shown on-screen — no real SMS is sent. Add cleartext traffic config for emulator access:

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">10.0.2.2</domain>
    </domain-config>
</network-security-config>

Ready to integrate?

Register as an operator to get your API key and start issuing passes.

Create operator account →