Order Discounts
Order discount adapters handle coupon codes, promotional discounts, and automatic order-level discounts.
For the conceptual overview, see Pricing System.
Creating an adapter
Use the registerOrderDiscount factory. The core hook is discountForPricingAdapterKey — return a discount configuration, or null to not discount.
import { registerOrderDiscount } from '@unchainedshop/core';
registerOrderDiscount({
adapterId: 'automatic-save10',
// Apply to every eligible cart without requiring a code:
isValidForSystemTriggering: async () => true,
discountForPricingAdapterKey: ({ pricingAdapterKey }) =>
pricingAdapterKey === 'shop.unchained.pricing.order-discount' ? { rate: 0.1 } : null,
});
| Option | Purpose |
|---|---|
isValidForSystemTriggering(context) | auto-apply without a code (e.g. first-order discount) |
isValidForCodeTriggering(code, context) | apply for a coupon code |
discountForPricingAdapterKey(params, context) | return { rate } / { fixedRate } for a pricing row, or null |
reserve(code, context) / release(context) | decrement / restore coupon capacity |
Examples
Coupon codes
Manual coupons need isManualAdditionAllowed and isManualRemovalAllowed. The order-discount factory inherits false for these flags, so define a full adapter when customers should enter and remove codes:
import {
OrderDiscountAdapter,
pluginRegistry,
type IDiscountAdapter,
type OrderDiscountConfiguration,
} from '@unchainedshop/core';
const codes = { SAVE10: { rate: 0.1 }, SAVE20: { rate: 0.2 }, DISCOUNT50: { fixedRate: 5000 } };
const Coupons: IDiscountAdapter<OrderDiscountConfiguration> = {
...OrderDiscountAdapter,
key: 'com.example.discount.coupons',
label: 'Coupon codes',
version: '1.0.0',
isManualAdditionAllowed: async () => true,
isManualRemovalAllowed: async () => true,
actions: async ({ context }) => ({
...(await OrderDiscountAdapter.actions({ context })),
isValidForCodeTriggering: async ({ code }) => Object.hasOwn(codes, code),
discountForPricingAdapterKey: ({ pricingAdapterKey }) =>
pricingAdapterKey === 'shop.unchained.pricing.order-discount'
? codes[context.code ?? ''] ?? null
: null,
}),
};
pluginRegistry.register({
key: Coupons.key,
label: Coupons.label,
version: Coupons.version,
adapters: [Coupons],
});
Automatic first-order discount
registerOrderDiscount({
adapterId: 'first-order',
isValidForSystemTriggering: async (context) => {
const previous = await ordersRepository.countConfirmedForUser(context.order.userId);
return previous === 0;
},
discountForPricingAdapterKey: ({ pricingAdapterKey }) =>
pricingAdapterKey === 'shop.unchained.pricing.order-discount' ? { rate: 0.15 } : null,
});
Discount configuration
Returned from discountForPricingAdapterKey:
| Property | Description |
|---|---|
rate | Percentage discount (0.1 = 10%) |
fixedRate | Fixed amount in the order currency's minor units (5000 = 50.00 for a currency with two decimals) |
For fine-grained control of manual code entry/removal (
isManualAdditionAllowed/isManualRemovalAllowed), build the adapter directly by spreadingOrderDiscountAdapterand registering it viapluginRegistry.register(). See Plugin System.
GraphQL
mutation ApplyDiscount($code: String!) {
addCartDiscount(code: $code) { _id code total { amount currencyCode } }
}
mutation RemoveDiscount($discountId: ID!) {
removeCartDiscount(discountId: $discountId) { _id }
}
Related
- Pricing System — conceptual overview
- Plugin Factories —
registerOrderDiscount/registerProductDiscount - Product Pricing