Surcharging

Surcharging adds a fee to credit card transactions to offset processing costs. Koard supports both automatic surcharging (processor-calculated) and custom surcharging (merchant-calculated, e.g., BIN-based).

Debit cards: Surcharges must not be applied to debit card transactions. Koard automatically excludes debit cards from surcharging for US-based transactions. The SDK will not trigger a surchargePending status for debit cards.

Partner responsibility: It is the partner's responsibility to ensure that merchants configure the correct surcharge rates, disclosure text, and comply with applicable card brand rules and state/local regulations. Koard provides the surcharge infrastructure, but legal compliance—including rate caps, signage, and receipt requirements—is the merchant's obligation.

Disclosure requirement: Most card brand rules and state laws require that the surcharge is disclosed to the cardholder before the transaction is completed. Koard's SDK handles this via the surcharge confirmation flow, but the partner must ensure the disclosure content is accurate and legally compliant.

How Automatic Surcharging Works

  1. The merchant initiates a sale.
  2. The processor evaluates the card—only credit cards in eligible regions are surcharged. Debit cards are automatically excluded.
  3. If eligible, the transaction returns with status surchargePending.
  4. The SDK surfaces the surcharge amount and disclosure text.
  5. The merchant app presents the disclosure to the customer.
  6. The merchant calls confirm() with the customer's decision.
  7. If confirmed, the transaction finalizes with the surcharge included.

Note: Surcharge pending only applies to sale transactions. Preauth transactions do not trigger surchargePending—use the custom BIN surcharge flow instead.

Surcharge Calculation Basis

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

surcharge = (subtotal + taxAmount + tipAmount) * surchargeRate

For example, with a 3.5% surcharge on a $100 subtotal + $8.75 tax + $20 tip:

surcharge = (10000 + 875 + 2000) * 0.035 = 12875 * 0.035 = $4.51 (451 cents)

Rate Hierarchy

Surcharge rates are resolved in priority order:

Priority Source Description
1 (highest) PaymentBreakdown.surcharge Per-transaction override passed in the SDK call
2 Terminal configuration Rate set on the terminal in the Koard dashboard
3 Merchant configuration Default rate from the merchant's account settings
4 (lowest) Processor default Fallback rate from the payment processor

The Surcharge Object

Both SDKs use a nested Surcharge object inside PaymentBreakdown:

PaymentBreakdown.Surcharge(
    amount: Int?,          // fixed surcharge in cents
    percentage: Double?,   // surcharge rate as decimal (0.035 = 3.5%)
    bypass: Bool           // skip automatic surcharge (default: false)
)
Surcharge(
    amount: Int?,          // fixed surcharge in cents
    percentage: Double?,   // surcharge rate as decimal
    bypass: Boolean        // skip automatic surcharge (default: false)
)

Usage in PaymentBreakdown

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 0.0875,
    taxAmount: 875,
    tipAmount: 2000,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(
        amount: 451,       // surcharge on (10000 + 875 + 2000) at 3.5%
        percentage: 0.035
    )
)
val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 0.0875,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = "fixed",
    surcharge = Surcharge(
        amount = 451,      // surcharge on (10000 + 875 + 2000) at 3.5%
        percentage = 0.035
    )
)

API Flows

Flow 1: Automatic Surcharge (Sale)

This is the standard flow where the processor automatically determines surcharge eligibility. Surcharge pending only triggers on sale transactions.

Step 1 — Initiate Sale via SDK

The sale is initiated through the Koard SDK on the device. The SDK handles card reading, encryption, and communication with Koard's servers.

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 8.75,
    taxAmount: 875,
    tipAmount: 2000,
    tipType: .fixed
)
let result = try await koard.createSale(amount: 12875, breakdown: breakdown)
// result.status may be "surcharge_pending" for eligible credit cards
val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 8.75,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = TipType.FIXED
)
val result = koard.createSale(amount = 12875, breakdown = breakdown)
// result.status may be "surcharge_pending" for eligible credit cards

Step 2 — Handle surchargePending Response

If the card is a credit card and surcharge rules apply:

{
  "transaction_id": "txn_abc123",
  "status": "surcharge_pending",
  "total_amount": 13326,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": true,
  "surcharge_amount": 451,
  "surcharge_rate": 0.035,
  "card_brand": "visa",
  "card_type": "credit",
  "payment_method": "contactless"
}

If the card is debit, the response will be captured immediately with no surcharge. No confirm step is needed.

Step 3 — Confirm or Decline Surcharge

Present the surcharge disclosure to the customer, then confirm:

POST /v1/payments/{transaction_id}/confirm
Authorization: Bearer {api_key}

{
  "confirm": true,
  "event_id": "evt_confirm_id"
}

Response (confirmed):

{
  "transaction_id": "txn_abc123",
  "status": "captured",
  "total_amount": 13326,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": true,
  "surcharge_amount": 451,
  "surcharge_rate": 0.035
}

Response (declined — confirm: false):

{
  "transaction_id": "txn_abc123",
  "status": "cancelled",
  "total_amount": 0,
  "surcharge_applied": false,
  "surcharge_amount": 0
}

Flow 2: Custom BIN Surcharge via Preauth

For merchants who calculate surcharges based on the card's BIN. Use preauth with bypass: true to skip automatic surcharge, then add the surcharge via incremental auth.

Step 1 — Preauth with Bypassed Surcharge via SDK

Initiate a preauth through the SDK with bypass: true to skip automatic surcharge calculation:

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 8.75,
    taxAmount: 875,
    tipAmount: 2000,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(bypass: true)
)
let result = try await koard.createPreauth(amount: 12875, breakdown: breakdown)
val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 8.75,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = TipType.FIXED,
    surcharge = Surcharge(bypass = true)
)
val result = koard.createPreauth(amount = 12875, breakdown = breakdown)

