@lodashventure/medusa-campaign 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.medusa/server/src/admin/index.js +939 -504
  2. package/.medusa/server/src/admin/index.mjs +941 -506
  3. package/.medusa/server/src/api/admin/buy-x-get-y/[id]/route.js +2 -6
  4. package/.medusa/server/src/api/admin/coupons/[id]/route.js +76 -0
  5. package/.medusa/server/src/api/admin/coupons/route.js +88 -0
  6. package/.medusa/server/src/api/middlewares.js +32 -1
  7. package/.medusa/server/src/api/store/campaigns/route.js +78 -7
  8. package/.medusa/server/src/api/store/coupons/public/route.js +110 -0
  9. package/.medusa/server/src/api/store/customers/me/coupons/route.js +148 -0
  10. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251024000000.js +2 -2
  11. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251025000000.js +2 -2
  12. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251101000000.js +16 -0
  13. package/.medusa/server/src/modules/custom-campaigns/types/campaign-type.enum.js +2 -1
  14. package/.medusa/server/src/workflows/custom-campaign/createCouponCampaignWorkflow.js +105 -0
  15. package/.medusa/server/src/workflows/custom-campaign/updateCouponCampaignWorkflow.js +59 -0
  16. package/.medusa/server/src/workflows/index.js +6 -2
  17. package/package.json +15 -30
  18. package/src/admin/components/BuyXGetYForm.tsx +24 -13
  19. package/src/admin/components/CouponForm.tsx +352 -0
  20. package/src/admin/components/CouponPage.tsx +104 -0
  21. package/src/admin/components/ProductSelector.tsx +22 -11
  22. package/src/admin/hooks/useCouponById.ts +36 -0
  23. package/src/admin/hooks/useCoupons.ts +46 -0
  24. package/src/admin/hooks/useFlashSaleById.ts +36 -10
  25. package/src/admin/hooks/useFlashSales.ts +36 -10
  26. package/src/admin/routes/coupons/[id]/page.tsx +147 -0
  27. package/src/admin/routes/coupons/create/page.tsx +49 -0
  28. package/src/admin/routes/coupons/page.tsx +15 -0
  29. package/src/admin/routes/flash-sales/[id]/page.tsx +2 -11
  30. package/src/admin/routes/flash-sales/create/page.tsx +0 -6
  31. package/src/admin/widgets/campaign-detail-widget.tsx +33 -26
  32. package/src/api/admin/buy-x-get-y/[id]/route.ts +11 -15
  33. package/src/api/admin/coupons/[id]/route.ts +98 -0
  34. package/src/api/admin/coupons/route.ts +109 -0
  35. package/src/api/middlewares.ts +34 -0
  36. package/src/api/store/campaigns/route.ts +107 -24
  37. package/src/api/store/coupons/public/route.ts +165 -0
  38. package/src/api/store/customers/me/coupons/route.ts +244 -0
  39. package/src/modules/custom-campaigns/migrations/Migration20251024000000.ts +1 -1
  40. package/src/modules/custom-campaigns/migrations/Migration20251025000000.ts +1 -1
  41. package/src/modules/custom-campaigns/migrations/Migration20251101000000.ts +21 -0
  42. package/src/modules/custom-campaigns/types/campaign-type.enum.ts +1 -0
  43. package/src/workflows/custom-campaign/createCouponCampaignWorkflow.ts +176 -0
  44. package/src/workflows/custom-campaign/updateCouponCampaignWorkflow.ts +105 -0
  45. package/src/workflows/index.ts +3 -1
  46. package/src/admin/widgets/campaign-stats-widget.tsx +0 -238
