Pay In
The Pay In component is Payabli’s native mobile UI for card and ACH payments in your app. It owns sensitive field state and renders a hosted form, so your app never touches raw card or bank data. See Tap to Pay if you’re looking for Payabli’s contactless payment component instead.
Right now, only iOS is supported.
Prerequisites
Before you start, confirm you have:
- Xcode 15 or later and Swift 5.9 or later
- iOS 16.7 or later as your deployment target
- A Payabli sandbox
entryPointwith card and ACH enabled - A Payabli
clientIdandclientSecretwith a permission for each operation you use. See OAuth authentication to provision them.
Installation
Install PayabliSDKPayIn from the sdk-ios Swift package:
Add the package
In Xcode, choose File → Add Package Dependencies… and enter the sdk-ios repository URL:
Or add it to your Package.swift dependencies:
Backend token endpoint
The Pay In component authenticates every request with a short-lived Payabli access token, obtained through a tokenProvider closure you implement.
Your backend holds the Payabli clientId and clientSecret, exchanges them for the access token, and never exposes them to your mobile app.
Create the project
This example server uses JavaScript with Node.js and Express.
Requires: Node.js 18 or later, and npm
Create a new Node.js project and enable ES modules:
Write the token endpoint
Create server.js with the following. It returns the token as { "accessToken": "..." }:
Create a .env file next to server.js with your credentials:
Anyone who can call this route gets a token that can charge, store payment methods, and void for your paypoint. Before you go live, replace authenticateUser and mayTakePayments with the same session and permission checks the rest of your app uses.
Keep clientId and clientSecret on your server, in environment variables or a secret store. Never commit .env to source control.
Call it from your app
Pass a tokenProvider closure that calls this endpoint and returns the token when you start the session:
The SDK calls the token provider before its first request, and again when a token is rejected. Concurrent callers share one call, so you don’t need to cache the token or schedule refreshes yourself. Each call has 30 seconds to return. A call that takes longer, throws, or returns a token the SDK can’t use fails with PayabliErrorType.tokenProviderFailed. Return a token from the closure, and don’t make SDK calls from inside it.
Anyone who can call your token endpoint gets a token that can charge, store payment methods, and void for your paypoint. Authenticate the caller and check that they’re allowed to take payments before you return a token.
Operations
PayabliPayIn’s operation parameter selects the hosted form’s behavior:
.storePaymentMethod— Tokenizes a card or ACH account for later use, without charging it. This is the default..capture— Authorizes and captures a card or ACH payment in one step..authorize— Authorizes a card payment without capturing it. The hosted form offers card only in this mode.
PayabliPayIn also exposes direct methods for building your own UI: capture(_:), authorize(_:), captureAuthorizedTransaction(_:), voidTransaction(_:), addCard(_:options:), addBankAccount(_:options:), and addPaymentMethod(_:options:). If you collect card data in your own UI and pass it to these methods, your app handles card numbers and security codes, which brings it into scope for PCI DSS. The hosted form keeps that data out of your app.
Implementation walkthrough
This walkthrough builds a minimal card or ACH capture, from constructing PayabliPayIn through rendering the hosted form:
Loading walkthrough...
/// Add the imports
Import PayabliSDKCore, which holds the session and configuration types, and PayabliSDKPayIn. Then create a view model that holds the component. You can design your own API client and view model conventions. This walkthrough uses a minimal ObservableObject.
/// Implement the backend token provider
Add a function that calls your backend’s token endpoint and returns a Payabli access token. Your backend holds the Payabli clientSecret, not the app. In production, send your app’s own session credential with this request so your token endpoint can authenticate the caller.
/// Start the session
Start one PayabliSession with your entrypoint, environment, and token provider before you build a component. PayabliConfig throws if the entrypoint is blank. Start the session once: calling initialize again with the same settings returns the same session, and different settings throw invalidConfiguration. Pay In and Tap to Pay both run on this session.
/// Create the Pay In component
Construct PayabliPayIn on the session, in .capture mode, with a requestConfiguration describing the amount to charge. Identify the payor in customerData, because a paypoint can require customer identifiers and refuses a charge without them with E7020. Set orderId to your own order reference so you can find the transaction if its outcome is unknown.
/// Render the hosted form and handle the result
Add a view that renders PayabliPayInView and handles the result. The hosted form collects card or ACH details itself. Your view never sees raw card data. If the result has no transaction ID, or the error is PayabliPayInError.submissionInterrupted, the outcome is unknown. Find the transaction by orderId before you let the payor try again.
Implementation reference
PayabliPayInFormConfiguration controls the hosted form’s fields, labels, and layout for every operation, and its hiddenValues and options carry the data sent when the form stores a payment method. PayabliPayInRequestConfiguration carries the request data for capture and authorize forms. PayabliPayInSheetConfiguration controls the sheet presentation.
PayabliPayInFormConfiguration fields
This hosted form is shared by all three operations. See Operations for what each one does.
Hosted methods the payor can select. For .authorize, the form offers card only.
Initial selected method. Falls back to the first allowed method if .card isn’t allowed.
Custom section grouping for card fields. Required and payment-summary fields are appended automatically if missing.
properties
Optional stable identifier. Defaults to the section title or its joined field names.
Section title, for example Card Information.
Per-section text style override.
Ordered PayabliPayInField values in this section.
Vertical spacing between inputs in this section.
Horizontal spacing for paired inputs in this section.
Per-field vertical spacing overrides, keyed by field.
.inputs for a section of input fields, or .summary for the section that shows the operation’s amounts. A capture or authorize form without a .summary section gets one. A .summary section with no title is headed Payment. An .inputs section with no title has no heading.
Custom section grouping for ACH fields. .secCode is never rendered, even if listed.
properties
Optional stable identifier. Defaults to the section title or its joined field names.
Section title, for example Card Information.
Per-section text style override.
Ordered PayabliPayInField values in this section.
Vertical spacing between inputs in this section.
Horizontal spacing for paired inputs in this section.
Per-field vertical spacing overrides, keyed by field.
.inputs for a section of input fields, or .summary for the section that shows the operation’s amounts. A capture or authorize form without a .summary section gets one. A .summary section with no title is headed Payment. An .inputs section with no title has no heading.
Flat card field order, used only when cardSections is nil.
Flat ACH field order, used only when bankSections is nil.
Values submitted with the form without rendering an editable field.
properties
.personal or .business.
.ppd, .web, .tel, .ccd, or .boc.
Default or hidden customer data, merged with any visible customer fields.
properties
Custom identifier fields, when the paypoint uses custom identifiers instead of name/email/customer ID.
Options sent when the form stores a payment method with .storePaymentMethod. These correspond to the options of Save a payment method.
properties
properties
Custom identifier fields, when the paypoint uses custom identifiers instead of name/email/customer ID.
vendorId and vendorNumber.
Client-side validation, run before a request is sent. Same fields and defaults as validation in PayabliPayInRequestConfiguration.
Title, subtitle, submit button text, field labels, and placeholders.
properties
Form title. Unset by default, so the form shows no title.
Submit button text. Unset by default, so the button names the operation: Pay for .capture, Authorize for .authorize, and Save for .storePaymentMethod. While a submission runs, the button shows the operation in progress, such as Paying….
Visible and accessibility label text, keyed by PayabliPayInField.
Placeholder text, keyed by PayabliPayInField.
Label on the payment summary’s Total row.
.external shows visible labels. .placeholder uses placeholder-first inputs.
Global visible-label override. Leave unset to follow labelLayout.
Fields to hide the visible label for. Accessibility labels are unaffected.
Card number spacing, expiration separator, and ACH account masking.
properties
Adds visual card number grouping.
Separator used in expiration entry. An empty string resolves to /.
Masks ACH account number entry in the hosted field. The routing number isn’t masked.
Default and per-field input sizes. Height is clamped to the accessibility minimum touch target.
properties
Default PayabliPayInInputSize: optional width, height (default 52), and horizontalPadding (default 14).
Per-field PayabliPayInInputSize overrides, keyed by PayabliPayInField.
.leading, .trailing, or .hidden.
.top or .aboveSubmitButton.
Optional visible fields to require. .amount is always required.
Text styles and spacing for the read-only amount and fee rows on capture and authorize forms. Forms that store a payment method leave the rows out. The row labels come from labels.fieldLabels for .amount, .serviceFee, and .surchargeFee, and the Total row’s label from labels.total.
properties
Font and color for the label text.
Font and color for the value text.
Card, ACH, customer, and payment-summary fields available for fields, cardFieldOrder, bankFieldOrder, requiredFields, and hiddenFieldLabels.
cases
bankSections.PayabliPayInRequestConfiguration fields
Used by hosted capture and authorize forms. Not used for .storePaymentMethod. The direct capture(_:) and authorize(_:) methods take a PayabliPayInRequest instead, which has the same fields plus a required paymentMethod.
properties
Must be greater than 0 after rounding to two decimal places, so 0.001 is refused.
Must not be negative.
For example, USD.
Defaults merged with form-entered customer data. A paypoint can require customer identifiers, such as customerNumber, and refuses a charge without them with E7020. When the form stores a payment method, pass customer data in hiddenValues.customerData or options.customerData instead.
properties
Custom identifier fields, when the paypoint uses custom identifiers instead of name/email/customer ID.
Your own order reference. Set it on every request so you can find the transaction in the Payabli Portal if the outcome is unknown.
The SDK generates a new key for each call when you don’t set one, so calling again without your own key is a second payment, not a retry.
Client-side validation, run before a request is sent.
properties
Runs a checksum on card numbers to catch typos before the request is sent.
Runs an ABA checksum on ACH routing numbers.
PayabliPayInSheetConfiguration fields
Controls the sheet presentation for .payabliPayInSheet(...). The sheet wraps the same hosted form as PayabliPayInView, so configuration, requestConfiguration, and style still apply:
Sheet title. Falls back to the form’s labels.title when movesFormHeaderToSheetHeader is true.
Sheet subtitle. Falls back to the form’s labels.subtitle when movesFormHeaderToSheetHeader is true.
.close, .back, or .hidden.
Dismisses the sheet automatically after onCompleted fires.
SwiftUI presentation detents. An empty set resolves to .large.
SwiftUI drag indicator visibility.
Padding around the sheet’s content.
Moves the form’s title and subtitle into the sheet header instead of rendering them inline.
Adds a detent sized to the form’s measured content height, when it fits.
Expands to .large if the form’s content doesn’t fit the selected detent.
Response reference
PayabliPayInResult is a unified wrapper for both token-storage and transaction operations.
.storedPaymentMethod or .transaction.
Stored-method result code, or MoneyIn response code.
Human-readable result or failure reason, when the backend returns one.
Present for .transaction results. Additional detail beyond reason.
Present for .transaction results. Suggested next step, when the backend returns one.
Present when kind is .storedPaymentMethod.
properties
nil when the service’s response doesn’t include an ID.
.card or .bankAccount.
Present when kind is .transaction. Covers capture, authorize, and capture-authorized results.
properties
Optional, and can be absent even in a completed result. When it’s absent, find the transaction by your orderId before you charge again.
The order reference sent with the request, when present.
Processor-level response detail. See Make a transaction (v2) for the underlying field semantics.
properties
The order reference the processor returned, when present.
Present for .transaction results. The raw response the transaction fields above were parsed from.
properties
Same shape as transaction above.
Outcomes and errors
Every charge ends in one of three outcomes. Only not charged is safe to retry.
A returned result means something different for each call:
For voidTransaction(_:), transactionFailed means the void was refused. It doesn’t mean the original payment wasn’t charged.
Show error.localizedDescription to the payor, since it names what the service rejected. Don’t log it, because it can quote what was submitted. Log (error as? any PayabliError)?.code instead.
Theming & diagnostics
Style the hosted form to match your app and inspect network activity for debugging:
Style
Use PayabliPayInStyle to style the hosted form. Pass it directly to PayabliPayInView or .payabliPayInSheet(...), or inject it with .payabliPayInStyle(_:).
To use a custom font:
Diagnostics
Diagnostics are disabled by default. Enable them for local debugging or QA. Don’t enable them in production builds shipped to end users.
Pass diagnostics when you construct PayabliPayIn. Each PayabliPayInDiagnosticEntry includes phase (.request, .response, .failure), timestamp, method, url, statusCode, headers, body, durationMilliseconds, and errorDescription.
Diagnostics redact authorization headers, access and request tokens, idempotency keys, card number, CVV, ACH account and routing numbers, stored method IDs, customer identifiers, names, emails, phones, and addresses before they reach your handler. You don’t need to add more redaction yourself, but don’t log the redacted output somewhere less secure than the app itself.
Example app
The sdk-ios repository includes PayabliDemo, a SwiftUI example app that exercises PayabliPayIn and PayabliTTP (card/ACH capture and Tap to Pay), alongside a bundled local Node.js token server for testing.
Configure your secrets
Copy the secrets template, then fill in your sandbox identifiers and point the app at the bundled local token server:
Loading walkthrough...
/// Set your sandbox identifiers
In App/Configuration/Secrets.swift, set the sandbox entry in entryPoints to your sandbox entryPoint. There’s no App ID to set: the SDK reads it from the signed app, and the TapToPay tab shows the value to register as an authorized app. localNetworkHost is the fallback host a physical device uses when you don’t pass -PayabliTokenHost. Set it to the output of scutil --get LocalHostName on your Mac, followed by .local:
/// Point the app at the local token server
Both token endpoints ship pointing at a placeholder backend. Switch them to DemoConfiguration.TokenServer.accessTokenURL, which resolves to 127.0.0.1 in the Simulator, so the Save and Capture tabs work there without a real backend. The TapToPay tab needs a physical device, which also needs the server setup in the next step:
Start the local token server
In a separate terminal, start the bundled LocalTokenServer:
Edit .env and set PAYABLI_ACCESS_TOKEN to a short-lived sandbox access token, then start the server:
The server binds only to 127.0.0.1 by default and authenticates no caller, so run it only on your own machine.
To test on a physical device, which Tap to Pay requires, the phone has to reach the server over your network:
-
Start the server on all interfaces instead, only on a trusted network, and stop it when you’re done:
-
In Xcode, choose Product → Scheme → Edit Scheme → Run → Arguments and pass your Mac’s LAN address, on the same network as the phone:
-
Allow Local Network access when the app asks for it on the phone. See the
LocalTokenServerREADME for the credential-exchange mode and physical-device networking notes.
Run the app
Open PayabliDemo.xcodeproj in Xcode and set DEVELOPMENT_TEAM on the PayabliDemo target. Select the PayabliDemo sandbox scheme, which picks the sandbox entry from Secrets.entryPoints, and run it. The Save and Capture tabs work in the Simulator. The TapToPay tab requires a physical device. See Requirements.
The example app assumes a sandbox entryPoint with card, ACH, and Tap to Pay enabled. Contact your Payabli representative if your sandbox paypoint doesn’t have these enabled yet.
Related resources
See these related resources to help you get the most out of Payabli.
Prerequisites
- Mobile components overview - Learn how Payabli’s native mobile SDK components let you accept payments in your mobile app