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
| Status | Description |
|---|---|
pending | Payment initiated, awaiting action |
authorized | Funds held, ready to capture |
captured | Funds collected |
cancelled | Authorization voided |
refunded | Funds returned (partial or full) |
failed | Payment 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.
| Provider | Type | Status |
|---|---|---|
| Square | Card | The card processor. Subscriptions, card-on-file |
| Crypto | Ethereum / Bitcoin | ERC-20, BTC, Lightning |
| Wire | Bank transfer | Always available |
| Stripe | Card, ACH | Legacy. Existing integrations only — never offered for a new charge |
| PayPal | Wallet | Legacy |
| Authorize.net | Card | Legacy |
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 dropSandbox 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 sandboxCreate 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.
- PCI Compliance: Use tokenized payment methods
- Webhook Validation: Always verify webhook signatures
- Idempotency: Use idempotency keys for payment requests
- Amount Verification: Verify amounts server-side before capture
- Fraud Detection: Enable provider fraud protection features
How is this guide?