Checkout API
The Checkout API handles payment processing for multi-artist gallery orders using Stripe Connect. It creates payment intents, splits payments across artists, and confirms successful transactions.
Overview
The checkout flow consists of three steps:
- Create Payment Intent - Initialize payment with calculated fees and transfers
- Customer Pays - Stripe Elements handles payment method collection (client-side)
- Confirm Payment - Verify payment succeeded and finalize order
Endpoints
Create Payment Intent
Creates a Stripe PaymentIntent with automatic transfer splitting to artist Connect accounts.
Endpoint: POST /api/gallery/checkout
Authentication: None (uses cart session ID)
Request Body:
{
"sessionId": "cart_1234567890",
"email": "buyer@example.com",
"shippingOption": "single_address",
"shippingAddress": {
"name": "John Doe",
"line1": "123 Main St",
"line2": "Apt 4B",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
}
Request Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
sessionId | string | Yes | Cart session identifier |
email | string | Yes | Customer email for receipt |
shippingOption | string | Yes | "single_address" or "per_artist" |
shippingAddress | object | Conditional | Required if shippingOption is "single_address" |
shippingAddress.name | string | Yes | Recipient name |
shippingAddress.line1 | string | Yes | Street address |
shippingAddress.line2 | string | No | Apartment/suite |
shippingAddress.city | string | Yes | City |
shippingAddress.state | string | Yes | State/province code |
shippingAddress.postal_code | string | Yes | Postal/ZIP code |
shippingAddress.country | string | Yes | Country code (ISO 3166-1 alpha-2) |
Response:
{
"clientSecret": "pi_1234567890_secret_abcdef",
"publicId": "GAL-2024-ABC123",
"total": 115.20,
"breakdown": {
"subtotal": 100.00,
"galleryFee": 12.00,
"stripeFee": 3.20,
"total": 115.20
},
"artists": [
{
"orgId": "org_abc123",
"artistName": "Jane Smith",
"itemCount": 2,
"subtotal": 60.00,
"artistPayout": 52.80
},
{
"orgId": "org_def456",
"artistName": "Bob Johnson",
"itemCount": 1,
"subtotal": 40.00,
"artistPayout": 35.20
}
]
}
Response Fields:
| Field | Type | Description |
|---|---|---|
clientSecret | string | Stripe PaymentIntent client secret (for Stripe Elements) |
publicId | string | Gallery order ID (format: GAL-YYYY-XXXXXX) |
total | number | Total amount customer pays (USD) |
breakdown.subtotal | number | Sum of all product prices |
breakdown.galleryFee | number | 12% commission |
breakdown.stripeFee | number | 2.9% + $0.30 |
breakdown.total | number | Final amount charged |
artists | array | Per-artist breakdown |
artists[].orgId | string | Artist organization ID |
artists[].artistName | string | Artist display name |
artists[].itemCount | number | Number of items from this artist |
artists[].subtotal | number | Subtotal for this artist's items |
artists[].artistPayout | number | Amount artist receives (88% of subtotal) |
Example Request:
curl -X POST https://gallery.artbase.studio/api/gallery/checkout \
-H "Content-Type: application/json" \
-d '{
"sessionId": "cart_abc123",
"email": "buyer@example.com",
"shippingOption": "single_address",
"shippingAddress": {
"name": "John Doe",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
}'
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Missing required fields | Required parameters missing |
| 400 | Cart is empty | No items in cart session |
| 400 | One or more products not found | Invalid product IDs |
| 400 | One or more products not gallery eligible | Products removed from gallery |
| 500 | Failed to create payment intent | Stripe API error |
Payment Processing Logic:
The endpoint performs these operations:
- Validate cart - Fetch cart items and verify products exist and are gallery-eligible
- Group by artist - Organize items by
org_id - Calculate fees:
subtotal = sum(product.price * quantity)
galleryFee = subtotal * 0.12
stripeFee = (subtotal + galleryFee) * 0.029 + 0.30
total = subtotal + galleryFee + stripeFee - Create Stripe PaymentIntent with transfer group:
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(total * 100), // cents
currency: 'usd',
transfer_group: transferGroup,
receipt_email: email,
metadata: { order_id: publicId, cart_session_id: sessionId }
}); - Create database records:
gallery_orders(parent order)gallery_order_artists(per-artist breakdown)gallery_order_items(individual line items)
- Schedule Stripe Transfers (after payment succeeds):
await stripe.transfers.create({
amount: Math.round(artistPayout * 100),
currency: 'usd',
destination: artist.stripe_connect_account_id,
transfer_group: transferGroup,
});
Confirm Payment
Verifies payment succeeded and finalizes the order.
Endpoint: POST /api/gallery/checkout/confirm
Authentication: None (uses payment intent ID)
Request Body:
{
"paymentIntentId": "pi_1234567890"
}
Response:
{
"success": true,
"order": {
"id": "order_abc123",
"publicId": "GAL-2024-ABC123",
"status": "paid",
"total": 115.20,
"email": "buyer@example.com",
"createdAt": "2024-01-15T10:30:00Z"
}
}
Example Request:
curl -X POST https://gallery.artbase.studio/api/gallery/checkout/confirm \
-H "Content-Type: application/json" \
-d '{
"paymentIntentId": "pi_1234567890"
}'
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Payment intent ID required | Missing parameter |
| 400 | Payment not successful | Payment failed or pending |
| 404 | Order not found for this payment intent | Invalid payment intent |
| 500 | Failed to retrieve payment intent | Stripe API error |
Post-Payment Actions:
- Verify payment status - Check
paymentIntent.status === 'succeeded' - Update order status - Set to
'paid' - Track attribution - Check for gallery referral cookie and create conversion record
- Clear cart - Delete cart items for this session
- Send confirmations - Email customer and notify artists (via
sendOrderConfirmationEmail,sendArtistOrderNotificationEmail)
Fee Calculation
Gallery uses transparent pricing with fees added to the customer total:
Formula
subtotal = Σ(product_price × quantity)
gallery_commission = subtotal × 0.12
stripe_fee = (subtotal + gallery_commission) × 0.029 + 0.30
customer_total = subtotal + gallery_commission + stripe_fee
artist_payout = product_price × 0.88
Example: $100 Product
| Component | Calculation | Amount |
|---|---|---|
| Product price | - | $100.00 |
| Gallery commission (12%) | $100 × 0.12 | $12.00 |
| Stripe fee (2.9% + $0.30) | ($100 + $12) × 0.029 + $0.30 | $3.55 |
| Customer pays | $100 + $12 + $3.55 | $115.55 |
| Artist receives | $100 × 0.88 | $88.00 |
Multi-Artist Example: $200 Total (2 Artists)
Artist A: 2 items @ $60 each = $120 Artist B: 1 item @ $80 = $80
| Component | Artist A | Artist B | Total |
|---|---|---|---|
| Subtotal | $120.00 | $80.00 | $200.00 |
| Gallery commission (12%) | $14.40 | $9.60 | $24.00 |
| Stripe fee (2.9% + $0.30) | - | - | $6.80 |
| Customer pays | - | - | $230.80 |
| Artist payout (88%) | $105.60 | $70.40 | $176.00 |
Shipping Options
Single Address
Most common option - all artists ship to the same address.
{
"shippingOption": "single_address",
"shippingAddress": {
"name": "John Doe",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
}
- Address stored in
gallery_orders.shipping_address(JSONB) - All artists see the same shipping address
- Customer receives multiple packages at one location
Per-Artist Addresses
For sending to multiple recipients (e.g., gifts).
{
"shippingOption": "per_artist"
}
- No address provided at checkout
- Customer receives email with link to specify addresses per artist
gallery_order_artists.shipping_addressstores individual addresses- More complex but flexible
Transfer Groups
Stripe Transfer Groups organize artist payouts:
Format: gallery_{orderId}_{timestamp}
Example: gallery_abc123_1640000000000
Benefits:
- Links all transfers to a single order
- Enables refund reconciliation
- Provides audit trail in Stripe Dashboard
Viewing in Stripe:
- Find PaymentIntent by
payment_intent_id - View Transfers tab
- See all artist payouts grouped together
Testing
Test Cards
Use Stripe test mode credit cards:
| Card Number | Scenario |
|---|---|
4242 4242 4242 4242 | Success |
4000 0000 0000 9995 | Declined (insufficient funds) |
4000 0000 0000 0341 | Declined (security code) |
4000 0025 0000 3155 | Requires authentication (3D Secure) |
Test Mode
Set environment variable:
STRIPE_SECRET_KEY=sk_test_...
All test payments use Stripe test mode and don't charge real cards.
Example Test Flow
- Add items to cart (use
gallery_eligible: trueproducts) - Create payment intent with test email
- Use test card
4242 4242 4242 4242in Stripe Elements - Confirm payment
- Verify order created with status
'paid' - Check Stripe Dashboard for PaymentIntent and Transfers