Crypto payment platform development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Crypto payment platform development
Complex
from 2 weeks to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Crypto Payment Platform

The difference between "add a crypto payment button" and "build a payment platform" is fundamental. The first integrates a third-party service with its guarantees and limitations. The second builds your own infrastructure: address generation, transaction monitoring, key management, settlement, reconciliation. This page covers the second variant: when volume, regulatory requirements, or business model don't allow going through an intermediary.

Why build your own platform

Typical reasons:

  • Volume — over $10M/month, processor commission of 0.5–1% becomes more expensive than own infrastructure
  • Data control — financial data should not go to third parties
  • Custom logic — conditional payments, escrow, recurring payments, payment splits
  • White-label — platform for other businesses, own processing mandatory
  • Regulatory requirements — some jurisdictions require licenses incompatible with foreign processors

Architecture: key components

Client → Payment API → Invoice Service → Address Generator
                                       → Blockchain Monitor
                                       → Settlement Engine
                                       → Merchant Webhook

HD wallets and address generation

Foundation of the platform — hierarchical deterministic wallets (BIP32/BIP44). One master seed generates unlimited child addresses — one unique address per invoice:

m / purpose' / coin_type' / account' / change / index
m / 44'      / 60'        / 0'       / 0      / invoice_id
from hdwallet import HDWallet
from hdwallet.symbols import ETH

def generate_payment_address(merchant_id: int, invoice_id: int) -> str:
    """
    Deterministic address generation: one invoice = one address
    merchant_id: account index (BIP44 level 3)
    invoice_id: address index (BIP44 level 5)
    """
    wallet = HDWallet(symbol=ETH)
    wallet.from_xprivate_key(MASTER_XPRIV)
    wallet.from_path(f"m/44'/60'/{merchant_id}'/0/{invoice_id}")
    return wallet.p2pkh_address()

Critically important: private key from master seed must be stored in HSM (Hardware Security Module) or at minimum in AWS KMS / HashiCorp Vault. No plaintext keys in config files or DB.

For public address generation (without private key access) — xpub is sufficient:

# Only xpub for watch-only addresses
wallet.from_xpublic_key(MERCHANT_XPUB)
wallet.from_path(f"m/0/{invoice_id}")  # relative from xpub
address = wallet.p2pkh_address()

Transaction monitoring

Two approaches to tracking incoming payments:

Polling via RPC — simple, works everywhere:

import asyncio
from web3 import Web3

async def monitor_invoice(
    address: str,
    expected_amount: int,  # in wei
    invoice_id: str,
    timeout_seconds: int = 3600
):
    w3 = Web3(Web3.HTTPProvider(ETH_RPC_URL))
    start_block = w3.eth.block_number
    deadline = time.time() + timeout_seconds

    while time.time() < deadline:
        # Check address balance
        balance = w3.eth.get_balance(address, "latest")
        if balance >= expected_amount:
            # Wait for confirmations
            await wait_for_confirmations(address, expected_amount, confirmations=6)
            await mark_invoice_paid(invoice_id, balance)
            return

        await asyncio.sleep(15)  # roughly per block

    await mark_invoice_expired(invoice_id)

WebSocket subscriptions — for minimal latency:

from web3 import AsyncWeb3
from web3.providers import WebsocketProviderV2

async def subscribe_to_address(address: str, callback):
    async with AsyncWeb3(WebsocketProviderV2("wss://eth-mainnet.g.alchemy.com/v2/KEY")) as w3:
        subscription_id = await w3.eth.subscribe(
            "logs",
            {"address": address}  # get all logs for address
        )
        async for response in w3.socket.process_subscriptions():
            if response["subscription"] == subscription_id:
                await callback(response["result"])

For ERC-20 payments monitoring is more complex: need to listen to Transfer events of token contract with filter by to address, not ETH balance.

Working with multiple networks and tokens

Production platform supports minimum: ETH/USDT/USDC on Ethereum, USDT/USDC on Polygon and Arbitrum (cheaper gas), BTC native. Multi-network architecture:

@dataclass
class NetworkConfig:
    chain_id: int
    rpc_url: str
    confirmations_required: int  # 1 for L2, 6 for ETH mainnet
    supported_tokens: dict[str, str]  # symbol -> contract address

NETWORKS = {
    "ethereum": NetworkConfig(
        chain_id=1,
        rpc_url=ETH_RPC,
        confirmations_required=6,
        supported_tokens={"USDT": "0xdAC17...", "USDC": "0xA0b86..."}
    ),
    "arbitrum": NetworkConfig(
        chain_id=42161,
        rpc_url=ARB_RPC,
        confirmations_required=1,
        supported_tokens={"USDT": "0xFd086...", "USDC": "0xFF970..."}
    ),
    # ...
}

Settlement: withdrawal

Collected payments must be aggregated and withdrawn to merchant. Two patterns:

Sweeping — periodic transfer from all addresses to hot wallet:

