Skip to content

Commit

Permalink
docs(core): Add docs on promotion side effect API for free gift action
Browse files Browse the repository at this point in the history
Relates to #1798
  • Loading branch information
michaelbromley committed Oct 24, 2022
1 parent e70bb66 commit 5c4c26b
Showing 1 changed file with 94 additions and 1 deletion.
95 changes: 94 additions & 1 deletion docs/content/developer-guide/promotions.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ export const config: VendureConfig = {

## Creating custom actions

There are two kinds of PromotionAction:
There are three kinds of PromotionAction:

- [`PromotionItemAction`]({{< relref "promotion-action" >}}#promotionitemaction) applies a discount on the OrderItem level, i.e. it would be used for a promotion like "50% off USB cables".
- [`PromotionOrderAction`]({{< relref "promotion-action" >}}#promotionorderaction) applies a discount on the Order level, i.e. it would be used for a promotion like "5% off the order total".
- [`PromotionShippingAction`]({{< relref "promotion-action" >}}#promotionshippingaction) applies a discount on the shipping, i.e. it would be used for a promotion like "free shipping".

Their implementations are similar, with the difference being the arguments passed to the `execute()` function of each.

Expand Down Expand Up @@ -157,6 +158,98 @@ export const config: VendureConfig = {
}
```

## Free gift promotions

Vendure v1.8 introduces a new **side effect API** to PromotionActions, which allow you to define some additional action to be performed when a Promotion becomes active or inactive.

A primary use-case of this API is to add a free gift to the Order. Here's an example of a plugin which implements a "free gift" action:

```TypeScript
import {
ID, idsAreEqual, isGraphQlErrorResult, LanguageCode,
Logger, OrderLine, OrderService, PromotionItemAction, VendurePlugin,
} from '@vendure/core';
import { createHash } from 'crypto';

let orderService: OrderService;
export const freeGiftAction = new PromotionItemAction({
code: 'free_gift',
description: [{ languageCode: LanguageCode.en, value: 'Add free gifts to the order' }],
args: {
productVariantIds: {
type: 'ID',
list: true,
ui: { component: 'product-selector-form-input' },
label: [{ languageCode: LanguageCode.en, value: 'Gift product variants' }],
},
},
init(injector) {
orderService = injector.get(OrderService);
},
execute(ctx, orderItem, orderLine, args) {
// This part is responsible for ensuring the variants marked as
// "free gifts" have their price reduced to zero.
if (lineContainsIds(args.productVariantIds, orderLine)) {
const unitPrice = orderLine.unitPrice;
return -unitPrice;
}
return 0;
},
// The onActivate function is part of the side effect API, and
// allows us to perform some action whenever a Promotion becomes active
// due to it's conditions & constraints being satisfied.
async onActivate(ctx, order, args, promotion) {
for (const id of args.productVariantIds) {
if (
!order.lines.find(
(line) =>
idsAreEqual(line.productVariant.id, id) &&
line.customFields.freeGiftDescription == null,
)
) {
// The order does not yet contain this free gift, so add it
const result = await orderService.addItemToOrder(ctx, order.id, id, 1, {
freeGiftPromotionId: promotion.id.toString(),
});
if (isGraphQlErrorResult(result)) {
Logger.error(`Free gift action error for variantId "${id}": ${result.message}`);
}
}
}
},
// The onDeactivate function is the other part of the side effect API and is called
// when an active Promotion becomes no longer active. It should reverse any
// side effect performed by the onActivate function.
async onDeactivate(ctx, order, args, promotion) {
const linesWithFreeGift = order.lines.filter(
(line) => line.customFields.freeGiftPromotionId === promotion.id.toString(),
);
for (const line of linesWithFreeGift) {
await orderService.removeItemFromOrder(ctx, order.id, line.id);
}
},
});

function lineContainsIds(ids: ID[], line: OrderLine): boolean {
return !!ids.find((id) => idsAreEqual(id, line.productVariant.id));
}

@VendurePlugin({
configuration: config => {
config.promotionOptions.promotionActions.push(freeGiftAction);
config.customFields.OrderItem.push(
{
name: 'freeGiftPromotionId',
type: 'string',
public: true,
readonly: true,
nullable: true,
})
}
})
export class FreeGiftPromotionPlugin {}
```

## Dependency relationships

It is possible to establish dependency relationships between a PromotionAction and one or more PromotionConditions.
Expand Down

0 comments on commit 5c4c26b

Please sign in to comment.