Implementing Crypto Payment Acceptance via Mobile Application (Merchant)
Merchants need to accept crypto payments at point of sale or online shop. Mobile app is a cash terminal: enter amount, show QR to customer, await confirmation, record sale. All must work fast and without complex setup.
Generating Payment Address and QR
Generate new address per transaction — simplifies matching "which payment for which order". HD wallet (BIP-32/BIP-44) allows address derivation: m/44'/60'/0'/0/N.
// iOS — deriving next address from HD wallet (secp256k1)
import HDWalletKit
func nextReceivingAddress(masterKey: HDPrivateKey, accountIndex: UInt32) -> String {
let derived = masterKey
.derived(at: 44, hardened: true)
.derived(at: 60, hardened: true) // ETH
.derived(at: 0, hardened: true) // account
.derived(at: 0) // external chain
.derived(at: accountIndex)
return derived.publicKey.ethereumAddress
}
Store accountIndex locally, increment per payment. Server side should monitor all used addresses.
QR Code with Exchange Rate Timer
Crypto rates change. To ensure customer pays exactly the right amount:
- Merchant enters fiat amount (1,000 RUB)
- App requests current rate (CoinGecko
/simple/price?ids=tether&vs_currencies=rub) - Calculates crypto amount with buffer:
amount_usdt = fiat_amount / rate * 1.005(0.5% slippage) - Locks rate for 15 minutes, shows timer
- Generates QR with URI:
ethereum:0xUsdtAddress@1/transfer?address=0xMerchant&uint256=<raw_amount>
// Android — calculating crypto amount with buffer
fun calculateCryptoAmount(
fiatAmount: BigDecimal,
ratePerUnit: BigDecimal,
slippagePercent: BigDecimal = BigDecimal("0.5"),
decimals: Int = 6 // USDT: 6
): BigInteger {
val slippageFactor = BigDecimal.ONE + slippagePercent / BigDecimal(100)
val cryptoAmount = fiatAmount.divide(ratePerUnit, 10, RoundingMode.HALF_UP) * slippageFactor
return cryptoAmount.movePointRight(decimals).toBigInteger()
}
On timer expiry — update QR with new rate. Warning: "QR rate expired, updating price".
Awaiting and Confirming Transaction
After showing QR — app listens for incoming transactions to payment address. Two options:
Polling via API: Query Etherscan API / Blockscout every 5–10 seconds:
GET https://api.etherscan.io/api?module=account&action=tokentx&address={paymentAddress}&startblock={latest}
WebSocket from server: Backend monitors blockchain and sends event via WebSocket or push on received transaction.
First option simpler but costly at scale. Second is correct for production.
Payment Statuses
// iOS — payment status state machine
enum PaymentStatus {
case awaitingPayment(expiresAt: Date)
case detected(txHash: String, confirmations: Int)
case confirmed(txHash: String, amountReceived: BigDecimal)
case underpaid(expected: BigDecimal, received: BigDecimal)
case expired
case failed(reason: String)
}
On detected — green screen "Payment detected, awaiting confirmations (1/3)". On confirmed — cash register ding, full success screen with amount.
Partial Payment and Timeout
If customer paid less — underpaid. Three strategy options:
- Wait for additional payment until extended timer (1–24 hours)
- Accept partially with debt record
- Auto-refund (complex — requires withdrawal key storage)
Choose strategy in merchant app settings.
Transaction History and Reports
List accepted payments: date, amount in crypto and fiat at payment rate, status, TxHash (Explorer link). Filters by date and status. CSV export — for accounting.
Timeline: 5 working days: HD address per order, QR generation with URI format and rate timer, polling/WS incoming transactions, confirmation screen with sound, history with filters.







