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:
- File → Add Package Dependencies…
- Paste the repository URL:
https://github.com/UpStreamPay/vault-ios.git
- Select Up to Next Major Version starting at
1.0.0, then click Add Package. - 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.
| Environment | VaultEnvironment case | Resolved vault URL |
|---|---|---|
| Sandbox (development) | .sandbox | https://api.vault.purse-sandbox.com |
| Production | .production | https://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.
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()
}
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
- Events and tokenization — full delegate reference, brand detection, error handling
- Customize — apply styles, placeholders, and layout guidance
- Security and compliance — required hardening for production