Skip to main content

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

CollectionOperationsPurpose
_store.creditsCRUDCredit request tracking
_store.invoicesReadInvoice validation
_store.productsReadPlatform 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:

  1. 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
  2. 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 ID
  • stripeAccount: 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.


💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM