From 5c4c26b48a8e47cc4eb20a137276c828b9edf975 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 24 Oct 2022 21:08:21 +0200 Subject: [PATCH] docs(core): Add docs on promotion side effect API for free gift action Relates to #1798 --- docs/content/developer-guide/promotions.md | 95 +++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/content/developer-guide/promotions.md b/docs/content/developer-guide/promotions.md index 90bc73ec13..336919fc57 100644 --- a/docs/content/developer-guide/promotions.md +++ b/docs/content/developer-guide/promotions.md @@ -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. @@ -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.