Payment Lifecycle
Understand how Koard transactions progress from initial authorization through capture, adjustment, reversal, and refund.
What You Learn
- How sale and preauthorization flows differ
- Which follow-up operations are available and when to use them
- iOS SDK entry points and their matching REST endpoints
- How to monitor state transitions and handle errors
Before You Begin
- Review the transaction-specific guides: Sale, Preauth, Capture, Incremental Auth, Tip Adjust, Reverse, and Refund.
- For implementation details in Swift, start with the Running Payments guide.
- Make sure you have a dedicated test iPhone with your Sandbox Apple Account signed in so you can exercise Tap to Pay flows end-to-end.
Transaction Categories
Tap-Initiated Transactions (card present)
| Transaction | Description | iOS SDK Method | API Entry Point |
|---|---|---|---|
| Sale | One-step auth + capture | KoardMerchantSDK.shared.sale() |
POST /v4/payment |
| Preauth | Authorization hold | KoardMerchantSDK.shared.preauth() |
POST /v4/preauth |
These operations require card data from Tap to Pay or another compliant reader.
Follow-Up Operations (card-not-present)
| Transaction | Purpose | iOS SDK | REST Endpoint |
|---|---|---|---|
| Capture | Settle an authorized amount | capture() |
POST /v3/payments/{id}/capture |
| Incremental Auth | Increase an existing hold | auth() |
POST /v3/payments/{id}/auth |
| Tip Adjust | Update gratuity before settlement | adjust() |
POST /v1/payments/{id}/adjust |
| Reverse | Release held funds | reverse() |
POST /v1/payments/{id}/reverse |
| Refund | Return captured funds | refund() |
POST /v1/payments/{id}/refund |
REST vs SDK: After a successful tap, you can perform every follow-up operation via the REST API, the iOS SDK, or both—choose the channel that fits your workflow.
Lifecycle Flows
Sale Flow
Tap → Sale (status: captured) → [Optional] Refund → Complete
Sales capture funds immediately. Refunds return money after settlement.
Preauth Flow
Tap → Preauth (status: authorized)
├─ Incremental Auth (optional, status stays authorized)
├─ Capture (status: captured) → Refund (optional)
└─ Reverse (status: reversed)
Preauths require an explicit capture to collect funds. If plans change, reverse the authorization instead of refunding.
Successive auths: If a follow-up authorization is declined, Koard automatically reverts to the last successfully authorized amount.
Swift SDK Reference
// Sale (tap required)
KoardMerchantSDK.shared.sale(
amount: Int,
breakdown: PaymentBreakdown? = nil,
currency: CurrencyCode,
transactionId: String? = nil,
type: PaymentType = .sale
) async throws -> TransactionResponse
// Preauthorization (tap required)
KoardMerchantSDK.shared.preauth(
amount: Int,
currency: CurrencyCode,
transactionId: String? = nil,
breakdown: PaymentBreakdown? = nil
) async throws -> TransactionResponse
// Follow-up operations
KoardMerchantSDK.shared.capture(transactionId: String, amount: Int? = nil, breakdown: PaymentBreakdown? = nil)
KoardMerchantSDK.shared.auth(transactionId: String, amount: Int, breakdown: PaymentBreakdown? = nil)
KoardMerchantSDK.shared.adjust(transactionId: String, type: AdjustmentType, amount: Int? = nil, percentage: Double? = nil)
KoardMerchantSDK.shared.reverse(transactionId: String, amount: Int? = nil)
KoardMerchantSDK.shared.refund(transactionId: String, amount: Int? = nil)
Updated Breakdown Example
let breakdown = PaymentBreakdown(
subtotal: 2500,
taxRate: 0.0875,
taxAmount: 219,
tipAmount: 500,
tipType: .fixed,
surchargeAmount: 75,
surchargeRate: 0.03
)
taxRate and surchargeRate are now floating-point decimals (e.g., 0.0875 for 8.75%).
REST Quick Reference
| Operation | Endpoint | Notes |
|---|---|---|
| Sale | POST /v4/payment |
Requires encrypted card data from Tap to Pay |
| Preauth | POST /v4/preauth |
Returns transaction_id for follow-ups |
| Capture | POST /v3/payments/{transaction_id}/capture |
Include breakdown to reconcile tips/surcharge |
| Incremental Auth | POST /v3/payments/{transaction_id}/auth |
Amount is the incremental delta |
| Tip Adjust | POST /v1/payments/{transaction_id}/adjust |
percentage is a decimal (e.g., 0.18) |
| Reverse | POST /v1/payments/{transaction_id}/reverse |
Releases uncaptured funds |
| Refund | POST /v1/payments/{transaction_id}/refund |
Works on captured transactions |
See the individual transaction guides for full payload examples.
Transaction States
| State | Description | Transitions |
|---|---|---|
pending |
Request accepted | → processing, failed |
processing |
Gateway evaluating | → authorized, captured, declined, failed |
authorized |
Funds on hold | → captured, reversed, cancelled |
captured |
Funds collected | → refunded, cancelled |
declined |
Processor rejected | Terminal |
failed |
Processing error | Terminal |
reversed |
Hold released | Terminal |
refunded |
Funds returned | Terminal |
cancelled |
Flow cancelled | Terminal |
Visual Flow
Sale:
pending → processing → captured → [refunded | cancelled]
Preauth:
pending → processing → authorized
├─ capture → captured → [refunded | cancelled]
└─ reverse → reversed
Monitoring State Changes
- Webhooks: Subscribe to
transaction.*events (created, authorized, captured, adjusted, reversed, refunded, settled). See Available Events. - iOS SDK: Inspect
TransactionResponse.transaction.statusto update UI immediately.
switch transaction.status {
case .approved, .captured:
// Success
case .declined:
// Inform user
case .error:
// Retry or escalate
default:
// Handle intermediate states
}
Error Handling & Retries
func processPaymentWithRetry(maxRetries: Int = 3) async throws {
var attempts = 0
while attempts < maxRetries {
do {
let response = try await KoardMerchantSDK.shared.sale(...)
// Success
return
} catch {
attempts += 1
if attempts >= maxRetries { throw error }
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempts))) * 1_000_000_000)
}
}
}
Best Practices
- Choose the right entry point: Use sales for immediate capture; use preauth when totals may change.
- Store transaction IDs: Needed for every follow-up operation and webhook reconciliation.
- Keep breakdowns accurate: Supply tax, tip, and surcharge data with the latest values to keep reports aligned.
- Use idempotency keys: Provide
transaction_idorevent_idto guard against duplicate requests. - Monitor via webhooks: Use asynchronous events to update order states reliably.
Troubleshooting Checklist
- Transaction not found: Confirm the transaction belongs to your Koard account and that the ID is spelled correctly.
- Invalid state transition: Verify the current state (
authorized,captured, etc.) before calling a new operation. - Amount validation errors: Capture/Refund amounts cannot exceed the available balances; partial operations require explicit amounts.
- Tap to Pay issues: Ensure the device has Developer Mode enabled, an active Sandbox Apple Account, and that
prepare()was called. - SDK errors: Authenticate with
login(), set an active location, and handleKoardMerchantSDKErrorcases explicitly.