Skip to content

FASSTAP / Worldline — Visual Architecture

Companion to 003-fasstap-configuration.md. Diagrams here render natively on GitHub via Mermaid.


1. System map — who talks to whom

flowchart LR
    subgraph Device["Android Device (applicationId io.sagio.merchant)"]
        UI["Settings UI<br/>SettingsScreen.kt"]
        VM["SettingsViewModel"]
        Repo["SettingsRepository<br/>EncryptedSharedPreferences"]
        Mgr["FasstapManager"]
        SDK["Soft Space MPOS SDK<br/>v3.6.5.0 (.aar)"]
        UI --> VM --> Repo --> Mgr --> SDK
    end

    subgraph Sagio["Sagio Backend (Hono on Cloudflare)"]
        Auth["/merchant-app/auth"]
        Cfg["/merchant-app/fasspay-config"]
        WLC["worldlineClient<br/>(future, server-only)"]
        ConfigObj["config.fasspay<br/>env + baked-in defaults"]
        Cfg --> ConfigObj
        Auth --> ConfigObj
        WLC --> ConfigObj
    end

    subgraph SoftSpace["Soft Space / Worldline Infrastructure"]
        Att["Attestation Host<br/>mpos-uat.fasspay.com:9001/v2"]
        Key["Keyloading Host<br/>mpos-uat.fasspay.com:9002/key/api/v3"]
        REST["Worldline REST<br/>(settlement, reporting)"]
    end

    Repo -- "GET fasspay-config<br/>(Bearer token)" --> Cfg
    Repo -- "POST auth<br/>(API key + secret)" --> Auth
    SDK -- "attest + token refresh" --> Att
    SDK -- "key loading" --> Key
    WLC -- "X-SSO-ID header" --> REST

Key boundary: the Android device never talks to Worldline REST directly. The phone only authenticates to Soft Space via the SDK using libAccessKey / libSecretKey. Direct Worldline REST calls (settlement, reporting) go from our backend with X-SSO-ID.


2. First-launch bootstrap

sequenceDiagram
    autonumber
    participant U as User
    participant App as Merchant App
    participant BC as BuildConfig
    participant API as Sagio API
    participant Cfg as config.fasspay
    participant Prefs as EncryptedSharedPrefs
    participant SDK as Soft Space SDK

    U->>App: Launch app
    App->>BC: read SAGIO_API_KEY / SAGIO_API_SECRET
    Note over BC: Injected at build time via<br/>local.properties or env vars
    App->>API: POST /merchant-app/auth<br/>{ apiKey, apiSecret, deviceId }
    API-->>App: { accessToken, refreshToken, merchantId }
    App->>API: GET /merchant-app/fasspay-config<br/>Authorization: Bearer ...
    API->>Cfg: read merged env + defaults
    Cfg-->>API: 11 fields (host, cert, IDs, env)
    API-->>App: { ...11 fields }
    App->>Prefs: encrypt + persist MerchantConfig
    App->>SDK: SSMPOSSDK.init(config)
    SDK->>SDK: setLibAccessKey / SecretKey / UniqueID / DeveloperID +<br/>setAttestationHost / setKeyloadingHost / ...
    SDK-->>App: ready (sdkVersion, cotsId)

If /fasspay-config rejects with "Fasstap configuration missing: uniqueId, developerId", the API operator is missing one of the four required env vars. The UI shows MISSING on the Worldline row.


3. Where each of the 11 SDK fields comes from

flowchart TD
    classDef required fill:#fee,stroke:#c33,stroke-width:2px
    classDef baked fill:#efe,stroke:#3a3
    classDef envOverride fill:#eef,stroke:#33c

    EnvVars["apps/api/.env.local<br/>or secrets manager"]:::required
    Defaults["Baked into config/index.ts<br/>(from SDK demo v3.6.5.0)"]:::baked

    EnvVars -- "ACCESS_KEY" --> AK[accessKey]:::required
    EnvVars -- "SECRET_KEY" --> SK[secretKey]:::required
    EnvVars -- "UNIQUE_ID" --> UID[uniqueId]:::required
    EnvVars -- "DEVELOPER_ID" --> DID[developerId]:::required
    EnvVars -- "ENVIRONMENT (default UAT)" --> ENV[environment]:::envOverride

    Defaults -- "UAT host" --> AH[attestationHost<br/>mpos-uat.fasspay.com:9001/v2]:::baked
    Defaults -- "UAT host" --> KH[keyloadingHost<br/>mpos-uat.fasspay.com:9002/key/api/v3]:::baked
    Defaults -- "sha256 pin" --> ACP[attestationCertPinning]:::baked
    Defaults -- "sha256 pin (same)" --> KCP[keyloadingCertPinning]:::baked
    Defaults -- "SSPaymentCA PEM" --> CA[keyloadingCACert]:::baked
    Defaults -- "Soft Space's project<br/>757874674469" --> GP[googlePlayProjectNumber]:::baked

    AK & SK & UID & DID & ENV & AH & KH & ACP & KCP & CA & GP --> Resp["GET /merchant-app/fasspay-config<br/>response payload"]
    Resp --> SDKBuilder["SSMPOSSDKConfiguration.Builder"]

    SSO[ssoId<br/>FASSTAP_SSO_ID]:::envOverride -.->|backend-only,<br/>not in response| WLREST["Worldline REST<br/>X-SSO-ID header"]

