Surcharging

Control how surcharges are applied across Sale, Preauth, Confirm, and Capture flows in the Koard Merchant SDK.

What You Learn
  • How Koard auto-applies surcharges when none are specified
  • The rate hierarchy across merchant, terminal, location, and per-transaction overrides
  • SDK structures for passing percentage, amount, or bypass
  • How to confirm surcharge_pending transactions or run a custom surcharge workflow

Automatic Surcharging

By default, Koard inspects the card BIN and the location's configured state to determine whether a surcharge can be applied. If the SDK call (Sale or Preauth) omits a surcharge in the payment breakdown and no fixed surcharge is configured on the merchant, terminal, or location, Koard calculates a compliant surcharge automatically.

Automatic surcharging currently applies to iOS and Android SDK flows. The server recalculates total_amount and annotates the transaction response with surcharge fields.

Surcharge Pending Responses

When Koard adds a surcharge on your behalf, the resulting Sale transaction returns status: "surcharge_pending" along with populated surcharge_amount and surcharge_rate. The app must show a disclosure to the payer and call confirm (or cancel) before the funds can settle.

{
  "transaction_id": "YOUR_TRANSACTION_ID",
  "event_id": "YOUR_EVENT_ID",
  "mid": "YOUR_KOARD_MID",
  "tid": "YOUR_KOARD_TID",
  "processor_mid": "YOUR_PROCESSOR_MID",
  "processor_tid": "YOUR_PROCESSOR_TID",
  "account_id": "YOUR_ACCOUNT_ID",
  "device_id": "YOUR_DEVICE_ID",
  "processor": "tsys",
  "gateway": "sierra",
  "currency": "USD",
  "location_id": "YOUR_LOCATION_ID",
  "gateway_transaction_id": "GATEWAY_TRANSACTION_ID",
  "subtotal": 1136,
  "tip_amount": 0,
  "tip_type": "fixed",
  "tax_amount": 110,
  "tax_rate": 10,
  "total_amount": 1283,
  "surcharge_applied": true,
  "surcharge_amount": 37,
  "surcharge_rate": 3.0,
  "status": "surcharge_pending",
  "status_reason": "approved",
  "payment_method": "contactlessIcc",
  "card_type": "Visa Credit",
  "card_brand": "Visa Credit",
  "card": "451593******8495",
  "additional_details": {
    "emv_tags": "EMV_TAGS",
    "approval_code": "APPROVAL_CODE",
    "retrieval_reference_number": "YOUR_RRN",
    "transaction_sequence_number": "YOUR_TSN",
    "merchant_identifier": "YOUR_MERCHANT_ID",
    "merchant_name": "YOUR_MERCHANT_NAME"
  },
  "gateway_transaction_response": {
    "type": "sale",
    "status": "ready",
    "currency": "USD",
    "approvalCode": "470382",
    "responseCode": "A",
    "responseMessage": "APPROVAL",
    "authorizedAmount": 1246,
    "processorResponseCode": "00"
  }
}

Only Sale transactions become surcharge_pending. You must present a disclosure that states the surcharge percentage before calling confirm. Declining the disclosure cancels the transaction as canceled_by_payer.

Rate Hierarchy

When a surcharge is configured in multiple places, the most granular configuration wins:

Priority Level Where it is managed
1 Transaction Passed in the SDK PaymentBreakdown
2 Location MMS or Location APIs
3 Terminal MMS or Terminal APIs
4 Merchant MMS or Merchant APIs
  • Merchant-level: Apply a default percentage or fixed amount for all transactions under the merchant.
  • Terminal-level: Override the merchant default for a specific terminal (e.g., countertop vs kiosk).
  • Location-level: Override both merchant and terminal defaults for a physical address.
  • Per-transaction: Passing a surcharge in the SDK request overrides all upstream settings for that payment only.

Fixed surcharges that are configured ahead of time will be added automatically for eligible card types. Ensure the payer view discloses the surcharge before the tap is completed.

iOS SDK Fields

The surcharge struct lives within PaymentBreakdown.Surcharge. Only one of percentage or amount is required; bypass explicitly skips all surcharge logic.

Field Type Description
percentage Float Whole-number value (3% = 3.0).
amount Int Minor units (cents).
bypass Bool When true, percentage and amount must be omitted and Koard will skip surcharging for that transaction.

Surcharge data can be supplied in Preauth, Sale, Confirm, and Capture flows.