async def sweep_address(from_address: str, to_address: str, token: str):
    private_key = await kms.get_key(derive_key_path(from_address))

    if token == "ETH":
        balance = w3.eth.get_balance(from_address)
        gas_estimate = 21000
        gas_price = w3.eth.gas_price
        amount = balance - (gas_estimate * gas_price)
        if amount <= 0:
            return

        tx = {
            "to": to_address,
            "value": amount,
            "gas": gas_estimate,
            "gasPrice": gas_price,
            "nonce": w3.eth.get_transaction_count(from_address),
            "chainId": 1
        }
    else:
        # ERC-20 sweep: first ETH for gas, then transfer token
        await fund_gas(from_address)
        tx = build_erc20_transfer(token, from_address, to_address)

    signed = w3.eth.account.sign_transaction(tx, private_key)
    tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
    return tx_hash.hex()

Batch payments — for payouts to merchants, use Multicall or specialized batch transfer contracts (e.g., Disperse.app pattern) — one gas fee for N transfers.

Working with ERC-20: the gas problem

ERC-20 payment weakness: receiving address is empty (no ETH for gas). Before sweep must send small amount of ETH to pay for token transfer gas. This creates dust problem: ETH remains after sweep.

Solution — EIP-2612 Permit: tokens with permit (USDC, DAI) allow signing transfer permission without on-chain approve transaction. Can sweep tokens in one transaction with signature, without pre-funding:

// USDC permit + transferFrom in one call
function sweepWithPermit(
    address token,
    address from,
    address to,
    uint256 amount,
    uint256 deadline,
    uint8 v, bytes32 r, bytes32 s
) external {
    IERC20Permit(token).permit(from, address(this), amount, deadline, v, r, s);
    IERC20(token).transferFrom(from, to, amount);
}

Exchange rates and pricing

Invoice created in fiat currency (USD/EUR), amount in crypto calculated dynamically. For stablecoins — 1:1 rate with small buffer. For volatile assets (ETH, BTC):

def calculate_crypto_amount(fiat_amount: Decimal, currency: str, crypto: str) -> dict:
    # Aggregate price from multiple sources
    prices = await asyncio.gather(
        fetch_binance_price(f"{crypto}USDT"),
        fetch_coinbase_price(f"{crypto}-USD"),
        fetch_chainlink_price(CHAINLINK_FEEDS[crypto])
    )
    median_price = statistics.median(prices)

    crypto_amount = fiat_amount / median_price
    # Add buffer for price movement (1-2%)
    crypto_amount_with_buffer = crypto_amount * Decimal("1.015")

    return {
        "amount": crypto_amount_with_buffer,
        "rate": median_price,
        "expires_at": datetime.now() + timedelta(minutes=15),
        "rate_locked": True
    }

Security and compliance

KYT (Know Your Transaction) — check addresses against sanction lists and connections to mixers/dark markets. Providers: Chainalysis, Elliptic, TRM Labs. API integrated into payment flow:

async def check_address_risk(address: str) -> RiskScore:
    response = await chainalysis_client.post("/v2/address/identifications", json={"address": address})
    risk = response.json()
    if risk["risk"] in ["HIGH", "SEVERE"]:
        await flag_for_review(address)
    return RiskScore(level=risk["risk"], categories=risk["categories"])

Transaction limits — automatic limits by volume for AML: daily limit per wallet, threshold for manual review, mandatory verification for large transactions.

Reconciliation — daily check: sum of merchant payouts + platform fees + balances on sweep addresses should match sum of all confirmed incoming transactions. Any discrepancy — alert.

Infrastructure and reliability

Node vs provider: for payment platform with reliability requirements — own node or Alchemy/Infura with fallback to another provider. Single point of failure is unacceptable.

from web3 import Web3
from web3.middleware import ExceptionRetryMiddleware

providers = [
    Web3.HTTPProvider(ALCHEMY_URL),
    Web3.HTTPProvider(INFURA_URL),
    Web3.HTTPProvider(QUICKNODE_URL),
]

def get_w3_with_fallback():
    for provider in providers:
        w3 = Web3(provider)
        w3.middleware_onion.add(ExceptionRetryMiddleware, retries=3)
        if w3.is_connected():
            return w3
    raise RuntimeError("All providers unavailable")

Idempotency: all operations must be idempotent — repeated webhook processing or payment check retry should not create duplicate payouts. Unique idempotency_key on each operation.

Timelines

Phase Content Duration
Architecture and HD wallet System design, address generation, KMS integration 1–2 weeks
Transaction monitoring Polling/WebSocket monitoring, confirmations 1–2 weeks
Settlement engine Sweep logic, ERC-20 permit, batch payments 2–3 weeks
Merchant API REST API, webhooks, dashboard 2–3 weeks
Compliance KYT integration, limits, reconciliation 1–2 weeks
Testing Testnet, stress testing, security review 2–3 weeks

Minimally viable platform with ETH/USDC on one network: 6–8 weeks. Full multi-network platform with compliance: 3–5 months.