Response:

{
  "transaction_id": "txn_preauth_123",
  "status": "authorized",
  "total_amount": 12875,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": false,
  "surcharge_amount": 0,
  "card_brand": "visa",
  "card_type": "credit"
}

Step 2 — BIN Lookup and Calculate Surcharge

Use the card BIN from the response to determine surcharge eligibility:

const bin = response.transaction.bin;
const isDebit = await checkIsDebitCard(bin);

if (isDebit) {
  // Debit card — skip surcharge, capture at original amount
  await capture(transactionId, 12875);
  return;
}

// Credit card — calculate surcharge
const baseAmount = 12875;
const surchargeRate = lookupSurchargeRate(bin);  // e.g., 0.035
const surchargeAmount = Math.round(baseAmount * surchargeRate);  // 451

Step 3 — Incremental Auth for Surcharge Amount

Add the surcharge as an incremental authorization on the existing preauth:

POST /v3/payments/{transaction_id}/auth
Authorization: Bearer {api_key}

{
  "amount": 451,
  "breakdown": {
    "subtotal": 0,
    "surcharge": {
      "amount": 451,
      "percentage": 0.035
    }
  },
  "event_id": "evt_inc_auth_id"
}

Response:

{
  "transaction_id": "txn_preauth_123",
  "status": "authorized",
  "total_amount": 13326,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": true,
  "surcharge_amount": 451,
  "surcharge_rate": 0.035
}

Step 4 — Capture the Full Amount

POST /v4/payments/{transaction_id}/capture
Authorization: Bearer {api_key}

{
  "amount": 13326,
  "breakdown": {
    "subtotal": 10000,
    "taxRate": 8.75,
    "taxAmount": 875,
    "tipAmount": 2000,
    "tipType": "fixed",
    "surcharge": {
      "amount": 451,
      "percentage": 0.035
    }
  },
  "event_id": "evt_capture_id"
}

Response:

{
  "transaction_id": "txn_preauth_123",
  "status": "captured",
  "total_amount": 13326,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": true,
  "surcharge_amount": 451,
  "surcharge_rate": 0.035,
  "batch_id": "batch_456"
}

Flow 3: Preauth with Surcharge, Then Remove on Capture

For cases where you preauth with surcharge included initially, but then discover the surcharge can't be applied (e.g., BIN lookup reveals a debit card). Capture at the lower amount with an updated breakdown.

Step 1 — Preauth with Surcharge Included via SDK

Initiate a preauth with the surcharge pre-calculated and included in the total:

let breakdown = PaymentBreakdown(
    subtotal: 10000,
    taxRate: 8.75,
    taxAmount: 875,
    tipAmount: 2000,
    tipType: .fixed,
    surcharge: PaymentBreakdown.Surcharge(amount: 451, percentage: 0.035)
)
// Total = 10000 + 875 + 2000 + 451 = 13326
let result = try await koard.createPreauth(amount: 13326, breakdown: breakdown)
val breakdown = PaymentBreakdown(
    subtotal = 10000,
    taxRate = 8.75,
    taxAmount = 875,
    tipAmount = 2000,
    tipType = TipType.FIXED,
    surcharge = Surcharge(amount = 451, percentage = 0.035)
)
// Total = 10000 + 875 + 2000 + 451 = 13326
val result = koard.createPreauth(amount = 13326, breakdown = breakdown)

Response:

{
  "transaction_id": "txn_preauth_456",
  "status": "authorized",
  "total_amount": 13326,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": true,
  "surcharge_amount": 451,
  "surcharge_rate": 0.035
}

Step 2 — BIN Lookup Reveals Debit Card

Your BIN lookup returns debit: true. Surcharges cannot be applied to debit cards.

Step 3 — Capture Without Surcharge (Lower Amount)

Capture at the original amount without surcharge. The processor releases the unused hold automatically:

POST /v4/payments/{transaction_id}/capture
Authorization: Bearer {api_key}

{
  "amount": 12875,
  "breakdown": {
    "subtotal": 10000,
    "taxRate": 8.75,
    "taxAmount": 875,
    "tipAmount": 2000,
    "tipType": "fixed",
    "surcharge": {
      "bypass": true
    }
  },
  "event_id": "evt_capture_no_surcharge"
}

Response:

{
  "transaction_id": "txn_preauth_456",
  "status": "captured",
  "total_amount": 12875,
  "subtotal": 10000,
  "tax_amount": 875,
  "tip_amount": 2000,
  "surcharge_applied": false,
  "surcharge_amount": 0,
  "surcharge_rate": 0,
  "batch_id": "batch_789"
}

The difference between the authorized amount ($133.26) and the captured amount ($128.75) is automatically released back to the cardholder.


Bypassing Automatic Surcharge

Set bypass: true to skip the processor's automatic surcharge calculation. Required for custom surcharge workflows:

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

Transaction Response — Surcharge Fields

Field Type Description
surcharge_applied boolean Whether a surcharge was applied to this transaction
surcharge_amount integer Surcharge amount in cents
surcharge_rate float Surcharge rate as decimal (0.035 = 3.5%)

Surcharging on Other Operations

Operation Surcharge Behavior
Sale Surcharge calculated automatically; triggers surchargePending for confirmation
Preauth No automatic surcharge pending. Use bypass: true + incremental auth for custom surcharge
Capture Include surcharge in breakdown for accurate settlement. Can capture less to remove surcharge
Incremental Auth Used to add custom surcharge amounts to existing preauth
Refund Surcharge prorated automatically—no breakdown needed
Reverse Full surcharge released automatically—no breakdown needed
Tip Adjust Surcharge preserved—not recalculated on tip change

See Also