@@ -0,0 +1,244 @@
1
+ import {
2
+ AuthenticatedMedusaRequest,
3
+ MedusaResponse,
4
+ container,
5
+ } from "@medusajs/framework";
6
+ import { MedusaError, Modules } from "@medusajs/framework/utils";
7
+ import z from "zod";
8
+ import { CUSTOM_CAMPAIGN_MODULE } from "../../../../../modules/custom-campaigns";
9
+ import CustomCampaignModuleService from "../../../../../modules/custom-campaigns/service";
10
+ import { CampaignTypeEnum } from "../../../../../modules/custom-campaigns/types/campaign-type.enum";
11
+
12
+ type PromotionCacheService = {
13
+ scan: <T>(pattern: string) => Promise<T[]>;
14
+ set: (key: string, value: unknown, ttl?: number) => Promise<void>;
15
+ exists: (key: string) => Promise<boolean>;
16
+ };
17
+
18
+ type UsablePromoModuleService = {
19
+ listUsablePromotions: (
20
+ selector: Record<string, unknown>,
21
+ ) => Promise<
22
+ Array<{
23
+ id: string;
24
+ promotion_id: string;
25
+ enabled: boolean;
26
+ }>
27
+ >;
28
+ };
29
+
30
+ const PROMOTION_CACHE_MODULE = "promotion_cache";
31
+ const USABLE_PROMO_MODULE = "usable_promotion";
32
+
33
+ const resolveOptional = <T>(token: string): T | null => {
34
+ try {
35
+ return container.resolve<T>(token);
36
+ } catch {
37
+ return null;
38
+ }
39
+ };
40
+
41
+ export const collectCouponSchema = z.object({
42
+ coupon_id: z.string().min(1, "coupon_id is required"),
43
+ });
44
+
45
+ export const GET = async (
46
+ req: AuthenticatedMedusaRequest,
47
+ res: MedusaResponse,
48
+ ) => {
49
+ const customerId = req.auth_context.actor_id;
50
+
51
+ if (!customerId) {
52
+ throw new MedusaError(
53
+ MedusaError.Types.NOT_ALLOWED,
54
+ "Customer must be authenticated",
55
+ );
56
+ }
57
+
58
+ const promoCacheService =
59
+ container.resolve<PromotionCacheService>(PROMOTION_CACHE_MODULE);
60
+ const customCampaignModuleService =
61
+ container.resolve<CustomCampaignModuleService>(CUSTOM_CAMPAIGN_MODULE);
62
+ const promotionService = container.resolve(Modules.PROMOTION);
63
+
64
+ const cacheKey = `user:${customerId}:promotion:*`;
65
+ const cachedPromotionIds = await promoCacheService.scan<string>(cacheKey);
66
+
67
+ if (!cachedPromotionIds.length) {
68
+ return res.status(200).json({
69
+ coupons: [],
70
+ count: 0,
71
+ });
72
+ }
73
+
74
+ const promotions = await promotionService.listPromotions(
75
+ {
76
+ id: cachedPromotionIds,
77
+ },
78
+ {
79
+ relations: ["campaign", "application_method"],
80
+ },
81
+ );
82
+
83
+ const campaignIds = promotions
84
+ .map((promotion) => promotion.campaign_id)
85
+ .filter((id): id is string => Boolean(id));
86
+
87
+ const campaignTypes =
88
+ await customCampaignModuleService.listCustomCampaignTypes({
89
+ campaign_id: campaignIds,
90
+ });
91
+
92
+ const couponCampaignIds = new Set(
93
+ campaignTypes
94
+ .filter((type) => type.type === CampaignTypeEnum.Coupon)
95
+ .map((type) => type.campaign_id),
96
+ );
97
+
98
+ const campaignDetails =
99
+ await customCampaignModuleService.listCampaignDetails({
100
+ campaign_id: Array.from(couponCampaignIds),
101
+ });
102
+
103
+ const detailMap = new Map(
104
+ campaignDetails.map((detail) => [detail.campaign_id, detail]),
105
+ );
106
+
107
+ const coupons = promotions
108
+ .filter(
109
+ (promotion) =>
110
+ promotion.campaign_id && couponCampaignIds.has(promotion.campaign_id),
111
+ )
112
+ .map((promotion) => {
113
+ const campaign = promotion.campaign!;
114
+ return {
115
+ id: promotion.id,
116
+ code: promotion.code,
117
+ name: campaign.name,
118
+ description: campaign.description,
119
+ starts_at: campaign.starts_at,
120
+ ends_at: campaign.ends_at,
121
+ discount_type: promotion.application_method?.type,
122
+ discount_value: promotion.application_method?.value,
123
+ allocation: promotion.application_method?.allocation,
124
+ target_type: promotion.application_method?.target_type,
125
+ currency_code: promotion.application_method?.currency_code,
126
+ campaign_id: campaign.id,
127
+ is_collectable: true,
128
+ is_collected: true,
129
+ detail: detailMap.get(campaign.id) ?? null,
130
+ };
131
+ });
132
+
133
+ res.status(200).json({
134
+ coupons,
135
+ count: coupons.length,
136
+ });
137
+ };
138
+
139
+ export const POST = async (
140
+ req: AuthenticatedMedusaRequest,
141
+ res: MedusaResponse,
142
+ ) => {
143
+ const body = collectCouponSchema.parse(req.body);
144
+ const customerId = req.auth_context.actor_id;
145
+
146
+ if (!customerId) {
147
+ throw new MedusaError(
148
+ MedusaError.Types.NOT_ALLOWED,
149
+ "Customer must be authenticated",
150
+ );
151
+ }
152
+
153
+ const promotionService = container.resolve(Modules.PROMOTION);
154
+ const customCampaignModuleService =
155
+ container.resolve<CustomCampaignModuleService>(CUSTOM_CAMPAIGN_MODULE);
156
+ const promoCacheService =
157
+ container.resolve<PromotionCacheService>(PROMOTION_CACHE_MODULE);
158
+ const usablePromoService = resolveOptional<UsablePromoModuleService>(
159
+ USABLE_PROMO_MODULE,
160
+ );
161
+
162
+ const promotion = await promotionService.retrievePromotion(
163
+ body.coupon_id,
164
+ {
165
+ relations: ["campaign", "application_method"],
166
+ },
167
+ );
168
+
169
+ if (!promotion?.campaign_id) {
170
+ throw new MedusaError(
171
+ MedusaError.Types.NOT_FOUND,
172
+ "Coupon promotion not found",
173
+ );
174
+ }
175
+
176
+ const [campaignType] =
177
+ await customCampaignModuleService.listCustomCampaignTypes({
178
+ campaign_id: promotion.campaign_id,
179
+ });
180
+
181
+ if (!campaignType || campaignType.type !== CampaignTypeEnum.Coupon) {
182
+ throw new MedusaError(
183
+ MedusaError.Types.INVALID_DATA,
184
+ "Promotion is not registered as a coupon",
185
+ );
186
+ }
187
+
188
+ if (promotion.is_automatic) {
189
+ throw new MedusaError(
190
+ MedusaError.Types.INVALID_DATA,
191
+ "Automatic promotions cannot be collected",
192
+ );
193
+ }
194
+
195
+ if (promotion.status !== "active") {
196
+ throw new MedusaError(
197
+ MedusaError.Types.INVALID_DATA,
198
+ "Promotion is not active",
199
+ );
200
+ }
201
+
202
+ if (usablePromoService) {
203
+ const [usable] = await usablePromoService.listUsablePromotions({
204
+ promotion_id: promotion.id,
205
+ });
206
+
207
+ if (!usable || !usable.enabled) {
208
+ throw new MedusaError(
209
+ MedusaError.Types.INVALID_DATA,
210
+ "This coupon is not available for collection",
211
+ );
212
+ }
213
+ }
214
+
215
+ const cacheKey = `user:${customerId}:promotion:${promotion.id}`;
216
+ const alreadyCollected = await promoCacheService.exists(cacheKey);
217
+
218
+ if (alreadyCollected) {
219
+ throw new MedusaError(
220
+ MedusaError.Types.INVALID_DATA,
221
+ "Coupon already collected",
222
+ );
223
+ }
224
+
225
+ await promoCacheService.set(cacheKey, promotion.id);
226
+
227
+ res.status(201).json({
228
+ message: "Coupon collected",
229
+ coupon: {
230
+ id: promotion.id,
231
+ code: promotion.code,
232
+ name: promotion.campaign?.name,
233
+ description: promotion.campaign?.description,
234
+ starts_at: promotion.campaign?.starts_at,
235
+ ends_at: promotion.campaign?.ends_at,
236
+ discount_type: promotion.application_method?.type,
237
+ discount_value: promotion.application_method?.value,
238
+ allocation: promotion.application_method?.allocation,
239
+ target_type: promotion.application_method?.target_type,
240
+ currency_code: promotion.application_method?.currency_code,
241
+ campaign_id: promotion.campaign_id,
242
+ },
243
+ });
244
+ };
@@ -1,4 +1,4 @@
1
- import { Migration } from "@mikro-orm/migrations";
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
2
 
