AstroBaaS

Orders & fulfilment

Exchange-First Returns (Store Credits & Swaps Before Refunds)

Paid pluginsize Mplanned, not built

Indicative price, not an offer: $15-29/mo

Generated from docs/plan/paid/exchange-first-returns/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Full refunds hurt margins. This paid module enables merchants to offer exchanges or store credit first, with refunds as a last resort. Includes advanced return routing logic and store-credit creation.

The problem

Every return becomes a refund, which kills my margins. I need to offer exchanges first (same item, different size/color), then store credit, and only refund if customer declines both.

What it does

  • Add exchange-first workflow to returns: customer can select exchange (different variant) before requesting return
  • Create store_credits collection: {id, customer_id, amount_minor, reason (return|promotion), created_at, balance, expires_at (optional)}
  • Allow staff to issue store credit as alternative to refund (e.g., issue $50 credit instead of $50 refund)
  • Customer can use store credit on next purchase; deduct from cart total at checkout
  • Track store credit usage in store_credits.transactions: [{order_id, amount_deducted, timestamp}]
  • Optional expiration: set store credit validity (e.g., 1 year from issue date)
  • Admin dashboard shows store credit balances and expiration status
  • Email notifications: issue store credit, credit expiring soon, credit expired

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Store credit as a gift card or promotional currency (beyond returns) — that is loyalty/promotions system, separate
  • Partial store credit (e.g., refund $30, credit $20) — that is complex accounting, can be extension
  • Store credit transferability between customers — intentionally not supported for tax/compliance reasons

Data model

store_credits collection: {id, customer_id, amount_minor, reason, created_at, balance, expires_at, transactions: [{order_id, amount_deducted, timestamp}]}. returns.refund_method enum: refund | store_credit. No migration if columns are additive.

API

  • POST /api/admin/returns/:returnId/offer-exchange — suggest exchange options to customer
  • POST /api/admin/store-credits — create store credit for customer
  • GET /api/customers/:customerId/store-credits — fetch customer’s store credit balances
  • POST /api/carts/:cartId/apply-store-credit — apply store credit to cart at checkout

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Return approval flow adds ‘Refund Method’ dropdown: ‘Refund to Card’ or ‘Store Credit’. If store credit selected, input ‘Amount’ and ‘Expiration Date’, confirm. Customer receives email: ‘Store credit issued: $X, expires MM/DD/YYYY’. Store Credits section in admin shows: customer name, balance, expiration date, transaction history.

The seam — why this is paid

Paid module owns exchange-first workflow, store-credit creation, and advanced refund routing. Core (returns-exchanges) owns basic return state machine and RMA. Merchant must pay for advanced return logic; core provides foundation.

Paid module owns exchange priority engine, store-credit creation, and refund-decline workflow logic

Dependencies

  • returns-exchanges (core feature)
  • Existing customers collection
  • email layer (for store credit notifications)
  • payments/checkout system (to apply store credit at cart)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • Issue store credit for $50 to customer; confirm store_credits collection stores amount_minor=5000, reason=‘return’
  • Set expiration date 1 year from now; confirm expires_at is recorded
  • Apply store credit to cart at checkout; confirm $50 deducted from total
  • Verify store_credits.transactions logs {order_id, amount_deducted, timestamp} when credit is used
  • Set expiration to 1 day ago and verify admin shows ‘Expired’ badge
  • Verify customer cannot apply expired store credit to cart

Risks

Store credit can be exploited: customer requests refund, receives credit, then requests refund again. Implement audit trail: refund_method must be recorded in returns, and prevent dual-refund. Tax implications: store credit may be taxable income in some jurisdictions; document in merchant setup docs.

Commercial context

Suggested price$15-29/mo
Rival anchorRich Returns, Return Prime (freemium, $9-99/mo for advanced return routing logic)

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.