Payment Processing

Complete examples for implementing payment processing patterns including invoicing, recurring payments, and subscription management on BSV.

Overview

Payment processing on BSV goes beyond simple transactions. This guide covers invoice generation, payment verification, recurring payments, subscription management, and payment workflow automation. These patterns are essential for building commercial applications on BSV.

Related SDK Components:

Invoice Generation and Payment

import { Transaction, PrivateKey, P2PKH, Script } from '@bsv/sdk'

/**
 * Invoice Payment Processor
 *
 * Generate and process invoice payments
 */
class InvoiceProcessor {
  /**
   * Generate a payment invoice
   */
  generateInvoice(params: {
    merchantAddress: string
    amount: number
    invoiceId: string
    description: string
    expiresAt?: number
  }): Invoice {
    const invoice: Invoice = {
      id: params.invoiceId,
      merchantAddress: params.merchantAddress,
      amount: params.amount,
      description: params.description,
      createdAt: Date.now(),
      expiresAt: params.expiresAt || Date.now() + 24 * 60 * 60 * 1000, // 24 hours default
      status: 'pending',
      paymentTxid: null
    }

    console.log('Invoice generated')
    console.log('Invoice ID:', invoice.id)
    console.log('Amount:', invoice.amount, 'satoshis')
    console.log('Expires:', new Date(invoice.expiresAt).toISOString())

    return invoice
  }

  /**
   * Process invoice payment
   */
  async processInvoicePayment(
    invoice: Invoice,
    payerKey: PrivateKey,
    utxo: {
      txid: string
      vout: number
      satoshis: number
      script: Script
    }
  ): Promise<{
    tx: Transaction
    invoice: Invoice
  }> {
    try {
      // Validate invoice
      if (invoice.status !== 'pending') {
        throw new Error('Invoice already paid or expired')
      }

      if (Date.now() > invoice.expiresAt) {
        throw new Error('Invoice expired')
      }

      if (utxo.satoshis < invoice.amount + 500) {
        throw new Error('Insufficient funds')
      }

      const tx = new Transaction()

      // Add input
      tx.addInput({
        sourceTXID: utxo.txid,
        sourceOutputIndex: utxo.vout,
        unlockingScriptTemplate: new P2PKH().unlock(payerKey),
        sequence: 0xffffffff
      })

      // Add payment output to merchant
      tx.addOutput({
        satoshis: invoice.amount,
        lockingScript: Script.fromAddress(invoice.merchantAddress)
      })

      // Add change output
      const fee = 500
      const change = utxo.satoshis - invoice.amount - fee

      if (change >= 546) {
        tx.addOutput({
          satoshis: change,
          lockingScript: new P2PKH().lock(payerKey.toPublicKey().toHash())
        })
      }

      await tx.sign()

      // Update invoice
      invoice.status = 'paid'
      invoice.paymentTxid = tx.id('hex')
      invoice.paidAt = Date.now()

      console.log('Invoice payment processed')
      console.log('Invoice ID:', invoice.id)
      console.log('Transaction ID:', tx.id('hex'))

      return { tx, invoice }
    } catch (error) {
      throw new Error(`Invoice payment failed: ${error.message}`)
    }
  }

  /**
   * Verify invoice payment on-chain
   */
  async verifyInvoicePayment(
    invoice: Invoice,
    tx: Transaction
  ): Promise<{
    verified: boolean
    message: string
  }> {
    try {
      if (!invoice.paymentTxid) {
        return {
          verified: false,
          message: 'Invoice has no payment transaction'
        }
      }

      // Check transaction ID matches
      if (tx.id('hex') !== invoice.paymentTxid) {
        return {
          verified: false,
          message: 'Transaction ID mismatch'
        }
      }

      // Check payment output
      let paymentFound = false
      for (const output of tx.outputs) {
        try {
          const outputAddress = output.lockingScript.toAddress()
          if (outputAddress === invoice.merchantAddress && output.satoshis === invoice.amount) {
            paymentFound = true
            break
          }
        } catch {
          // Skip non-address outputs
          continue
        }
      }

      if (!paymentFound) {
        return {
          verified: false,
          message: 'Payment output not found or amount incorrect'
        }
      }

      return {
        verified: true,
        message: 'Invoice payment verified'
      }
    } catch (error) {
      return {
        verified: false,
        message: `Verification failed: ${error.message}`
      }
    }
  }

  /**
   * Generate payment receipt
   */
  generateReceipt(invoice: Invoice): PaymentReceipt {
    if (invoice.status !== 'paid') {
      throw new Error('Invoice not paid')
    }

    return {
      invoiceId: invoice.id,
      amount: invoice.amount,
      merchantAddress: invoice.merchantAddress,
      paymentTxid: invoice.paymentTxid!,
      paidAt: invoice.paidAt!,
      description: invoice.description
    }
  }
}

interface Invoice {
  id: string
  merchantAddress: string
  amount: number
  description: string
  createdAt: number
  expiresAt: number
  status: 'pending' | 'paid' | 'expired'
  paymentTxid: string | null
  paidAt?: number
}

interface PaymentReceipt {
  invoiceId: string
  amount: number
  merchantAddress: string
  paymentTxid: string
  paidAt: number
  description: string
}

/**
 * Usage Example
 */
async function invoiceExample() {
  const processor = new InvoiceProcessor()
  const customerKey = PrivateKey.fromRandom()

  // Merchant generates invoice
  const invoice = processor.generateInvoice({
    merchantAddress: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
    amount: 50000,
    invoiceId: 'INV-001',
    description: 'Website hosting - Monthly'
  })

  // Customer pays invoice
  const utxo = {
    txid: 'customer-utxo...',
    vout: 0,
    satoshis: 100000,
    script: new P2PKH().lock(customerKey.toPublicKey().toHash())
  }

  const { tx, invoice: paidInvoice } = await processor.processInvoicePayment(
    invoice,
    customerKey,
    utxo
  )

  // Verify payment
  const verification = await processor.verifyInvoicePayment(paidInvoice, tx)
  console.log('Payment verified:', verification.verified)

  // Generate receipt
  const receipt = processor.generateReceipt(paidInvoice)
  console.log('Receipt:', receipt)
}

Recurring Payments

Subscription Management

See Also

SDK Components:

Learning Paths:

Last updated