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

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: 8.75,         // 8.75% as a percent value
    taxAmount: 219,
    tipAmount: 500,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(
        amount: 75,        // $0.75 surcharge in cents
        percentage: 0.03,  // 3% surcharge rate
        bypass: false
    )
)

taxRate is a floating-point percent value (e.g., 8.75 for 8.75%). Surcharge details are passed as a nested Surcharge object with amount, percentage, and bypass fields.

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.status to 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_id or event_id to 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 handle KoardMerchantSDKError cases explicitly.

See Also