Sale

A sale authorizes and captures a payment in a single step—use it when the final amount is known at checkout.

Prerequisites

  • Authenticated merchant with login()
  • Active location set via setActiveLocationID()
  • Card reader prepared with prepare() (iOS) or device enrolled (Android)

Basic Sale

iOS:

let breakdown = PaymentBreakdown(
    subtotal: 10000,          // $100.00 in cents
    taxRate: 0.0875,          // 8.75% as a decimal
    taxAmount: 875,           // $8.75 in cents
    tipAmount: 2000,          // $20.00 tip
    tipType: .fixed
)

let currency = CurrencyCode(currencyCode: "USD", displayName: "US Dollar")

let response = try await KoardMerchantSDK.shared.sale(
    amount: 12875,            // subtotal + tax + tip
    breakdown: breakdown,
    currency: currency,
    eventId: UUID().uuidString
)

Android:

val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 0.0875,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = "fixed"
)

sdk.sale(
    activity = this,
    amount = 12875,
    breakdown = breakdown,
    eventId = UUID.randomUUID().toString()
).collect { event ->
    when (event.actionStatus) {
        ActionStatus.OnComplete -> {
            val txn = event.response?.transaction
            println("Sale complete: ${txn?.transactionId}")
        }
        ActionStatus.OnFailure -> {
            println("Sale failed: ${event.response?.message}")
        }
        else -> { /* reader progress */ }
    }
}

Sale with Surcharge

The surcharge percentage is applied to the full transaction amount (subtotal + tax + tip):

surcharge = (subtotal + taxAmount + tipAmount) × surchargeRate
         = (10000 + 875 + 2000) × 0.035
         = 451 cents ($4.51)

iOS:

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 0.0875,
    taxAmount: 875,
    tipAmount: 2000,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(
        amount: 451,       // surcharge on full amount
        percentage: 0.035
    )
)

let response = try await KoardMerchantSDK.shared.sale(
    amount: 13326,            // 12875 + 451 surcharge
    breakdown: breakdown,
    currency: currency,
    eventId: UUID().uuidString
)

Android:

val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 0.0875,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = "fixed",
    surcharge = Surcharge(
        amount = 451,
        percentage = 0.035
    )
)

sdk.sale(
    activity = this,
    amount = 13326,
    breakdown = breakdown,
    eventId = UUID.randomUUID().toString()
).collect { event ->
    when (event.actionStatus) {
        ActionStatus.OnComplete -> {
            println("Sale complete: ${event.response?.transaction?.transactionId}")
        }
        ActionStatus.OnConfirmSurcharge -> {
            val txn = event.response?.transaction
            sdk.confirm(
                transactionId = txn?.transactionId ?: "",
                confirm = true
            )
        }
        ActionStatus.OnFailure -> {
            println("Sale failed: ${event.response?.message}")
        }
        else -> { /* reader progress */ }
    }
}

Surcharge Confirmation

If the terminal has surcharging enabled and the card is eligible (credit only—debit cards are automatically excluded), the transaction returns surchargePending. You must present the disclosure and confirm.

iOS:

if response.transaction?.status == .surchargePending {
    let disclosure = response.transaction?.surchargeDisclosure ?? ""
    let approved = await showSurchargeDisclosure(disclosure)

    let confirmed = try await KoardMerchantSDK.shared.confirm(
        transaction: response.transactionId ?? "",
        confirm: approved,
        amount: nil,
        breakdown: nil,
        eventId: nil
    )
}

Android:

ActionStatus.OnConfirmSurcharge -> {
    val txn = event.response?.transaction
    // Present disclosure to customer, then:
    sdk.confirm(
        transactionId = txn?.transactionId ?: "",
        confirm = true
    )
}

Bypassing Automatic Surcharge

Set bypass: true to skip the processor's automatic surcharge. Useful for custom BIN-based surcharging:

iOS:

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 0.0875,
    taxAmount: 875,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(bypass: true)
)

Android:

val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 0.0875,
    taxAmount = 875,
    tipType = "fixed",
    surcharge = Surcharge(bypass = true)
)

Parameters

Parameter Type Required Description
amount Int Yes Total amount in minor units (cents)
breakdown PaymentBreakdown? No Itemized breakdown (see below)
currency CurrencyCode Yes (iOS) Currency for the transaction
eventId String? No Idempotency key (UUID recommended)
activity Activity Yes (Android) Android activity for NFC access

PaymentBreakdown

Field Type Description
subtotal Int Base amount in minor units
taxRate Double? Tax rate as decimal (0.0875 = 8.75%)
taxAmount Int Calculated tax in minor units
tipAmount Int? Tip in minor units
tipRate Double? Tip rate as decimal (alternative to fixed tip)
tipType TipType .fixed / .percentage (iOS) or "fixed" / "percentage" (Android)
surcharge Surcharge? Nested surcharge object

Surcharge

Field Type Default Description
amount Int? nil Fixed surcharge in minor units
percentage Double? nil Surcharge rate as decimal (0.035 = 3.5%). Applied to subtotal + tax + tip.
bypass Bool false Skip automatic surcharge calculation

See Also