Payments API Reference
Overview
The Payments API provides comprehensive payment processing capabilities supporting multiple payment providers, mixed tender payments (voucher + external payment), refund processing, and no-show fee management.
Supported Payment Providers:
- Stripe - Credit/debit card payments (Authorize & Capture pattern)
- Paytrail - Finnish payment methods (bank transfers, mobile payments)
- Vouchers - Gift cards and promotional vouchers
- Payment on Arrival (PoA) - Pay at venue after arrival confirmation
Key Features:
- Multi-provider payment orchestration
- Mixed tender support (partial voucher + external payment)
- Webhook-based payment confirmation
- Signature verification and replay protection
- Automatic refund processing
- No-show fee assessment and charging
Authentication
All payment endpoints require proper authentication:
- Webhooks: Use provider-specific signature verification (no Bearer token required)
- Admin endpoints: Require
Authorization: Bearer <token>header with admin permissions - Customer endpoints: Require valid authentication token
All requests must include the x-tenant-id header for multi-tenant isolation.
Payment Providers
Credential Resolution (site → tenant → environment)
Every charge, refund, and provider adapter is built against credentials resolved from the booking's context in a fixed precedence:
- Site — the booking's site, if it has its own gated payment credentials (
site_payment_settingswithenable_custom_payments = true) - Tenant — the tenant's configured gateway credentials
- Environment — the platform's credentials
Provider-specific rules:
- Stripe resolves site → tenant → not-configured. It never falls back to the platform environment key for customer charges — an unconfigured site/tenant yields a not-configured result rather than charging into the platform account.
- Paytrail resolves site → tenant → environment. The
PaytrailAdapterFactoryexposesforSite(tenantId, siteId)(site-aware) alongside the existingforTenant(tenantId); adapters are cached by a hash of the resolvedmerchantId:secretKey, so site and tenant credentials occupy distinct cache entries.
The resolved source is tracked as site, tenant, or env. The same precedence is applied to charges, refunds, group / mixed-tender settlement, and the site-aware Paytrail stale-payment poller, so a payment and its refund always settle against the same account. See Per-Site Payment Credentials for the admin configuration side.
1. Stripe Integration
Authorize & Capture Pattern
Stripe payments use a two-phase authorization and capture flow:
- Authorization: Hold funds on customer's card during booking hold
- Capture: Charge the authorized amount when booking is confirmed
Benefits:
- Funds are reserved but not charged until confirmation
- Prevents double charges if customer abandons booking
- Supports partial captures (charge less than authorized amount)
Stripe Payment Flow
Create Booking Hold
↓
Authorize Payment (Stripe PaymentIntent)
↓
Customer confirms booking
↓
Capture Payment
↓
Webhook confirms payment
↓
Booking status → PAID
Webhook Endpoint
Endpoint: POST /payments/stripe/webhook
Purpose: Receives payment event notifications from Stripe
Headers:
stripe-signature: t=1234567890,v1=signature_hash
content-type: application/json
Security Features:
- Signature Verification: Validates webhook authenticity using Stripe signing secret
- Idempotency: Prevents duplicate processing of same event
- Replay Protection: Detects and rejects tampered signatures
- Automatic Retry: Stripe retries failed webhooks automatically
Common Event Types:
payment_intent.succeeded- Payment completed successfullypayment_intent.payment_failed- Payment failedcharge.refunded- Refund processedcharge.dispute.created- Chargeback initiated
Response:
{
"received": true,
"processed": true,
"eventId": "evt_1234567890",
"message": "Event processed successfully"
}
Error Handling:
400 Bad Request- Invalid signature or missing headers (Stripe won't retry)500 Internal Server Error- Processing failed (Stripe will retry)
2. Paytrail Integration
Payment Creation Flow
Paytrail supports Finnish payment methods including bank transfers, mobile payments, and credit cards.
Flow:
Create Booking Hold
↓
Create Paytrail Payment
↓
Redirect customer to Paytrail payment page
↓
Customer completes payment
↓
Paytrail callback to webhook
↓
Booking status → PAID
Webhook Endpoint
Endpoint: POST /webhooks/paytrail/callback
Purpose: Receives payment completion callbacks from Paytrail
Headers:
signature: calculated_hmac_signature
checkout-account: merchant_account_id
checkout-algorithm: sha256
checkout-method: POST
checkout-timestamp: 2025-11-17T12:00:00Z
checkout-nonce: unique_nonce_value
Request Body:
{
"checkout-stamp": "booking-123-external-1234567890",
"checkout-transaction-id": "paytrail_tx_abc123",
"checkout-status": "ok",
"checkout-amount": "10000",
"checkout-reference": "booking-123",
"checkout-provider": "nordea"
}
Callback Statuses:
ok- Payment successfulfail- Payment failedpending- Payment pending (bank transfer)delayed- Payment delayed
Security Features:
- HMAC Signature Verification: Validates callback authenticity
- Idempotency: Prevents duplicate processing using idempotency keys
- Replay Protection: Detects and rejects duplicate callbacks
Response:
{
"status": "success",
"message": "Callback processed successfully",
"idempotency_key": "idem_paytrail_abc123xyz"
}
3. Mixed Tender Payments
Mixed tender allows customers to pay with a combination of voucher and external payment (Stripe/Paytrail).
Mixed Tender Flow
Total Amount: €100
↓
Apply Voucher (€30 balance)
↓
Voucher redeemed: €30
Remaining: €70
↓
External Payment (Stripe/Paytrail): €70
↓
Both payments recorded
↓
Booking status → PAID
Payment Orchestration
Process:
- Voucher Redemption: Attempt to redeem voucher code first
- Balance Calculation: Calculate remaining amount after voucher
- External Payment: Process remaining balance via Stripe/Paytrail
- Transaction Recording: Record both payments under same booking
Example Request:
{
"booking_id": "booking_abc123",
"customer_id": "cus_xyz789",
"total_amount": 10000,
"currency": "EUR",
"tenant_id": "t_demo",
"voucher_code": "SUMMER2025",
"payment_provider": "stripe",
"payment_method_id": "pm_card_visa",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel"
}
Example Response:
{
"success": true,
"total_amount_charged": 10000,
"voucher_payment": {
"voucher_code": "SUMMER2025",
"voucher_transaction_id": "vt_abc123",
"amount_redeemed": 3000,
"remaining_voucher_balance": 0
},
"external_payment": {
"provider": "stripe",
"payment_intent_id": "pi_xyz789",
"amount_charged": 7000,
"requires_action": false,
"client_secret": "pi_xyz789_secret_abc"
}
}
Refund Processing
Refund Orchestrator
The platform supports automatic refunds across all payment providers with fee calculation.
Refund Flow
Cancellation Request
↓
Calculate Refund Amount (apply cancellation fee policy)
↓
Create Refund Request
↓
Process Refund via Provider
↓
Update Booking Status
↓
Refund Confirmation
Refund Calculation
Refunds are calculated based on tenant-specific cancellation policies:
- Flat Fee: Deduct fixed amount (e.g., €10)
- Percentage Fee: Deduct percentage of total (e.g., 10%)
- Time-based: Fee depends on time until booking (e.g., no fee if
>24h, 50% fee if<24h)
Mixed Payment Refunds
For bookings paid with voucher + external payment:
Refund Priority:
- External payment refunded first (Stripe/Paytrail)
- Voucher credited back (balance restored)
Example:
Original Payment:
- Voucher: €30
- Stripe: €70
Total: €100
Refund (with 10% fee):
- Fee: €10
- Refund: €90
Refund Distribution:
- Stripe refund: €70 (full external payment)
- Voucher credit: €20 (partial voucher amount)
Refund Response Format
{
"success": true,
"refund_id": "ref_abc123",
"total_refund_amount": 9000,
"fee_deducted": 1000,
"net_refund_amount": 9000,
"refund_breakdown": [
{
"payment_method": "stripe",
"original_amount": 7000,
"refund_amount": 7000,
"provider_refund_id": "re_stripe_xyz"
},
{
"payment_method": "voucher",
"original_amount": 3000,
"refund_amount": 2000,
"voucher_new_balance": 2000
}
]
}
No-Show Fee Processing
Fee Assessment Service
Automatically charges no-show fees when customers fail to arrive for their bookings.
No-Show Flow
Customer misses booking
↓
Admin marks as NO_SHOW
↓
Calculate fee based on policy
↓
Charge fee using original payment method
↓
Update booking status
↓
Fee recorded in ledger
Fee Assessment Request
Endpoint: POST /admin/no-shows/process-fee (admin only)
Request:
{
"booking_id": "booking_abc123",
"tenant_id": "t_demo",
"customer_id": "cus_xyz789",
"assessed_by": "admin_user_123",
"assessment_reason": "Customer did not arrive for scheduled booking",
"override_fee_amount": 2000
}
Response:
{
"success": true,
"fee_assessment_id": "fee_abc123",
"calculated_fee_amount": 2000,
"charged_fee_amount": 2000,
"fee_type": "flat",
"payment_method_used": "stripe",
"charge_response": {
"charge_id": "ch_xyz789",
"charged_amount": 2000,
"provider_fees": 58
},
"metadata": {
"policy_applied": "tenant_no_show_policy",
"calculation_details": {
"base_amount": 2000,
"multiplier": 1.0
},
"payment_attempts": 1,
"assessment_timestamp": "2025-11-17T12:00:00.000Z"
}
}
Payment Security
Webhook Security
All webhook endpoints implement comprehensive security measures:
1. Signature Verification
Stripe:
- Uses
stripe-signatureheader - HMAC signature with timestamp
- Validates against webhook signing secret
Paytrail:
- Uses
signatureheader - SHA-256 HMAC signature
- Includes checkout headers in signature calculation
2. Idempotency
Event Deduplication:
- Each webhook event has unique ID
- System tracks processed event IDs
- Duplicate events return cached result without reprocessing
Benefits:
- Prevents double charges
- Safe for automatic retries
- Maintains data consistency
3. Replay Protection
Tampered Signature Detection:
- Checks for obviously malformed signatures
- Validates signature format before processing
- Rejects signatures with suspicious patterns
Timestamp Validation:
- Verifies event timestamp is recent
- Rejects events older than threshold (e.g., 5 minutes)
- Prevents replay of old events
Payment Status Flow
Status Transitions
NOT_STARTED → PENDING → AUTHORIZED → CAPTURED → COMPLETED
↓
FAILED
CAPTURED → REFUND_PENDING → REFUNDED
Status Definitions:
- NOT_STARTED: No payment initiated
- PENDING: Payment created, awaiting customer action
- AUTHORIZED: Funds held (Stripe authorize phase)
- CAPTURED: Funds charged (Stripe capture phase)
- COMPLETED: Payment fully processed and confirmed
- FAILED: Payment failed at any stage
- REFUND_PENDING: Refund initiated
- REFUNDED: Refund completed
Error Handling
Common Error Codes
| Error Code | HTTP Status | Description | Recovery |
|---|---|---|---|
PAYMENT_FAILED | 400 | Payment processing failed | Retry with different payment method |
INVALID_SIGNATURE | 403 | Webhook signature invalid | Check webhook signing secret |
DUPLICATE_EVENT | 200 | Event already processed | No action needed (idempotent) |
INSUFFICIENT_FUNDS | 400 | Voucher balance too low | Use different voucher or add external payment |
PROVIDER_ERROR | 502 | Payment provider error | Retry after delay |
REFUND_FAILED | 500 | Refund processing failed | Manual intervention required |
Error Response Format
{
"error": {
"code": "PAYMENT_FAILED",
"message": "Payment processing failed: card declined",
"details": {
"provider": "stripe",
"provider_error_code": "card_declined",
"retry_allowed": true,
"suggested_action": "Try different payment method"
}
}
}
Best Practices
1. Webhook Implementation
Do:
- ✅ Verify signatures before processing
- ✅ Return
200 OKquickly to acknowledge receipt - ✅ Process events asynchronously if needed
- ✅ Log all webhook attempts for monitoring
- ✅ Implement idempotency for safe retries
Don't:
- ❌ Process webhooks without signature verification
- ❌ Perform long-running operations synchronously
- ❌ Trust webhook data without validation
- ❌ Ignore duplicate event detection
2. Payment Processing
Do:
- ✅ Use idempotency keys for all payment operations
- ✅ Store correlation IDs for payment tracking
- ✅ Handle partial payments correctly (mixed tender)
- ✅ Implement proper error handling and retries
- ✅ Log all payment attempts for auditing
Don't:
- ❌ Charge customers before booking confirmation
- ❌ Skip capture phase for authorized payments
- ❌ Process payments without tenant context
3. Refund Processing
Do:
- ✅ Calculate fees based on cancellation policy
- ✅ Refund external payments before crediting vouchers
- ✅ Record all refund attempts in audit log
- ✅ Notify customers of refund status
Don't:
- ❌ Refund without verifying booking status
- ❌ Process refunds without fee calculation
- ❌ Skip refund confirmation notifications
Testing
Stripe Test Mode
Test Card Numbers:
Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
3D Secure: 4000 0025 0000 3155
Test Webhook Events: Use Stripe CLI to send test webhooks:
stripe trigger payment_intent.succeeded
Paytrail Test Mode
Test Configuration:
PAYTRAIL_MERCHANT_ID=test_merchant_id
PAYTRAIL_SECRET_KEY=test_secret_key
PAYTRAIL_WEBHOOK_SECRET=test_webhook_secret
Test Payment Status:
- Success: Use test callback with
checkout-status=ok - Failure: Use test callback with
checkout-status=fail
Monitoring & Observability
Payment Metrics
Track:
- Payment success/failure rates
- Average processing time
- Webhook delivery latency
- Refund processing time
- No-show fee collection rate
Webhook Health Check
Stripe Health Check:
POST /payments/stripe/webhook/health
Paytrail Health Check:
POST /webhooks/paytrail/health
Response:
{
"status": "healthy",
"timestamp": "2025-11-17T12:00:00.000Z",
"stats": {
"total_events_processed": 1250,
"events_last_hour": 42,
"duplicate_events_rejected": 8,
"failed_events": 2
}
}
Payment on Arrival (PoA)
Payment on Arrival allows customers to pay at the venue instead of online. The booking is confirmed without upfront payment and transitions through a PoA-specific lifecycle.
For the full PoA flow including arrival confirmation, payment collection methods, late arrival handling, and no-show fee assessment, see the Payment on Arrival Guide.
Key endpoints for PoA:
POST /api/v1/bookings/{id}/confirmwithpaymentMethod: "poa"— confirm a PoA bookingPOST /api/v1/bookings/{id}/mark-arrived— record customer arrival and optionally collect paymentPOST /api/v1/bookings/{id}/mark-paid— record payment received at venue
Related Documentation
- Bookings API Reference - Booking lifecycle and payment integration
- Payment on Arrival Guide - Full PoA workflow and configuration
- Promotions API - Voucher redemption and promotion codes
- Developer Guide - Architecture - Payment module architecture
Support
For payment integration questions or issues:
- Check webhook logs for detailed error messages
- Verify webhook signing secrets are correctly configured
- Monitor payment status in admin dashboard
- Contact support for provider-specific issues