Hanzo

Payments

Process payments with multiple providers including crypto

Hanzo Commerce supports multiple payment providers including Stripe, PayPal, Authorize.net, and cryptocurrency payments via Ethereum and Bitcoin.

Payment Flow

Cart → Order → Authorize → Capture → Complete

              (Hold/Cancel)

Payment Statuses

StatusDescription
pendingPayment initiated, awaiting action
authorizedFunds held, ready to capture
capturedFunds collected
cancelledAuthorization voided
refundedFunds returned (partial or full)
failedPayment failed

Supported Providers

Square is the card processor. Stripe is not surfaced for new charges — the payment-method surface a storefront renders is Square, crypto and wire.

ProviderTypeStatus
SquareCardThe card processor. Subscriptions, card-on-file
CryptoEthereum / BitcoinERC-20, BTC, Lightning
WireBank transferAlways available
StripeCard, ACHLegacy. Existing integrations only — never offered for a new charge
PayPalWalletLegacy
Authorize.netCardLegacy

Square Integration

Setup

Square credentials belong in KMS, never in a repo or a plain environment file. The deployment reads them under these names:

SQUARE_ACCESS_TOKEN=            # secret — KMS only
SQUARE_APPLICATION_ID=          # public — the browser tokenizes with this
SQUARE_LOCATION_ID=             # public
SQUARE_ENVIRONMENT=production   # or: sandbox
SQUARE_WEBHOOK_SIGNATURE_KEY=   # secret — KMS only; without it every
                                # webhook fails signature and renewals drop

Sandbox uses the parallel SQUARE_SANDBOX_ACCESS_TOKEN, SQUARE_SANDBOX_APPLICATION_ID and SQUARE_SANDBOX_LOCATION_ID.

Which environment a charge uses

Sandbox-versus-production is org state, not an environment variable: an organization carries a Live flag, and TestMode() is !Live. An org that is not live tokenizes and charges against Square sandbox even where the deployment is configured for production. Flip it with:

POST /v1/billing/mode   { "testMode": false }

That endpoint is admin-only by design — a user must not be able to move their own org to sandbox to dodge real charges.

This route was /v1/billing/test-mode until the billing surface dropped its compound names, alongside /v1/billing/spend-alerts/v1/billing/alerts. If mode 404s, the rename has not reached that deployment yet.

Fetch the public config the browser needs (application id, location id, environment) from GET /v1/billing/payment-config, or, for a storefront resolving by host, GET /v1/commerce/tenant. Both answer from the same authority, so the application the browser tokenizes with always matches the one the charge is made against.

Stripe Integration (legacy)

Kept for existing integrations. Do not wire a new storefront to it — use Square above.

Setup

STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Authorize Payment

curl -X POST https://api.hanzo.ai/checkout/authorize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentMethod": "stripe",
    "token": "tok_visa"
  }'

For payment intents (recommended):

curl -X POST https://api.hanzo.ai/checkout/authorize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentMethod": "stripe",
    "paymentIntentId": "pi_1234567890"
  }'

3D Secure

Handle 3D Secure authentication:

{
  "status": "requires_action",
  "action": {
    "type": "redirect_to_url",
    "redirectUrl": "https://hooks.stripe.com/3d_secure_2/...",
    "returnUrl": "https://yourstore.com/checkout/complete"
  }
}

Capture Payment

curl -X POST https://api.hanzo.ai/checkout/capture \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentId": "pay_xyz789"
  }'

Partial Capture

Capture less than the authorized amount:

curl -X POST https://api.hanzo.ai/checkout/capture \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentId": "pay_xyz789",
    "amount": 3000
  }'

PayPal Integration

Setup

PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_MODE=live  # or sandbox

Create PayPal Order

curl -X POST https://api.hanzo.ai/checkout/authorize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentMethod": "paypal",
    "returnUrl": "https://yourstore.com/checkout/complete",
    "cancelUrl": "https://yourstore.com/checkout/cancel"
  }'

Response:

{
  "status": "requires_action",
  "action": {
    "type": "redirect_to_url",
    "redirectUrl": "https://www.paypal.com/checkoutnow?token=..."
  },
  "paypalOrderId": "5O190127TN364715T"
}

Capture After Approval

