Crypto billing system 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 billing system development
Medium
~1-2 weeks
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 Billing Systems

The difference between "accept a crypto payment" and "issue a crypto invoice" is fundamental. An invoice is a legal document with fixed amount, due date, counterparty ID, and reconciliation capability. Most turnkey solutions stop at the first — they provide a payment address. Full billing requires accounting, reminders, partial payments, multi-currency, and accounting system integration.

Invoice lifecycle

DRAFT → SENT → PENDING_PAYMENT → PARTIALLY_PAID → PAID | OVERDUE | CANCELLED

Each transition is an event with timestamp and transaction data. For audit, statuses aren't overwritten but appended.

interface Invoice {
    id: string              // UUID
    number: string          // readable: INV-2024-0042
    issuerId: string        // organization/wallet
    clientId: string
    clientWallet?: string   // if known
    issuedAt: Date
    dueDate: Date
    lineItems: LineItem[]
    baseCurrency: string    // USD/EUR — invoice currency
    subtotalFiat: Decimal
    taxAmountFiat: Decimal
    totalFiat: Decimal
    acceptedTokens: AcceptedToken[]  // payment options
    paymentAddress: string  // unique deposit address
    status: InvoiceStatus
    payments: InvoicePayment[]  // received partial/full payments
}

interface AcceptedToken {
    token: string           // contract address
    chain: string
    amountEquiv: Decimal    // amount in tokens at current rate
    rateLockedAt?: Date     // if rate is fixed
    rateLockExpiry?: Date   // until when fixed rate is valid
}

Generating unique payment addresses

Each invoice gets a unique address for receiving payments — key to automatic matching incoming transactions without manual memo/tag.

HD wallet derivation (BIP-32):

import { HDNodeWallet } from 'ethers'

class InvoiceAddressGenerator {
    private xpub: string  // master public key, never private key

    generateAddress(invoiceIndex: number): string {
        const node = HDNodeWallet.fromExtendedKey(this.xpub)
        // Derivation path: m/0/{invoiceIndex}
        return node.deriveChild(0).deriveChild(invoiceIndex).address
    }

    async createInvoiceAddress(invoiceId: string): Promise<string> {
        // Atomically get next index
        const index = await this.db.transaction(async (trx) => {
            const result = await trx('address_counter')
                .increment('counter', 1)
                .returning('counter')
            return result[0].counter
        })

        const address = this.generateAddress(index)

        await this.db('invoice_addresses').insert({
            invoice_id: invoiceId,
            address,
            derivation_index: index,
        })

        return address
    }
}

One address per invoice enables automatic matching incoming transactions via address monitoring (Alchemy Notify, Moralis Streams, or custom event listener).

Monitoring incoming payments

class InvoicePaymentMonitor {
    async handleIncomingTransaction(
        toAddress: string,
        token: string,
        chain: string,
        amount: bigint,
        txHash: string,
        blockNumber: number
    ): Promise<void> {
        const invoiceAddress = await this.db('invoice_addresses')
            .where({ address: toAddress.toLowerCase() })
            .first()

        if (!invoiceAddress) return  // not our address

        const invoice = await this.getInvoice(invoiceAddress.invoice_id)

        if (!['sent', 'pending_payment', 'partially_paid'].includes(invoice.status)) {
            // Invoice already paid or cancelled — alert for manual processing
            await this.alertUnexpectedPayment(invoice, txHash, amount)
            return
        }

        // Wait for confirmations before crediting
        await this.pendingPayments.add({
            invoiceId: invoice.id,
            txHash,
            blockNumber,
            token,
            chain,
            amount,
        })
    }

