Skip to main content

Getting started

Add the iOS Secure Fields SDK to your project and render a working card form.

Prerequisites

  • An Xcode project targeting iOS 15 or higher, Swift 5.9+
  • A Purse account with a tenant ID (available in your Purse dashboard)

Step 1 — Add the dependency

The SDK is distributed as a signed XCFramework via Swift Package Manager. No compilation, credentials, or extra tooling required.

In Xcode:

  1. File → Add Package Dependencies…
  2. Paste the repository URL:
    https://github.com/UpStreamPay/vault-ios.git
  3. Select Up to Next Major Version starting at 1.0.0, then click Add Package.
  4. When prompted, add PurseSecureFields to your app target.

In Package.swift:

dependencies: [
.package(url: "https://github.com/UpStreamPay/vault-ios.git", from: "1.0.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "PurseSecureFields", package: "vault-ios")
]
)
]

Check the releases page for the latest version. Each tagged release distributes a signed XCFramework.

Step 2 — Choose an environment

Pass a VaultEnvironment case for your deployment stage. The SDK resolves the vault gateway URL internally — no raw URL configuration is required in the host app, and the endpoint cannot be overridden from outside the SDK.

EnvironmentVaultEnvironment caseResolved vault URL
Sandbox (development).sandboxhttps://api.vault.purse-sandbox.com
Production.productionhttps://api.vault.purse-secure.com

For a release build, drive this from a build configuration flag:

#if DEBUG
let environment: VaultEnvironment = .sandbox
#else
let environment: VaultEnvironment = .production
#endif

Step 3 — Initialize the SDK

Create a SecureFieldsManager in your view controller and set yourself as the delegate.

import UIKit
import PurseSecureFields

class CheckoutViewController: UIViewController {

lazy var secureFields = SecureFieldsManager(
config: SecureFieldsConfig(
tenantId: "${TENANT_ID}",
environment: .sandbox // or .production
)
)

override func viewDidLoad() {
super.viewDidLoad()
secureFields.delegate = self
setupLayout()
}
}

SecureFieldsManager is not a view controller — it owns the card input views, which you embed into your own layout in the next step.

Production requirement: certificate pinning

Before going live, pin the vault server's public key to protect against MITM attacks from a rogue root CA (MDM profiles, enterprise proxies). See Security and compliance — Enable certificate pinning.

Optional: restrict brands and apply a style

SecureFieldsConfig(
tenantId: "${TENANT_ID}",
environment: .sandbox,
brands: [.visa, .mastercard, .carteBancaire],
style: SecureFieldsStyle(
font: .systemFont(ofSize: 16),
textColor: .label,
placeholderColor: .placeholderText
)
)

See Customize for the full styling and placeholder reference.

Step 4 — Embed the card fields

SecureFieldsManager exposes four UIView instances. Add them to your layout with Auto Layout.

import UIKit
import PurseSecureFields

class CheckoutViewController: UIViewController {

lazy var secureFields = SecureFieldsManager(
config: SecureFieldsConfig(
tenantId: "${TENANT_ID}",
environment: .sandbox
)
)

override func viewDidLoad() {
super.viewDidLoad()
secureFields.delegate = self
setupLayout()
}

private func setupLayout() {
let pan = secureFields.panContainer
let expDate = secureFields.expDateView
let cvv = secureFields.cvvView
let holder = secureFields.holderNameView

[pan, expDate, cvv, holder].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}

NSLayoutConstraint.activate([
pan.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
pan.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
pan.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
pan.heightAnchor.constraint(equalToConstant: 48),

expDate.topAnchor.constraint(equalTo: pan.bottomAnchor, constant: 12),
expDate.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
expDate.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.45),
expDate.heightAnchor.constraint(equalToConstant: 48),

cvv.topAnchor.constraint(equalTo: pan.bottomAnchor, constant: 12),
cvv.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
cvv.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.45),
cvv.heightAnchor.constraint(equalToConstant: 48),

holder.topAnchor.constraint(equalTo: expDate.bottomAnchor, constant: 12),
holder.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
holder.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
holder.heightAnchor.constraint(equalToConstant: 48),
])
}
}

panContainer includes the card brand selector for co-branded cards. Fields typically look correct at a height of 44–56pt.

SwiftUI

Wrap each view with UIViewRepresentable:

import SwiftUI
import PurseSecureFields

struct SecureFieldView: UIViewRepresentable {
let view: UIView

func makeUIView(context: Context) -> UIView { view }
func updateUIView(_ uiView: UIView, context: Context) {}
}

struct CheckoutView: View {
@StateObject private var vm = CheckoutViewModel()

var body: some View {
VStack(spacing: 12) {
SecureFieldView(view: vm.secureFields.panContainer)
.frame(height: 48)
HStack(spacing: 12) {
SecureFieldView(view: vm.secureFields.expDateView)
.frame(height: 48)
SecureFieldView(view: vm.secureFields.cvvView)
.frame(height: 48)
}
SecureFieldView(view: vm.secureFields.holderNameView)
.frame(height: 48)
}
.padding()
}
}

Step 5 — Submit and handle the result

Adopt SecureFieldsDelegate and call submit() when the user taps Pay.

extension CheckoutViewController: SecureFieldsDelegate {

func secureFieldsFormValidityChanged(_ isValid: Bool) {
payButton.isEnabled = isValid
}

func secureFieldsDidTokenize(_ result: TokenizationResult) {
loadingIndicator.stopAnimating()
sendTokenToBackend(result.vaultFormToken)
}

func secureFieldsDidFail(_ error: SecureFieldsError) {
loadingIndicator.stopAnimating()
showAlert("Payment error: \(error)")
}
}

@objc func payTapped() {
payButton.isEnabled = false
loadingIndicator.startAnimating()
secureFields.submit()
}
Send the token to your backend

vaultFormToken is an opaque server-side token. Pass it to your backend and use it to create a payment via the Payment API. Do not attempt to decode or store the raw token client-side.

Next steps