Legend: - 🔴 Required — must be set as env var; endpoint rejects if blank. - 🟢 Baked-in default — works out of the box for UAT; override via env var if Soft Space rotates. - 🔵 Override — has a sensible default; env var only needed for non-UAT or non-default behavior.


4. Settings UI states

stateDiagram-v2
    [*] --> Loading: App opens Settings
    Loading --> Missing: BuildConfig keys empty<br/>OR API unreachable
    Loading --> Ready: fasspay-config returned all required
    Loading --> Partial: API returned but missing<br/>uniqueId / developerId

    Missing --> Refreshing: tap "Refresh SDK Credentials"
    Partial --> Refreshing: tap "Refresh SDK Credentials"
    Ready --> Refreshing: tap "Refresh SDK Credentials"

    Refreshing --> Ready: 200 + all required present
    Refreshing --> Partial: 500 "missing: uniqueId, developerId"
    Refreshing --> Missing: 401 / network error

    state Ready {
        [*] --> ShowEnv
        ShowEnv: "Environment: UAT" displayed
    }

    state Missing {
        [*] --> ShowError
        ShowError: "Not loaded yet" + MISSING badge
    }

The merchant only ever interacts with two controls: Refresh and (optionally) the Sagio API base URL field. They never type Worldline values.


5. Transaction flow (steady state)

sequenceDiagram
    autonumber
    participant Cust as Customer Card
    participant Mer as Merchant App
    participant SDK as Soft Space SDK
    participant Att as Attestation Host
    participant Key as Keyloading Host
    participant MPOS as MPOS Host

    Mer->>SDK: startTransaction(amount, ref)
    SDK->>Att: ensure fresh attestation token
    Att-->>SDK: token (refreshed every 5min)
    SDK->>Key: load DUKPT working keys
    Key-->>SDK: keys
    SDK-->>Mer: state = WaitingForCard
    Cust->>SDK: NFC tap
    SDK-->>Mer: state = CardDetected → CardReadOk
    SDK->>MPOS: authorize (encrypted PAN, amount)
    MPOS-->>SDK: outcome (approved / declined)
    SDK-->>Mer: state = Success(outcome) / Failed

Our app code in FasstapManager.kt:132-184 (startTransaction) only observes the MPOSTransaction.TransactionEvents callbacks — the attestation refresh, key loading, and authorization roundtrips are managed entirely inside the SDK.


6. Build & deploy topology

flowchart LR
    subgraph Dev["Local Dev"]
        LP["apps/sagio/local.properties<br/>SAGIO_API_KEY=...<br/>SAGIO_API_SECRET=..."]
        EnvLocal["apps/api/.env.local<br/>FASSTAP_*"]
    end

    subgraph CI["CI / Release"]
        Secrets["CI secrets store"]
        GradleBuild["./gradlew assembleRelease"]
        ApiDeploy["wrangler deploy"]
    end

    subgraph Runtime["Runtime"]
        APK["Signed APK<br/>BuildConfig.SAGIO_API_KEY baked in"]
        Worker["Cloudflare Worker<br/>process.env.FASSTAP_*"]
    end

    LP -. local build .-> APK
    Secrets -- env vars --> GradleBuild --> APK
    EnvLocal -. local dev .-> Worker
    Secrets -- env vars --> ApiDeploy --> Worker

    APK -- HTTPS --> Worker

Two independent credential pipelines: - Merchant app side: Sagio API key/secret → Gradle local.properties or CI env → BuildConfig → baked into APK - API side: FASSTAP_* env vars → process.envconfig.fasspay → response payload

Neither path requires merchant-side data entry.


7. What lives where in the repo

Concern File
Baked-in UAT defaults apps/api/src/config/index.ts (fasspay block)
Merchant-app config endpoint apps/api/src/controllers/merchantAppController.tsgetFasstapConfig
Mint Sagio API key/secret apps/api/src/scripts/mintMerchantKey.ts
Android applicationId (Soft Space-whitelisted) apps/sagio/app/build.gradle.kts:16
BuildConfig wiring for Sagio API creds apps/sagio/app/build.gradle.kts:33-34
Encrypted on-device persistence apps/sagio/app/src/main/java/io/sagio/merchant/data/SettingsRepository.kt
SDK config builder call apps/sagio/app/src/main/java/io/sagio/merchant/sdk/FasstapManager.kt (initialize)
Settings screen (status + refresh only) apps/sagio/app/src/main/java/io/sagio/merchant/ui/screens/SettingsScreen.kt
SDK binaries apps/sagio/app/libs/ssmobile-*.aar
TypeScript SDK wrapper (server-side) packages/worldline-sdk/src/