    async processConfirmedPayment(pendingPayment: PendingPayment): Promise<void> {
        const invoice = await this.getInvoice(pendingPayment.invoiceId)
        const tokenPrice = await this.priceService.getHistoricalPrice(
            pendingPayment.token,
            pendingPayment.chain,
            pendingPayment.confirmedAt
        )

        const fiatEquivalent = new Decimal(pendingPayment.amount.toString())
            .div(10 ** TOKEN_DECIMALS)
            .mul(tokenPrice)

        await this.db.transaction(async (trx) => {
            await trx('invoice_payments').insert({
                invoice_id: invoice.id,
                tx_hash: pendingPayment.txHash,
                token: pendingPayment.token,
                chain: pendingPayment.chain,
                crypto_amount: pendingPayment.amount.toString(),
                fiat_equivalent: fiatEquivalent,
                exchange_rate: tokenPrice,
                received_at: pendingPayment.confirmedAt,
            })

            const totalPaid = await this.getTotalPaidFiat(invoice.id, trx)
            const newStatus = totalPaid.gte(invoice.total_fiat)
                ? 'paid'
                : 'partially_paid'

            await trx('invoices')
                .where({ id: invoice.id })
                .update({ status: newStatus, updated_at: new Date() })
        })

        await this.notifyPaymentReceived(invoice, fiatEquivalent)
    }
}

Rate locking and volatility protection

For B2B invoicing, clients may request fixed rate for 1-24 hours. Reduces uncertainty — client knows exactly how much USDC to send. For seller, it's risk if token drops (relevant for volatile tokens, not stablecoins).

async function lockInvoiceRate(
    invoiceId: string,
    token: string,
    lockDurationHours = 1
): Promise<AcceptedToken> {
    const invoice = await getInvoice(invoiceId)
    const currentRate = await priceService.getRate('USD', token)

    const tokenAmount = invoice.totalFiat.div(currentRate)
    const expiry = new Date(Date.now() + lockDurationHours * 3600 * 1000)

    await db('invoice_accepted_tokens')
        .where({ invoice_id: invoiceId, token })
        .update({
            amount_equiv: tokenAmount,
            rate_locked_at: new Date(),
            rate_lock_expiry: expiry,
            locked_rate: currentRate,
        })

    return { token, amountEquiv: tokenAmount, rateLockExpiry: expiry }
}

After rateLockExpiry expires, amount recalculates at current rate — client gets notification.

PDF generation and legal form

Invoices must look like invoices, not blockchain dumps. Generate PDF with QR code to payment address and amount:

import PDFDocument from 'pdfkit'
import QRCode from 'qrcode'

async function generateInvoicePdf(invoice: Invoice): Promise<Buffer> {
    const doc = new PDFDocument({ margin: 50 })
    const buffers: Buffer[] = []

    doc.on('data', chunk => buffers.push(chunk))

    // Header and details
    doc.fontSize(20).text(`Invoice ${invoice.number}`, 50, 50)
    doc.fontSize(10)
        .text(`Issued: ${format(invoice.issuedAt, 'dd MMM yyyy')}`)
        .text(`Due: ${format(invoice.dueDate, 'dd MMM yyyy')}`)

    // Line items table, totals...

    // QR code with EIP-681 URI for convenient payment
    const paymentUri = `ethereum:${invoice.paymentAddress}?value=${invoice.totalFiat}`
    const qrBuffer = await QRCode.toBuffer(paymentUri, { width: 150 })
    doc.image(qrBuffer, 400, 650, { width: 100 })
    doc.fontSize(8).text('Scan to pay', 410, 755)

    doc.end()
    return new Promise(resolve =>
        doc.on('end', () => resolve(Buffer.concat(buffers)))
    )
}

Reminders and automation

Background job for overdue invoices:

// Runs every hour
async function processOverdueInvoices(): Promise<void> {
    const overdue = await db('invoices')
        .where('status', 'in', ['sent', 'pending_payment', 'partially_paid'])
        .where('due_date', '<', new Date())

    for (const invoice of overdue) {
        const daysPastDue = differenceInDays(new Date(), invoice.due_date)

        if (daysPastDue === 1 || daysPastDue === 3 || daysPastDue === 7) {
            await emailService.sendOverdueReminder(invoice, daysPastDue)
        }

        if (invoice.status !== 'overdue') {
            await db('invoices').where({ id: invoice.id }).update({ status: 'overdue' })
        }
    }
}

Stack: Node.js/TypeScript backend, PostgreSQL, Bull/BullMQ for background tasks (payment monitoring, reminders), React frontend for client and internal interface. Integrations: Alchemy Notify for webhooks on incoming transactions, SendGrid/Postmark for email. Development time for basic system with multi-currency billing and automatic matching — 2–3 weeks.