Skip to main content

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:

  1. Create Payment Intent - Initialize payment with calculated fees and transfers
  2. Customer Pays - Stripe Elements handles payment method collection (client-side)
  3. 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:

FieldTypeRequiredDescription
sessionIdstringYesCart session identifier
emailstringYesCustomer email for receipt
shippingOptionstringYes"single_address" or "per_artist"
shippingAddressobjectConditionalRequired if shippingOption is "single_address"
shippingAddress.namestringYesRecipient name
shippingAddress.line1stringYesStreet address
shippingAddress.line2stringNoApartment/suite
shippingAddress.citystringYesCity
shippingAddress.statestringYesState/province code
shippingAddress.postal_codestringYesPostal/ZIP code
shippingAddress.countrystringYesCountry 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:

FieldTypeDescription
clientSecretstringStripe PaymentIntent client secret (for Stripe Elements)
publicIdstringGallery order ID (format: GAL-YYYY-XXXXXX)
totalnumberTotal amount customer pays (USD)
breakdown.subtotalnumberSum of all product prices
breakdown.galleryFeenumber12% commission
breakdown.stripeFeenumber2.9% + $0.30
breakdown.totalnumberFinal amount charged
artistsarrayPer-artist breakdown
artists[].orgIdstringArtist organization ID
artists[].artistNamestringArtist display name
artists[].itemCountnumberNumber of items from this artist
artists[].subtotalnumberSubtotal for this artist's items
artists[].artistPayoutnumberAmount 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:

StatusErrorDescription
400Missing required fieldsRequired parameters missing
400Cart is emptyNo items in cart session
400One or more products not foundInvalid product IDs
400One or more products not gallery eligibleProducts removed from gallery
500Failed to create payment intentStripe API error

Payment Processing Logic:

The endpoint performs these operations:

  1. Validate cart - Fetch cart items and verify products exist and are gallery-eligible
  2. Group by artist - Organize items by org_id
  3. Calculate fees:
    subtotal = sum(product.price * quantity)
    galleryFee = subtotal * 0.12
    stripeFee = (subtotal + galleryFee) * 0.029 + 0.30
    total = subtotal + galleryFee + stripeFee
  4. 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 }
    });
  5. Create database records:
    • gallery_orders (parent order)
    • gallery_order_artists (per-artist breakdown)
    • gallery_order_items (individual line items)
  6. 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:

StatusErrorDescription
400Payment intent ID requiredMissing parameter
400Payment not successfulPayment failed or pending
404Order not found for this payment intentInvalid payment intent
500Failed to retrieve payment intentStripe API error

Post-Payment Actions:

  1. Verify payment status - Check paymentIntent.status === 'succeeded'
  2. Update order status - Set to 'paid'
  3. Track attribution - Check for gallery referral cookie and create conversion record
  4. Clear cart - Delete cart items for this session
  5. 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

ComponentCalculationAmount
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

ComponentArtist AArtist BTotal
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_address stores 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:

  1. Find PaymentIntent by payment_intent_id
  2. View Transfers tab
  3. See all artist payouts grouped together

Testing

Test Cards

Use Stripe test mode credit cards:

Card NumberScenario
4242 4242 4242 4242Success
4000 0000 0000 9995Declined (insufficient funds)
4000 0000 0000 0341Declined (security code)
4000 0025 0000 3155Requires 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

  1. Add items to cart (use gallery_eligible: true products)
  2. Create payment intent with test email
  3. Use test card 4242 4242 4242 4242 in Stripe Elements
  4. Confirm payment
  5. Verify order created with status 'paid'
  6. Check Stripe Dashboard for PaymentIntent and Transfers

Next Steps