let surcharge = PaymentBreakdown.Surcharge(
    amount: nil,                // Optional fixed amount in cents
    percentage: 3.0,            // Optional percentage (3%)
    bypass: false               // true bypasses surcharging
)

let breakdown = PaymentBreakdown(
    subtotal: 2500,
    taxRate: 0.0875,
    taxAmount: 219,
    tipAmount: 500,
    tipType: .fixed,
    surcharge: surcharge
)

let currency = CurrencyCode(currencyCode: "USD", displayName: "US Dollar")
let transactionId = UUID().uuidString

do {
    let response = try await KoardMerchantSDK.shared.sale(
        amount: 3219,
        breakdown: breakdown,
        currency: currency,
        transactionId: transactionId
    )
    print("Sale completed: \(response.transactionId ?? "Unknown")")
} catch {
    print("Sale failed: \(error)")
}

Confirming a Pending Surcharge

For responses with status: "surcharge_pending", display the disclosure prompt. Afterwards call:

try await KoardMerchantSDK.shared.confirm(
    confirm: true,
    transactionId: transactionId,
    breakdown: updatedBreakdown      // Optional: override surcharge here
)
  • Passing confirm: false cancels the transaction (canceled_by_payer).
  • Passing confirm: true finalizes the surcharge. You may provide a new breakdown to override the automatically calculated rate.

Custom Surcharge Workflow

If you prefer to run your own BIN lookup or surcharge logic:

  1. Call preauth with surcharge.bypass = true.
  2. Inspect additionalDetails["bin"] in the response (9-digit BIN) alongside location data.
  3. Calculate the surcharge externally.
  4. Call capture (or confirm) with the calculated surcharge in the breakdown.

This pattern lets you integrate a proprietary surcharge service while still leveraging Koard's transaction lifecycle.

BIN-Based Custom Surcharge with Incremental Auth

For scenarios where you need to calculate a custom surcharge based on the card BIN (e.g., different rates for debit vs credit, or custom BIN routing rules), you can use incremental authorization to add the surcharge amount after the initial preauth.

This workflow requires running a transaction first to obtain the BIN, then calculating and applying the surcharge:

Step 1: Preauth with Bypassed Surcharge

Start by running a preauth with bypass: true to hold the base amount without any surcharge applied:

let surcharge = PaymentBreakdown.Surcharge(bypass: true)
let breakdown = PaymentBreakdown(
    subtotal: 2500,
    taxRate: 875,
    taxAmount: 263,
    tipAmount: 500,
    tipType: .fixed,
    surcharge: surcharge
)
let currency = CurrencyCode(currencyCode: "USD", displayName: "US Dollar")

let transactionResponse = try await KoardMerchantSDK.shared.preauth(
    amount: 3263,
    breakdown: breakdown,
    currency: currency
)

Step 2: Retrieve BIN and Calculate Surcharge

Extract the card BIN from the transaction response and calculate your custom surcharge:

let cardBin = transactionResponse.additionalDetails?["bin"] as? String ?? ""

// Implement your custom surcharge calculation logic
let surchargeAmount = calculateCustomSurcharge(
    cardBin: cardBin,
    baseAmount: transactionResponse.totalAmount ?? 3263
)

The calculateCustomSurcharge function is your implementation. Use it to apply custom rates based on BIN ranges, card type, or integrate with third-party surcharge services.

Step 3: Incremental Auth for Surcharge

Use incremental authorization to add the calculated surcharge amount to the preauth:

guard let txnId = transactionResponse.transactionId else {
    throw NSError(domain: "CustomSurcharge", code: 1,
                  userInfo: [NSLocalizedDescriptionKey: "Missing transaction ID"])
}

let incrementalAuth = try await KoardMerchantSDK.shared.auth(
    transactionId: txnId,
    amount: surchargeAmount
)

Step 4: Capture with Surcharge Breakdown

Capture the full authorized amount including the surcharge:

let finalAmount = (transactionResponse.totalAmount ?? 0) + surchargeAmount

let captureBreakdown = PaymentBreakdown(
    subtotal: 2500,
    taxRate: 875,
    taxAmount: 263,
    tipAmount: 500,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(
        amount: surchargeAmount,
        bypass: false
    )
)

let capture = try await KoardMerchantSDK.shared.capture(
    transactionId: txnId,
    amount: finalAmount,
    breakdown: captureBreakdown
)

This workflow requires you to run a transaction before calculating the surcharge. Ensure you display the surcharge disclosure to the cardholder before completing the capture, and handle cancellation appropriately if they decline.