curl -X POST https://api.hanzo.ai/checkout/capture \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paypalOrderId": "5O190127TN364715T"
  }'

Cryptocurrency Payments

Ethereum

curl -X POST https://api.hanzo.ai/checkout/authorize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentMethod": "ethereum",
    "currency": "ETH"
  }'

Response:

{
  "status": "pending",
  "paymentAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
  "amount": "0.0234",
  "currency": "ETH",
  "expiresAt": "2024-01-15T11:00:00Z",
  "qrCode": "data:image/png;base64,..."
}

Bitcoin

curl -X POST https://api.hanzo.ai/checkout/authorize \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentMethod": "bitcoin"
  }'

Response:

{
  "status": "pending",
  "paymentAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkf...",
  "amount": "0.00123456",
  "currency": "BTC",
  "expiresAt": "2024-01-15T11:00:00Z"
}

Monitor Blockchain Transactions

Payments are automatically confirmed when transactions are detected:

{
  "status": "confirmed",
  "txHash": "0x123abc...",
  "confirmations": 12,
  "blockNumber": 18500000
}

Refunds

Full Refund

curl -X POST https://api.hanzo.ai/checkout/refund \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentId": "pay_xyz789",
    "reason": "customer_request"
  }'

Partial Refund

curl -X POST https://api.hanzo.ai/checkout/refund \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "order_abc123",
    "paymentId": "pay_xyz789",
    "amount": 1500,
    "reason": "partial_return"
  }'

Webhooks

Configure webhooks to receive payment events:

curl -X POST https://api.hanzo.ai/webhook \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourserver.com/webhooks/hanzo",
    "events": [
      "payment.authorized",
      "payment.captured",
      "payment.refunded",
      "payment.failed"
    ]
  }'

Webhook Payload

{
  "id": "evt_123",
  "type": "payment.captured",
  "data": {
    "paymentId": "pay_xyz789",
    "orderId": "order_abc123",
    "amount": 6578,
    "currency": "USD",
    "provider": "stripe"
  },
  "createdAt": "2024-01-15T10:35:00Z"
}

Verify Webhook Signature

import "github.com/hanzoai/commerce/util/webhook"

func handleWebhook(c *gin.Context) {
    payload, err := io.ReadAll(c.Request.Body)
    signature := c.GetHeader("X-Webhook-Signature")

    if err := webhook.Verify(payload, signature, webhookSecret); err != nil {
        c.JSON(401, gin.H{"error": "invalid signature"})
        return
    }

    // Process event
}

SDK Examples

JavaScript

import { Commerce } from '@hanzo/commerce'

const commerce = new Commerce({ apiKey: 'your_key' })

// Authorize with Stripe
const payment = await commerce.checkout.authorize({
  orderId: 'order_abc123',
  paymentMethod: 'stripe',
  paymentIntentId: 'pi_1234567890'
})

// Handle 3D Secure if needed
if (payment.status === 'requires_action') {
  window.location.href = payment.action.redirectUrl
}

// Capture payment
await commerce.checkout.capture({
  orderId: 'order_abc123',
  paymentId: payment.id
})

// Refund
await commerce.checkout.refund({
  orderId: 'order_abc123',
  paymentId: payment.id,
  amount: 1500,
  reason: 'partial_return'
})

Go

// Authorize payment
payment, err := client.Checkout.Authorize(ctx, &sdk.AuthorizeInput{
    OrderID:       "order_abc123",
    PaymentMethod: "stripe",
    Token:         "tok_visa",
})

// Capture payment
err = client.Checkout.Capture(ctx, &sdk.CaptureInput{
    OrderID:   "order_abc123",
    PaymentID: payment.ID,
})

// Refund
err = client.Checkout.Refund(ctx, &sdk.RefundInput{
    OrderID:   "order_abc123",
    PaymentID: payment.ID,
    Amount:    1500,
    Reason:    "partial_return",
})

Security Best Practices

Never log or store full payment tokens or card numbers. Use tokenization provided by payment processors.

  1. PCI Compliance: Use tokenized payment methods
  2. Webhook Validation: Always verify webhook signatures
  3. Idempotency: Use idempotency keys for payment requests
  4. Amount Verification: Verify amounts server-side before capture
  5. Fraud Detection: Enable provider fraud protection features

How is this guide?

On this page