Run the sample app
The SDK's repository ships a SwiftUI demo, Demo/Demo.xcodeproj, that exercises the SDK end to end: card, Apple Pay, PayPal, Klarna, redirection methods, gift cards and co-payment, saved cards, BNPL simulation, session refresh, teardown, and the confirmation of a payment after a redirect. It is the reference to read next to the guides.
It opens on a mode picker with two modes, built on one controller and one session-minting path:
| Mode | What it is |
|---|---|
| Playground | Session → pay → result. Raw session and transaction dumps, an event log, a widget.data paste box, a manual capture tool. For working on the SDK |
| Checkout | Basket → payment → confirmation. What a shopper sees, with the same SDK calls and none of the developer chrome |
Get it
git clone https://github.com/UpStreamPay/purse-headless-checkout-ios.git
cd purse-headless-checkout-ios
open Demo/Demo.xcodeproj
The demo depends on the SDK targets of the repository, so it always runs against the checked-out version; check out a release tag to match the version you depend on. The package targets iOS only: build for a simulator destination, never swift build.
Give it a session
The demo mints its sessions through alfred, the usp-widget development server that holds the server-side Purse credentials a real merchant backend would. ALFRED_BASE_URL in .env points at it; blank falls back to http://localhost:9001.
cd ../usp-widget && npm run dev:alfred # port 9001
cp .env.example .env # then set ALFRED_BASE_URL
source scripts/load-env.sh # must be sourced — see the script for why
localhost is correct here: the Simulator shares the host's network stack, where an Android emulator needs 10.0.2.2. A physical device needs your machine's LAN IP.
It does nothing outside Purse's network. Merchants create a session with their own sandbox credentials as in Your first payment with Purse, then paste the widget.data string into the playground's session screen. There is no offline path otherwise.
What each screen shows
| Screen | Exercises |
|---|---|
| Session (playground) | PurseHeadlessCheckout(widgetData:), the decoded session, the method list with each item's disabled state |
| Pay (playground) | One row per primary method and saved token, the payment element of the selected one, gift card entry with balance check and slices, the split and isPaymentFulfilled, setSession(_:) as "refresh session", BNPL simulation preview |
| Result (playground) | The four PaymentOutcomes, and a Get transactions call against alfred to settle an unconfirmed return |
| Basket → Payment → Confirmation (checkout) | The same SDK calls a merchant ships, ending on a confirmation screen that reads the session's transactions through alfred, polling while they are pending |
Both result screens read the session through alfred, which stands in for the merchant backend that holds server-side Purse credentials. Treat that part as backend code, not SDK surface.
Which methods appear
The session decides. The demo installs all three partner modules at start-up, so any method your entity offers is usable: a card vaulted by Purse, Apple Pay, PayPal, Klarna, any method configured in redirection mode, and every gift card issuer the SDK ships.
Apple Pay needs what this repository cannot ship: the In-App Payments entitlement alongside its merchant identifier, so it only works on a signed build and a real device. The button renders regardless and the failure is reported, which is the honest result. Klarna needs nothing from the app: its client token comes back in the /init answer, so activating Klarna on the entity is enough. Everything else depends only on what the session offers.
Where to look in the code
| File | Shows |
|---|---|
Demo/Sources/DemoController.swift | Building the checkout, installing the partner modules, selecting items, one listener per item, submitting, mapping outcomes, teardown() |
Demo/Sources/CheckoutPayScreen.swift | The shopper-facing pay screen: method rows, the selected element, gift cards, the split |
Demo/Sources/SimulationPreview.swift | A complete rendering of simulateLoan |
Demo/Sources/ConfirmationScreen.swift | The order confirmation, from the session's transactions read through alfred (DemoController.confirmPayment()) |
Demo/Sources/AlfredClient.swift | What a merchant backend does, and therefore what is not SDK surface |
Two launch arguments help smoke tests and are never on by default: -demo-autostart mints a session and opens the playground's pay screen, and -demo-checkout opens the shopper flow at its basket (with both, it mints and lands on the payment page). CI builds the demo in Debug and Release.