Documentation Index
Fetch the complete documentation index at: https://docs.withflex.com/llms.txt
Use this file to discover all available pages before exploring further.
Important IDs to store:
checkout_session_id - Extract from redirect URL (e.g., fcs_01kbjcmbt5mhsmaggfqc1a4rns)
- This ID is required for creating refunds
You can also retrieve the checkout session to get additional details:
GET /v1/checkout/sessions/{checkout_session_id}
Response includes:
payment_intent_id - Flex’s internal payment ID
latest_charge - Charge ID
status - Should be complete for refunds
Primary Refund Endpoint
POST /v1/checkout/sessions//refund
This is the main refund endpoint for processing refunds on completed checkout sessions.
Base URL: https://api.withflex.com (or your custom domain)
Authentication:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Option 1: Line Item-Based Refund (Granular Control)
Refund specific products/prices with custom amounts:
{
"checkout_session": {
"line_items": [
{
"product": "fprod_01k3m6j3zqcvy6pbj1a65sszpw",
"amount_to_refund": 2000
},
{
"product": "fprod_01k3m6r0qj2bpc25q6h56r3jar",
"amount_to_refund": 1500
}
],
"amount_tax": 350,
"amount_shipping": 500,
"amount_discount": 200,
"refund_metadata": {
"OrderNo": "ORDER123",
"Source": "Customer_Request",
"Reason": "Damaged_Product"
}
}
}
Alternative: Use price instead of product
{
"checkout_session": {
"line_items": [
{
"price": "fprice_01k3m6j3zqcvy6pbj1a65sszpw",
"amount_to_refund": 2000
}
],
"refund_metadata": {
"reason": "customer_request"
}
}
}
Option 2: Simple Amount-Based Refund
Refund a specific total amount without line item breakdown:
{
"checkout_session": {
"amount": 5000,
"refund_metadata": {
"reason": "customer_not_satisfied",
"order_id": "12345"
}
}
}
Option 3: Full Refund
Refund the entire checkout session amount:
{
"checkout_session": {
"refund_metadata": {
"reason": "order_cancelled"
}
}
}
Or simply:
{
"checkout_session": {}
}
Request Parameters
| Field | Type | Required | Description |
|---|
line_items | array | No | Specific line items to refund with amounts |
line_items[].product | string | One of | Product ID to refund (e.g., fprod_xxx) |
line_items[].price | string | product/price | Price ID to refund (e.g., fprice_xxx) |
line_items[].amount_to_refund | integer | No | Amount in cents for this item (omit for full item refund) |
amount | integer | No | Total amount to refund in cents (alternative to line_items) |
amount_tax | integer | No | Tax amount to refund in cents |
amount_shipping | integer | No | Shipping amount to refund in cents |
amount_discount | integer | No | Discount amount to refund in cents |
refund_metadata | object | No | Key-value pairs for tracking (order ID, reason, etc.) |
Success Response
Status Code: 200 OK
{
"checkout_session": {
"checkout_session_id": "fcs_01kbjcmbt5mhsmaggfqc1a4rns",
"status": "complete",
"amount_total": 10000,
"amount_subtotal": 9000,
"currency": "usd",
"customer": {
"customer_id": "fcus_xxx",
"email": "customer@example.com",
"name": "John Doe"
},
"payment_intent": {
"payment_intent_id": "fpi_xxx",
"amount": 10000,
"amount_received": 10000,
"status": "succeeded"
},
"line_items": [...],
"refunds": [
{
"refund_id": "fref_01kbjdxyz123",
"payment_intent_id": "fpi_xxx",
"amount": 3500,
"status": "pending",
"reason": null,
"created_at": "2024-12-04T10:30:00Z",
"test_mode": false,
"metadata": {
"OrderNo": "ORDER123",
"Source": "Customer_Request"
},
"reference_id": null,
"reference_type": "acquirer_reference_number",
"reference_status": "pending",
"items": [
{
"amount_refunded": 3500,
"payment_intent": "fpi_xxx",
"reference_id": null,
"reference_type": "acquirer_reference_number",
"reference_status": "pending"
}
]
}
]
}
}
Key Response Fields:
refunds[].refund_id - Store this for tracking refund status
refunds[].status - Current refund status (pending, succeeded, failed)
refunds[].reference_id - Acquirer Reference Number (ARN) for bank tracking (populated later)
refunds[].reference_status - Status of reference number (pending, available, unavailable)
Error Responses
Checkout Session Not Complete (422 Unprocessable Entity)
{
"error": {
"type": "validation_error",
"message": "Checkout session must be complete",
"errors": [
{
"loc": ["status"],
"msg": "Checkout session must be complete",
"type": "invalid_request_error"
}
]
}
}
Cause: Checkout session status is not complete
Checkout Session Not Found (404 Not Found)
{
"error": {
"type": "not_found",
"message": "checkout session does not exist"
}
}
Cause: Invalid checkout_session_id or doesn’t belong to your account
Unauthorized (401 Unauthorized)
{
"error": {
"type": "unauthorized",
"message": "Invalid API key"
}
}
Cause: Missing or invalid Authorization header
Refund Statuses
| Status | Description |
|---|
pending | Refund initiated, processing in progress |
requires_action | Additional action required (rare) |
succeeded | Refund completed, funds returned to customer |
failed | Refund failed (usually insufficient funds in connected account) |
canceled | Refund was canceled |
Typical Timeline:
- Refund status updates to
succeeded within minutes to hours
- Funds appear in customer’s account within 5-10 business days (varies by bank)
Reference Numbers for Tracking
Flex provides reference numbers that can be used to track refunds with banks and payment networks.
Reference Types
| Type | Description |
|---|
acquirer_reference_number | ARN - Most common, used for tracking with issuing banks |
system_trace_audit_number | Alternative tracking number for payment networks |
retrieval_reference_number | Used for dispute resolution and chargebacks |
Reference Status
| Status | Description |
|---|
pending | Reference number not yet available from payment network |
available | Reference number ready, can be provided to customer/bank |
unavailable | Payment network doesn’t provide this reference type |
Note: Reference IDs typically become available within hours after the refund is created.
Learn More: Stripe Reference Numbers Guide
Complete Workflow Examples
Example 1: Full Refund
Step 1: Customer completes payment
You receive redirect after successful payment:
https://yoursite.com/success?session_id=fcs_01kbjcmbt5mhsmaggfqc1a4rns
Step 2: Customer requests full refund
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"checkout_session": {
"refund_metadata": {
"reason": "customer_cancelled_order",
"order_id": "ORD-12345",
"requested_by": "customer_service"
}
}
}'
Step 3: Response
{
"checkout_session": {
"checkout_session_id": "fcs_01kbjcmbt5mhsmaggfqc1a4rns",
"status": "complete",
"amount_total": 15000,
"refunds": [
{
"refund_id": "fref_01kbjdxyz123",
"amount": 15000,
"status": "pending",
"created_at": "2024-12-04T10:30:00Z",
"metadata": {
"reason": "customer_cancelled_order",
"order_id": "ORD-12345"
}
}
]
}
}
Step 4: Track refund status (optional)
curl -X GET https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
-H "Authorization: Bearer sk_live_xxx"
Example 2: Partial Refund by Line Items
Scenario: Customer returns 1 of 3 items purchased
Request:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"checkout_session": {
"line_items": [
{
"product": "fprod_yoga_mat",
"amount_to_refund": 3500
}
],
"amount_tax": 280,
"amount_shipping": 0,
"refund_metadata": {
"reason": "item_returned",
"rma_number": "RMA-2024-1234",
"returned_items": "Yoga Mat (Blue)"
}
}
}'
Explanation:
- Refunding only the yoga mat: $35.00
- Including proportional tax: $2.80
- Not refunding shipping (customer kept other items)
- Total refund: $37.80
Example 3: Partial Refund by Amount
Scenario: Price adjustment without item-level detail
Request:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"checkout_session": {
"amount": 2000,
"refund_metadata": {
"reason": "price_match_guarantee",
"original_amount": 15000,
"adjusted_amount": 13000,
"competitor": "CompetitorStore"
}
}
}'
Explanation:
- Refunding $20.00 difference due to price match
- No line item breakdown needed
- Metadata tracks the reason
Example 4: Multiple Partial Refunds
You can create multiple refunds for the same checkout session until the full amount is refunded.
First refund: $50
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_xxx/refund \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"checkout_session": {"amount": 5000}}'
Second refund: $30
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_xxx/refund \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"checkout_session": {"amount": 3000}}'
Important: Track the total refunded amount to avoid over-refunding.
Secondary Refund API
Tracking and Listing Refunds
After creating a refund via the checkout session endpoint, use the secondary refund API to track status:
GET /v1/refunds/
curl -X GET https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
-H "Authorization: Bearer sk_live_xxx"
Response:
{
"refund": {
"refund_id": "fref_01kbjdxyz123",
"payment_intent_id": "fpi_xxx",
"amount": 5000,
"status": "succeeded",
"reason": null,
"created_at": "2024-12-04T10:30:00Z",
"test_mode": false,
"metadata": {
"order_id": "12345"
},
"reference_id": "ARN8493274932",
"reference_type": "acquirer_reference_number",
"reference_status": "available",
"items": [...]
}
}
GET /v1/refunds (List Refunds)
Query Parameters:
payment_intent - Filter by payment intent ID
checkout_session - Filter by checkout session ID
status - Filter by status
Examples:
# All refunds
curl -X GET https://api.withflex.com/v1/refunds \
-H "Authorization: Bearer sk_live_xxx"
# Refunds for specific checkout session
curl -X GET https://api.withflex.com/v1/refunds?checkout_session=fcs_01kbjcmbt5mhsmaggfqc1a4rns \
-H "Authorization: Bearer sk_live_xxx"
# Succeeded refunds only
curl -X GET https://api.withflex.com/v1/refunds?status=succeeded \
-H "Authorization: Bearer sk_live_xxx"
Response:
{
"refunds": [
{
"refund_id": "fref_01kbjdxyz123",
"payment_intent_id": "fpi_xxx",
"amount": 5000,
"status": "succeeded",
"created_at": "2024-12-04T10:30:00Z",
...
},
{
"refund_id": "fref_01kbjdabc456",
"payment_intent_id": "fpi_yyy",
"amount": 3000,
"status": "pending",
"created_at": "2024-12-03T15:20:00Z",
...
}
]
}
Update refund metadata or reason (does not change amount or status):
curl -X PATCH https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"refund": {
"reason": "fraudulent",
"metadata": {
"updated_by": "admin",
"investigation_id": "INV-2024-001"
}
}
}'
Implementation Details
Automatic Handling by Flex
Flex automatically handles:
✅ Reverse Transfer - Funds are clawed back from the connected account (partner) ✅ Application Fee Retention - Flex keeps the platform fee (not refunded) ✅ Webhook Processing - Status updates via Stripe webhooks ✅ Charge Tracking - Records which charges were refunded in refund_charge table ✅ Reference Number Retrieval - ARN and other tracking numbers populated when available
Partial Refund Behavior
- Can refund any amount up to the original charge amount
- Multiple partial refunds allowed until full amount refunded
- Track
amount_received vs total refunded to calculate remaining refundable amount
Tax, Shipping, and Discount Refunds
When refunding line items, you can specify component amounts:
{
"checkout_session": {
"line_items": [
{
"product": "fprod_xxx",
"amount_to_refund": 5000
}
],
"amount_tax": 400, // Proportional tax for refunded items
"amount_shipping": 500, // Shipping refund
"amount_discount": 250 // Discount adjustment
}
}
Best Practice: Calculate proportional tax when refunding partial line items.
Best Practices
1. Store Checkout Session ID
Always save the checkout_session_id after payment completion:
// After payment redirect
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('session_id');
// Store in your database
await db.orders.update({
orderId: orderId,
flexCheckoutSessionId: sessionId
});
2. Verify Checkout Session Status
Before attempting refund, confirm the session is complete:
const session = await fetch(
`https://api.withflex.com/v1/checkout/sessions/${sessionId}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
).then(r => r.json());
if (session.checkout_session.status !== 'complete') {
throw new Error('Cannot refund incomplete checkout session');
}
Always include metadata to track refund context:
{
"checkout_session": {
"amount": 5000,
"refund_metadata": {
"order_id": "ORD-12345",
"reason": "customer_request",
"requested_by": "customer_service_agent_42",
"ticket_id": "TICKET-789",
"timestamp": "2024-12-04T10:30:00Z"
}
}
}
4. Handle Refund Webhooks
Subscribe to Flex webhooks for automatic refund status updates:
Event Types:
refund.created - Refund initiated
refund.succeeded - Refund completed successfully
refund.failed - Refund failed
Webhook Payload Example:
{
"type": "refund.succeeded",
"data": {
"object": {
"refund_id": "fref_xxx",
"payment_intent_id": "fpi_xxx",
"amount": 5000,
"status": "succeeded",
"reference_id": "ARN123456789"
}
}
}
5. Track Remaining Refundable Amount
For multiple partial refunds, track the remaining amount:
const session = await getCheckoutSession(sessionId);
const totalCharged = session.amount_total;
const totalRefunded = session.refunds.reduce((sum, r) => sum + r.amount, 0);
const remainingRefundable = totalCharged - totalRefunded;
console.log(`Can still refund: $${remainingRefundable / 100}`);
6. Save Reference Numbers
Store ARNs for customer support inquiries:
const refund = await getRefund(refundId);
if (refund.reference_status === 'available') {
await db.refunds.update({
refundId: refundId,
arn: refund.reference_id,
referenceType: refund.reference_type
});
// Provide to customer for bank tracking
console.log(`ARN: ${refund.reference_id}`);
}
7. Handle Errors Gracefully
try {
const response = await fetch(refundUrl, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify(refundRequest)
});
if (!response.ok) {
const error = await response.json();
if (error.error.type === 'validation_error') {
console.error('Validation error:', error.error.message);
// Handle validation errors
} else if (response.status === 404) {
console.error('Checkout session not found');
// Handle not found
} else {
console.error('Unexpected error:', error);
// Handle other errors
}
}
const result = await response.json();
console.log('Refund created:', result.checkout_session.refunds[0].refund_id);
} catch (error) {
console.error('Network error:', error);
// Handle network errors
}
Testing
Test Mode
Use test API keys (starting with sk_test_) to test refunds without affecting real money:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_test_xxx/refund \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{"checkout_session": {}}'
Test Mode Behavior:
- Refunds complete instantly with
status: "succeeded"
- No actual money movement
- Reference IDs still generated for testing
- Full webhook flow works
Test Credentials
For testing payments → refunds flow:
Email: miguel@homefit.com Password: password
Create test API keys at: http://localhost:3001/partners/dashboard/developers
Troubleshooting
Problem: “Checkout session must be complete”
Cause: Trying to refund a checkout session that hasn’t been paid or is in progress
Solution:
- Check session status:
GET /v1/checkout/sessions/{id}
- Ensure
status === "complete"
- Wait for payment to finish if still processing
Problem: “checkout session does not exist”
Cause: Invalid checkout session ID or doesn’t belong to your account
Solution:
- Verify the
checkout_session_id is correct
- Ensure you’re using the right API key (test vs. production)
- Check that the session belongs to your partner account
Problem: Refund shows “pending” for too long
Cause: Stripe processing delay or connected account issues
Solution:
- Wait up to 24 hours for normal processing
- Check Stripe dashboard for errors
- Verify connected account has sufficient balance
- Contact support if still pending after 24 hours
Problem: “Failed to create refund”
Cause: Stripe API error (usually insufficient funds in connected account)
Solution:
- Check connected account balance in Stripe dashboard
- Ensure partner account has funds to cover refund
- Review Stripe logs for specific error details
- Contact Flex support with
checkout_session_id
Problem: Reference ID not available
Cause: Payment network hasn’t provided ARN yet
Solution:
- Reference IDs typically appear within hours
- Check
reference_status - if pending, wait
- If
unavailable, payment network doesn’t provide this type
- Poll
/v1/refunds/{refund_id} periodically for updates
API Endpoint Summary
| Endpoint | Method | Purpose |
|---|
/v1/checkout/sessions/{id}/refund | POST | Primary - Create refund for checkout session |
/v1/refunds | GET | List all refunds with filters |
/v1/refunds/{refund_id} | GET | Get single refund details |
/v1/refunds/{refund_id} | PATCH | Update refund metadata |
/v1/checkout/sessions/{id} | GET | Get checkout session details |
Common Use Cases
Use Case 1: Customer Service Full Refund
async function processFullRefund(checkoutSessionId, reason) {
const response = await fetch(
`https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
checkout_session: {
refund_metadata: {
reason: reason,
processed_by: 'customer_service',
timestamp: new Date().toISOString()
}
}
})
}
);
const result = await response.json();
const refund = result.checkout_session.refunds[0];
console.log(`Refund created: ${refund.refund_id}`);
return refund;
}
Use Case 2: Return Processing with Line Items
async function processReturn(checkoutSessionId, returnedItems) {
// returnedItems = [{ productId, amount }, ...]
const lineItems = returnedItems.map(item => ({
product: item.productId,
amount_to_refund: item.amount
}));
const response = await fetch(
`https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
checkout_session: {
line_items: lineItems,
amount_tax: calculateProportionalTax(returnedItems),
refund_metadata: {
reason: 'items_returned',
rma_number: generateRmaNumber()
}
}
})
}
);
return await response.json();
}
Use Case 3: Price Adjustment
async function applyPriceAdjustment(checkoutSessionId, adjustmentAmount, reason) {
const response = await fetch(
`https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
checkout_session: {
amount: adjustmentAmount,
refund_metadata: {
reason: 'price_adjustment',
adjustment_type: reason,
original_ticket: 'TICKET-123'
}
}
})
}
);
return await response.json();
}
Additional Resources
Support
For issues or questions about refunds:
- Check this guide for common solutions
- Review Stripe dashboard for payment details
- Contact Flex support with:
checkout_session_id
refund_id (if refund was created)
- Error messages
- Timestamp of the issue