3
3
  export class Migration20251024000000 extends Migration {
4
4
  override async up(): Promise<void> {
@@ -1,4 +1,4 @@
1
- import { Migration } from "@mikro-orm/migrations";
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
2
 
3
3
  export class Migration20251025000000 extends Migration {
4
4
  override async up(): Promise<void> {
@@ -0,0 +1,21 @@
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
+
3
+ export class Migration20251101000000 extends Migration {
4
+ override async up(): Promise<void> {
5
+ this.addSql(
6
+ `alter table if exists "custom_campaign_type" drop constraint if exists "custom_campaign_type_type_check";`,
7
+ );
8
+ this.addSql(
9
+ `alter table if exists "custom_campaign_type" add constraint "custom_campaign_type_type_check" check ("type" in ('flash-sale', 'buy-x-get-y', 'coupon'));`,
10
+ );
11
+ }
12
+
13
+ override async down(): Promise<void> {
14
+ this.addSql(
15
+ `alter table if exists "custom_campaign_type" drop constraint if exists "custom_campaign_type_type_check";`,
16
+ );
17
+ this.addSql(
18
+ `alter table if exists "custom_campaign_type" add constraint "custom_campaign_type_type_check" check ("type" in ('flash-sale', 'buy-x-get-y'));`,
19
+ );
20
+ }
21
+ }
@@ -1,4 +1,5 @@
1
1
  export enum CampaignTypeEnum {
2
2
  FlashSale = "flash-sale",
3
3
  BuyXGetY = "buy-x-get-y",
4
+ Coupon = "coupon",
4
5
  }
@@ -0,0 +1,176 @@
1
+ import { container } from "@medusajs/framework";
2
+ import {
3
+ CampaignDTO,
4
+ CreateApplicationMethodDTO,
5
+ CreatePromotionDTO,
6
+ PromotionDTO,
7
+ } from "@medusajs/framework/types";
8
+ import {
9
+ ContainerRegistrationKeys,
10
+ MedusaError,
11
+ Modules,
12
+ } from "@medusajs/framework/utils";
13
+ import {
14
+ createStep,
15
+ createWorkflow,
16
+ StepResponse,
17
+ WorkflowResponse,
18
+ } from "@medusajs/framework/workflows-sdk";
19
+ import {
20
+ createCampaignsWorkflow,
21
+ createPromotionsWorkflow,
22
+ deleteCampaignsWorkflow,
23
+ deletePromotionsWorkflow,
24
+ } from "@medusajs/medusa/core-flows";
25
+ import { CUSTOM_CAMPAIGN_MODULE } from "../../modules/custom-campaigns";
26
+ import CustomCampaignModuleService from "../../modules/custom-campaigns/service";
27
+ import { CampaignTypeEnum } from "../../modules/custom-campaigns/types/campaign-type.enum";
28
+
29
+ export type CreateCouponCampaignInput = {
30
+ name: string;
31
+ description: string;
32
+ code: string;
33
+ type: CampaignTypeEnum.Coupon;
34
+ starts_at: Date;
35
+ ends_at: Date;
36
+ discount_type: "percentage" | "fixed";
37
+ discount_value: number;
38
+ currency_code?: string | null;
39
+ allocation?: "each" | "total";
40
+ target_type?: "order" | "items";
41
+ };
42
+
43
+ const createCouponCampaignStep = createStep(
44
+ "create-coupon-campaign-step",
45
+ async (input: CreateCouponCampaignInput) => {
46
+ if (new Date(input.ends_at) < new Date(input.starts_at)) {
47
+ throw new MedusaError(
48
+ MedusaError.Types.INVALID_DATA,
49
+ "End date must be after start date",
50
+ );
51
+ }
52
+
53
+ if (input.discount_type === "fixed" && !input.currency_code) {
54
+ throw new MedusaError(
55
+ MedusaError.Types.INVALID_DATA,
56
+ "currency_code is required for fixed discount type",
57
+ );
58
+ }
59
+
60
+ const link = container.resolve(ContainerRegistrationKeys.LINK);
61
+ const customCampaignModuleService =
62
+ container.resolve<CustomCampaignModuleService>(CUSTOM_CAMPAIGN_MODULE);
63
+
64
+ const campaignIdentifier = `${input.type}-${Date.now()}`;
65
+
66
+ let campaign: CampaignDTO | undefined;
67
+ let promotion: PromotionDTO | undefined;
68
+ let customCampaignType:
69
+ | Awaited<
70
+ ReturnType<CustomCampaignModuleService["createCustomCampaignTypes"]>
71
+ >[number]
72
+ | undefined;
73
+
74
+ try {
75
+ const {
76
+ result: [createdCampaign],
77
+ } = await createCampaignsWorkflow.run({
78
+ input: {
79
+ campaignsData: [
80
+ {
81
+ name: input.name,
82
+ description: input.description,
83
+ campaign_identifier: campaignIdentifier,
84
+ starts_at: new Date(input.starts_at),
85
+ ends_at: new Date(input.ends_at),
86
+ },
87
+ ],
88
+ },
89
+ });
90
+
91
+ campaign = createdCampaign;
92
+
93
+ const {
94
+ result: [createdPromotion],
95
+ } = await createPromotionsWorkflow.run({
96
+ input: {
97
+ promotionsData: [
98
+ {
99
+ code: input.code,
100
+ type: "standard",
101
+ status: "active",
102
+ is_automatic: false,
103
+ campaign_id: campaign.id,
104
+ application_method: {
105
+ target_type: input.target_type ?? "order",
106
+ allocation: (input.allocation ?? "across") as any,
107
+ type: input.discount_type,
108
+ value: input.discount_value,
109
+ currency_code:
110
+ input.discount_type === "fixed" && input.currency_code
111
+ ? input.currency_code
112
+ : undefined,
113
+ } satisfies CreateApplicationMethodDTO,
114
+ } satisfies CreatePromotionDTO,
115
+ ],
116
+ },
117
+ });
118
+
119
+ promotion = createdPromotion;
120
+
121
+ customCampaignType =
122
+ await customCampaignModuleService.createCustomCampaignTypes({
123
+ campaign_id: campaign.id,
124
+ type: input.type,
125
+ });
126
+
127
+ await link.create([
128
+ {
129
+ [Modules.PROMOTION]: {
130
+ campaign_id: campaign.id,
131
+ },
132
+ [CUSTOM_CAMPAIGN_MODULE]: {
133
+ custom_campaign_type_id: customCampaignType.id,
134
+ },
135
+ },
136
+ ]);
137
+
138
+ return new StepResponse({
139
+ campaign,
140
+ promotion,
141
+ });
142
+ } catch (error) {
143
+ if (campaign) {
144
+ await deleteCampaignsWorkflow.run({
145
+ input: {
146
+ ids: campaign.id,
147
+ },
148
+ });
149
+ }
150
+
151
+ if (promotion) {
152
+ await deletePromotionsWorkflow.run({
153
+ input: {
154
+ ids: promotion.id,
155
+ },
156
+ });
157
+ }
158
+
159
+ if (customCampaignType) {
160
+ await customCampaignModuleService.deleteCustomCampaignTypes(
161
+ customCampaignType.id,
162
+ );
163
+ }
164
+
165
+ throw error;
166
+ }
167
+ },
168
+ );
169
+
170
+ export const createCouponCampaignWorkflow = createWorkflow(
171
+ "create-coupon-campaign",
172
+ (input: CreateCouponCampaignInput) => {
173
+ const couponCampaign = createCouponCampaignStep(input);
174
+ return new WorkflowResponse(couponCampaign);
175
+ },
176
+ );
@@ -0,0 +1,105 @@
1
+ import { container } from "@medusajs/framework";
2
+ import {
3
+ UpdateCampaignDTO,
4
+ UpdatePromotionDTO,
5
+ } from "@medusajs/framework/types";
6
+ import { MedusaError, Modules } from "@medusajs/framework/utils";
7
+ import {
8
+ createStep,
9
+ createWorkflow,
10
+ StepResponse,
11
+ WorkflowResponse,
12
+ } from "@medusajs/framework/workflows-sdk";
13
+ import { updateCampaignsWorkflow } from "@medusajs/medusa/core-flows";
14
+
15
+ export type UpdateCouponCampaignInput = {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ code: string;
20
+ starts_at: Date;
21
+ ends_at: Date;
22
+ discount_type: "percentage" | "fixed";
23
+ discount_value: number;
24
+ currency_code?: string | null;
25
+ allocation?: "each" | "total";
26
+ target_type?: "order" | "items";
27
+ };
28
+
29
+ const updateCouponCampaignStep = createStep(
30
+ "update-coupon-campaign-step",
31
+ async (input: UpdateCouponCampaignInput) => {
32
+ if (new Date(input.ends_at) < new Date(input.starts_at)) {
33
+ throw new MedusaError(
34
+ MedusaError.Types.INVALID_DATA,
35
+ "End date must be after start date",
36
+ );
37
+ }
38
+
39
+ if (input.discount_type === "fixed" && !input.currency_code) {
40
+ throw new MedusaError(
41
+ MedusaError.Types.INVALID_DATA,
42
+ "currency_code is required for fixed discount type",
43
+ );
44
+ }
45
+
46
+ const promotionService = container.resolve(Modules.PROMOTION);
47
+
48
+ const promotions = await promotionService.listPromotions({
49
+ campaign_id: input.id,
50
+ } as any);
51
+
52
+ if (!promotions.length) {
53
+ throw new MedusaError(
54
+ MedusaError.Types.NOT_FOUND,
55
+ "Coupon promotion not found for campaign",
56
+ );
57
+ }
58
+
59
+ const promotion = promotions[0];
60
+
61
+ await updateCampaignsWorkflow.run({
62
+ input: {
63
+ campaignsData: [
64
+ {
65
+ id: input.id,
66
+ name: input.name,
67
+ description: input.description,
68
+ starts_at: new Date(input.starts_at),
69
+ ends_at: new Date(input.ends_at),
70
+ } satisfies UpdateCampaignDTO,
71
+ ],
72
+ },
73
+ });
74
+
75
+ const updateData: UpdatePromotionDTO = {
76
+ id: promotion.id,
77
+ code: input.code,
78
+ application_method: {
79
+ type: input.discount_type,
80
+ target_type: input.target_type ?? "order",
81
+ allocation: (input.allocation ?? "across") as any,
82
+ value: input.discount_value,
83
+ currency_code:
84
+ input.discount_type === "fixed" && input.currency_code
85
+ ? input.currency_code
86
+ : undefined,
87
+ },
88
+ };
89
+
90
+ await promotionService.updatePromotions([updateData]);
91
+
92
+ return new StepResponse({
93
+ campaign_id: input.id,
94
+ promotion_id: promotion.id,
95
+ });
96
+ },
97
+ );
98
+
99
+ export const updateCouponCampaignWorkflow = createWorkflow(
100
+ "update-coupon-campaign",
101
+ (input: UpdateCouponCampaignInput) => {
102
+ const result = updateCouponCampaignStep(input);
103
+ return new WorkflowResponse(result);
104
+ },
105
+ );
@@ -1,4 +1,6 @@
1
1
  export { createCustomCampaignWorkflow, createCustomCampaignWorkflow as createCustomFlashSaleWorkflow } from "./custom-campaign/createCustomCampaignWorkflow";
2
2
  export { updateCustomFlashSaleWorkflow } from "./custom-campaign/updateCustomFlashSaleWorkflow";
3
3
  export { updatePromotionUsageWorkflow } from "./custom-campaign/updatePromotionUsageWorkflow";
4
- export { applyBuyXGetYToCartWorkflow } from "./buy-x-get-y/applyBuyXGetYToCartWorkflow";
4
+ export { applyBuyXGetYToCartWorkflow } from "./buy-x-get-y/applyBuyXGetYToCartWorkflow";
5
+ export { createCouponCampaignWorkflow } from "./custom-campaign/createCouponCampaignWorkflow";
6
+ export { updateCouponCampaignWorkflow } from "./custom-campaign/updateCouponCampaignWorkflow";