Store Credit Management
Source: internal/api/v1/store/Controllers/credit.js
Overview
The Credit controller manages store credit requests with multi-stage approval workflows. Credits can be issued for invoice line items or as manual balance adjustments, with automatic application to future invoices.
Key Capabilities
- Create credit requests for invoice line items or manual amounts
- Multi-stage approval workflow (account → provider)
- Platform product detection with application fee handling
- Credit balance tracking
- Requester/requestee access control
MongoDB Collections
| Collection | Operations | Purpose |
|---|---|---|
_store.credits | CRUD | Credit request tracking |
_store.invoices | Read | Invoice validation |
_store.products | Read | Platform product detection |
Service Methods
newCreditRequest
Creates a credit request for invoice line items or manual amount.
Endpoint: POST /store/credits
Request Body (Invoice-Based):
{
invoice: string, // Invoice MongoDB _id
products: [{
id: string, // Product ID
quantity: number,
amount: number // Credit amount per unit
}]
}
Request Body (Manual):
{
amount: number, // Total credit amount
destination: string // Optional: account to credit
}
Response: Created credit request object
Business Logic:
-
Invoice-Based Credits:
- Validate invoice access
- Verify product IDs match invoice line items
- Validate quantities and amounts
- Detect DashClicks platform products
- Flag application fee credits
- Set approval requirements
-
Manual Credits:
- Set destination account
- Require account approval
- Calculate total amount
DashClicks Product Detection:
const dcProducts = await StoreProduct.find({
stripe_id: { $in: product_ids },
$or: [
{ platform_type: 'dashclicks' },
{ $and: [{ platform_type: 'default' }, { account: parent }] },
],
});
if (dcProducts?.length) {
creditRequest.provider_approval_required = true;
creditRequest.provider_approval_status = 'pending';
}
getCreditRequest
Retrieves a single credit request by ID.
Endpoint: GET /store/credits/:id
Authorization: Must be requester or requestee
Response: Credit request with populated invoice, requester, requestee
listCreditRequests
Lists credit requests with optional type filtering.
Endpoint: GET /store/credits
Query Parameters:
{
page: number,
limit: number,
type: 'requester' | 'requestee' // Optional filter
}
Response: Paginated credit request list
creditRequestAction
Executes credit request actions (approve, deny, cancel).
Endpoint: POST /store/credits/:id/actions/:action
Supported Actions: approve, deny, cancel
Business Logic:
APPROVE Action:
// Invoice-based credit
if (creditRequest.invoice) {
if (provider_approval_required && provider_approval_status === 'approved') {
// Both approvals granted
creditRequest.stripe_credit = {
customer: invoice.customer,
stripeAccount: invoice.account_id,
application_fees: sum_of_application_fee_amounts,
};
creditRequest.status = 'approved';
creditRequest.amount_remaining = total_amount;
} else {
creditRequest.account_approval_status = 'approved';
}
}
// Manual credit
else {
creditRequest.stripe_credit = {
customer: requester.stripe_customer,
stripeAccount: requestee.stripe_connected_account,
};
creditRequest.status = 'approved';
creditRequest.amount_remaining = creditRequest.amount;
}
Credit Storage: Credits stored in database with stripe_credit object containing:
customer: Stripe customer IDstripeAccount: Connected account (or null for platform)application_fees: Amount of application fees to credit
DENY Action:
- Sets
status = 'denied' - Sets
account_approval_status = 'denied' - Sets
denied_by_requestee = true
CANCEL Action:
- Only requester can cancel
- Sets
status = 'canceled' - Sets
canceled_by_requester = true
Approval Workflow
Two-Stage Approval (Platform Products)
graph TD
A[Create Credit Request] --> B{DashClicks Product?}
B -->|No| C[account_approval_required only]
B -->|Yes| D[provider + account approval required]
C --> E[Account Approves]
D --> F[Provider Approves]
F --> G[Account Approves]
E --> H[Credit Available]
G --> H
H --> I[Applied to Next Invoice]
Edge Cases & Business Rules
1. Credit Application
Credits are stored in database but NOT immediately applied to Stripe customer balance. They are applied when:
- Next invoice is generated
- Manual application via separate endpoint
- Webhook processes upcoming invoice
2. Application Fee Credits
For platform products, credits include application fee portions:
creditRequest.products.forEach(product => {
if (product.platform_type === 'default') {
product.application_fee_refund = true;
}
});
const feeAmount = products.reduce(
(sum, p) => (p.application_fee_refund ? sum + p.amount * p.quantity : sum),
0,
);
3. Amount Validation
Maximum credit cannot exceed invoice line item amount minus discounts:
const maxCreditAllowed = line_amount - discount_amounts.reduce((sum, d) => sum + d.amount, 0);
if (product.amount > maxCreditAllowed) {
throw new Error('INVALID_PRODUCT_INFORMATION');
}
4. Immutable States
Once credit is approved, denied, or canceled, no further actions allowed.
Related Documentation
- Invoices - Invoice management
- Refunds - Refund processing
- Subscriptions - Subscription credits