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
surchargePendingstatus 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
- The merchant initiates a sale.
- The processor evaluates the card—only credit cards in eligible regions are surcharged. Debit cards are automatically excluded.
- If eligible, the transaction returns with status
surchargePending. - The SDK surfaces the surcharge amount and disclosure text.
- The merchant app presents the disclosure to the customer.
- The merchant calls
confirm()with the customer's decision. - 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
capturedimmediately 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
- Sale — One-step payment with automatic surcharge
- Preauth — Hold with surcharge bypass option
- Incremental Auth — Add custom surcharge to existing auth
- Payment Lifecycle — End-to-end payment flow
