Add order refunded webhook (#751)
* Update the app sdk package * Add order refunded webhook * Add changeset
This commit is contained in:
parent
3c6cd4ccec
commit
790a47ee08
15 changed files with 1571 additions and 173 deletions
5
.changeset/cyan-pianos-hide.md
Normal file
5
.changeset/cyan-pianos-hide.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added support for new event: order refunded.
|
File diff suppressed because it is too large
Load diff
|
@ -73,6 +73,6 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"saleor": {
|
"saleor": {
|
||||||
"schemaVersion": "3.13"
|
"schemaVersion": "3.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { vi, expect, describe, it } from "vitest";
|
import { expect, describe, it } from "vitest";
|
||||||
import { getEventFormStatus } from "./get-event-form-status";
|
import { getEventFormStatus } from "./get-event-form-status";
|
||||||
import { PermissionEnum } from "../../generated/graphql";
|
import { PermissionEnum } from "../../generated/graphql";
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ describe("getEventFormStatus", function () {
|
||||||
appPermissions: [PermissionEnum.ManageGiftCard],
|
appPermissions: [PermissionEnum.ManageGiftCard],
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
giftCardSentEvent: true,
|
giftCardSentEvent: true,
|
||||||
|
orderRefundedEvent: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
@ -22,6 +23,7 @@ describe("getEventFormStatus", function () {
|
||||||
appPermissions: [],
|
appPermissions: [],
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
giftCardSentEvent: false,
|
giftCardSentEvent: false,
|
||||||
|
orderRefundedEvent: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
@ -38,6 +40,7 @@ describe("getEventFormStatus", function () {
|
||||||
appPermissions: [],
|
appPermissions: [],
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
giftCardSentEvent: true,
|
giftCardSentEvent: true,
|
||||||
|
orderRefundedEvent: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
@ -54,6 +57,7 @@ describe("getEventFormStatus", function () {
|
||||||
appPermissions: [PermissionEnum.ManageGiftCard],
|
appPermissions: [PermissionEnum.ManageGiftCard],
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
giftCardSentEvent: false,
|
giftCardSentEvent: false,
|
||||||
|
orderRefundedEvent: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
|
|
@ -17,24 +17,38 @@ export const getEventFormStatus = ({
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
requiredSaleorVersion: string | undefined;
|
requiredSaleorVersion: string | undefined;
|
||||||
} => {
|
} => {
|
||||||
// Since GIFT_CARD_SENT is the only event with such validation, we can exit early
|
switch (eventType) {
|
||||||
if (eventType !== "GIFT_CARD_SENT") {
|
case "ORDER_REFUNDED": {
|
||||||
|
const isUnsupported = !featureFlags?.orderRefundedEvent;
|
||||||
|
|
||||||
|
const hasPermission = (appPermissions || []).includes(PermissionEnum.ManageOrders);
|
||||||
|
|
||||||
|
const isDisabled = isUnsupported || !hasPermission;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDisabled,
|
||||||
|
missingPermission: hasPermission ? undefined : PermissionEnum.ManageOrders,
|
||||||
|
requiredSaleorVersion: isUnsupported ? ">=3.14" : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "GIFT_CARD_SENT": {
|
||||||
|
const isUnsupported = !featureFlags?.giftCardSentEvent;
|
||||||
|
|
||||||
|
const hasPermission = (appPermissions || []).includes(PermissionEnum.ManageGiftCard);
|
||||||
|
|
||||||
|
const isDisabled = isUnsupported || !hasPermission;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDisabled,
|
||||||
|
missingPermission: hasPermission ? undefined : PermissionEnum.ManageGiftCard,
|
||||||
|
requiredSaleorVersion: isUnsupported ? ">=3.13" : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
return {
|
return {
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
missingPermission: undefined,
|
missingPermission: undefined,
|
||||||
requiredSaleorVersion: undefined,
|
requiredSaleorVersion: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUnsupported = !featureFlags?.giftCardSentEvent;
|
|
||||||
|
|
||||||
const hasGiftCardPermission = (appPermissions || []).includes(PermissionEnum.ManageGiftCard);
|
|
||||||
|
|
||||||
const isDisabled = isUnsupported || !hasGiftCardPermission;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isDisabled,
|
|
||||||
missingPermission: hasGiftCardPermission ? undefined : PermissionEnum.ManageGiftCard,
|
|
||||||
requiredSaleorVersion: isUnsupported ? ">=3.13" : undefined,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
OrderFullyPaidWebhookPayloadFragment,
|
OrderFullyPaidWebhookPayloadFragment,
|
||||||
InvoiceSentWebhookPayloadFragment,
|
InvoiceSentWebhookPayloadFragment,
|
||||||
GiftCardSentWebhookPayloadFragment,
|
GiftCardSentWebhookPayloadFragment,
|
||||||
|
OrderRefundedWebhookPayloadFragment,
|
||||||
} from "../../../generated/graphql";
|
} from "../../../generated/graphql";
|
||||||
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
||||||
|
|
||||||
|
@ -137,6 +138,10 @@ const orderFullyPaidPayload: OrderFullyPaidWebhookPayloadFragment = {
|
||||||
order: exampleOrderPayload,
|
order: exampleOrderPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const orderRefundedPayload: OrderRefundedWebhookPayloadFragment = {
|
||||||
|
order: exampleOrderPayload,
|
||||||
|
};
|
||||||
|
|
||||||
const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = {
|
const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = {
|
||||||
invoice: {
|
invoice: {
|
||||||
id: "SW52b2ljZToxMDE=",
|
id: "SW52b2ljZToxMDE=",
|
||||||
|
@ -312,16 +317,17 @@ const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const examplePayloads: Record<MessageEventTypes, any> = {
|
export const examplePayloads: Record<MessageEventTypes, any> = {
|
||||||
ORDER_CREATED: orderCreatedPayload,
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: accountChangeEmailConfirmPayload,
|
||||||
ORDER_CONFIRMED: orderConfirmedPayload,
|
ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload,
|
||||||
|
ACCOUNT_CONFIRMATION: accountConfirmationPayload,
|
||||||
|
ACCOUNT_DELETE: accountDeletePayload,
|
||||||
|
ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload,
|
||||||
|
GIFT_CARD_SENT: giftCardSentPayload,
|
||||||
|
INVOICE_SENT: invoiceSentPayload,
|
||||||
ORDER_CANCELLED: orderCancelledPayload,
|
ORDER_CANCELLED: orderCancelledPayload,
|
||||||
|
ORDER_CONFIRMED: orderConfirmedPayload,
|
||||||
|
ORDER_CREATED: orderCreatedPayload,
|
||||||
ORDER_FULFILLED: orderFulfilledPayload,
|
ORDER_FULFILLED: orderFulfilledPayload,
|
||||||
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
||||||
INVOICE_SENT: invoiceSentPayload,
|
ORDER_REFUNDED: orderRefundedPayload,
|
||||||
GIFT_CARD_SENT: giftCardSentPayload,
|
|
||||||
ACCOUNT_CONFIRMATION: accountConfirmationPayload,
|
|
||||||
ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload,
|
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload,
|
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: accountChangeEmailConfirmPayload,
|
|
||||||
ACCOUNT_DELETE: accountDeletePayload,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
export const messageEventTypes = [
|
export const messageEventTypes = [
|
||||||
|
"ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
||||||
|
"ACCOUNT_CHANGE_EMAIL_REQUEST",
|
||||||
|
"ACCOUNT_CONFIRMATION",
|
||||||
|
"ACCOUNT_DELETE",
|
||||||
|
"ACCOUNT_PASSWORD_RESET",
|
||||||
|
"GIFT_CARD_SENT",
|
||||||
|
"INVOICE_SENT",
|
||||||
|
"ORDER_CANCELLED",
|
||||||
|
"ORDER_CONFIRMED",
|
||||||
"ORDER_CREATED",
|
"ORDER_CREATED",
|
||||||
"ORDER_FULFILLED",
|
"ORDER_FULFILLED",
|
||||||
"ORDER_CONFIRMED",
|
|
||||||
"ORDER_CANCELLED",
|
|
||||||
"ORDER_FULLY_PAID",
|
"ORDER_FULLY_PAID",
|
||||||
"INVOICE_SENT",
|
"ORDER_REFUNDED",
|
||||||
"ACCOUNT_CONFIRMATION",
|
|
||||||
"ACCOUNT_PASSWORD_RESET",
|
|
||||||
"ACCOUNT_CHANGE_EMAIL_REQUEST",
|
|
||||||
"ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
|
||||||
"ACCOUNT_DELETE",
|
|
||||||
"GIFT_CARD_SENT",
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type MessageEventTypes = (typeof messageEventTypes)[number];
|
export type MessageEventTypes = (typeof messageEventTypes)[number];
|
||||||
|
|
||||||
export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
||||||
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Customer account change email confirmation",
|
||||||
|
ACCOUNT_CHANGE_EMAIL_REQUEST: "Customer account change email request",
|
||||||
|
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
||||||
|
ACCOUNT_DELETE: "Customer account delete request",
|
||||||
|
ACCOUNT_PASSWORD_RESET: "Customer account password reset request",
|
||||||
|
GIFT_CARD_SENT: "Gift card sent",
|
||||||
|
INVOICE_SENT: "Invoice sent",
|
||||||
|
ORDER_CANCELLED: "Order cancelled",
|
||||||
|
ORDER_CONFIRMED: "Order confirmed",
|
||||||
ORDER_CREATED: "Order created",
|
ORDER_CREATED: "Order created",
|
||||||
ORDER_FULFILLED: "Order fulfilled",
|
ORDER_FULFILLED: "Order fulfilled",
|
||||||
ORDER_CONFIRMED: "Order confirmed",
|
|
||||||
ORDER_CANCELLED: "Order cancelled",
|
|
||||||
ORDER_FULLY_PAID: "Order fully paid",
|
ORDER_FULLY_PAID: "Order fully paid",
|
||||||
INVOICE_SENT: "Invoice sent",
|
ORDER_REFUNDED: "Order refunded",
|
||||||
GIFT_CARD_SENT: "Gift card sent",
|
|
||||||
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
|
||||||
ACCOUNT_PASSWORD_RESET: "Customer account password reset request",
|
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "Customer account change email request",
|
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Customer account change email confirmation",
|
|
||||||
ACCOUNT_DELETE: "Customer account delete request",
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SaleorVersionCompatibilityValidator } from "@saleor/apps-shared";
|
import { SaleorVersionCompatibilityValidator } from "@saleor/apps-shared";
|
||||||
|
|
||||||
export const featureFlags = ["giftCardSentEvent"] as const;
|
export const featureFlags = ["giftCardSentEvent", "orderRefundedEvent"] as const;
|
||||||
|
|
||||||
export type FeatureFlag = (typeof featureFlags)[number];
|
export type FeatureFlag = (typeof featureFlags)[number];
|
||||||
|
|
||||||
|
@ -17,5 +17,6 @@ interface GetFeatureFlagsArgs {
|
||||||
export const getFeatureFlags = ({ saleorVersion }: GetFeatureFlagsArgs): FeatureFlagsState => {
|
export const getFeatureFlags = ({ saleorVersion }: GetFeatureFlagsArgs): FeatureFlagsState => {
|
||||||
return {
|
return {
|
||||||
giftCardSentEvent: new SaleorVersionCompatibilityValidator(">=3.13").isValid(saleorVersion),
|
giftCardSentEvent: new SaleorVersionCompatibilityValidator(">=3.13").isValid(saleorVersion),
|
||||||
|
orderRefundedEvent: new SaleorVersionCompatibilityValidator(">=3.14").isValid(saleorVersion),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -138,6 +138,23 @@ const defaultOrderFullyPaidMjmlTemplate = `<mjml>
|
||||||
</mj-body>
|
</mj-body>
|
||||||
</mjml>`;
|
</mjml>`;
|
||||||
|
|
||||||
|
const defaultOrderRefundedMjmlTemplate = `<mjml>
|
||||||
|
<mj-body>
|
||||||
|
<mj-section>
|
||||||
|
<mj-column>
|
||||||
|
<mj-text font-size="16px">
|
||||||
|
Hello!
|
||||||
|
</mj-text>
|
||||||
|
<mj-text>
|
||||||
|
Order {{ order.number}} has been refunded.
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
${addressSection}
|
||||||
|
${orderLinesSection}
|
||||||
|
</mj-body>
|
||||||
|
</mjml>`;
|
||||||
|
|
||||||
const defaultOrderCancelledMjmlTemplate = `<mjml>
|
const defaultOrderCancelledMjmlTemplate = `<mjml>
|
||||||
<mj-body>
|
<mj-body>
|
||||||
<mj-section>
|
<mj-section>
|
||||||
|
@ -286,6 +303,7 @@ export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
||||||
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
||||||
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
||||||
|
ORDER_REFUNDED: defaultOrderRefundedMjmlTemplate,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
||||||
|
@ -301,4 +319,5 @@ export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CREATED: "Order {{ order.number }} has been created",
|
ORDER_CREATED: "Order {{ order.number }} has been created",
|
||||||
ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled",
|
ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled",
|
||||||
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
||||||
|
ORDER_REFUNDED: "Order {{ order.number }} has been refunded",
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { expect, describe, it } from "vitest";
|
||||||
import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema";
|
import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema";
|
||||||
import { getWebhookStatusesFromConfigurations } from "./get-webhook-statuses-from-configurations";
|
import { getWebhookStatusesFromConfigurations } from "./get-webhook-statuses-from-configurations";
|
||||||
import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema";
|
import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema";
|
||||||
|
import { webhookStatusesFactory } from "./webhook-status-dict";
|
||||||
|
|
||||||
export const nonActiveSmtpConfiguration: SmtpConfiguration = {
|
export const nonActiveSmtpConfiguration: SmtpConfiguration = {
|
||||||
id: "1685343953413npk9p",
|
id: "1685343953413npk9p",
|
||||||
|
@ -169,16 +170,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
smtpConfigurations: [],
|
smtpConfigurations: [],
|
||||||
sendgridConfigurations: [],
|
sendgridConfigurations: [],
|
||||||
})
|
})
|
||||||
).toStrictEqual({
|
).toStrictEqual(webhookStatusesFactory({}));
|
||||||
invoiceSentWebhook: false,
|
|
||||||
notifyWebhook: false,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Statuses should be set to false, when no active configurations passed", async () => {
|
it("Statuses should be set to false, when no active configurations passed", async () => {
|
||||||
|
@ -187,16 +179,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
smtpConfigurations: [nonActiveSmtpConfiguration],
|
smtpConfigurations: [nonActiveSmtpConfiguration],
|
||||||
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
||||||
})
|
})
|
||||||
).toStrictEqual({
|
).toStrictEqual(webhookStatusesFactory({}));
|
||||||
invoiceSentWebhook: false,
|
|
||||||
notifyWebhook: false,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Statuses should be set to false, when configuration is not active even if events were activated", async () => {
|
it("Statuses should be set to false, when configuration is not active even if events were activated", async () => {
|
||||||
|
@ -210,16 +193,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
smtpConfigurations: [smtpConfiguration],
|
smtpConfigurations: [smtpConfiguration],
|
||||||
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
||||||
})
|
})
|
||||||
).toStrictEqual({
|
).toStrictEqual(webhookStatusesFactory({}));
|
||||||
invoiceSentWebhook: false,
|
|
||||||
notifyWebhook: false,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Status of the event should be set to true, when at least one active configuration has activated it", async () => {
|
it("Status of the event should be set to true, when at least one active configuration has activated it", async () => {
|
||||||
|
@ -241,16 +215,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
smtpConfigurations: [nonActiveSmtpConfiguration, smtpConfiguration],
|
smtpConfigurations: [nonActiveSmtpConfiguration, smtpConfiguration],
|
||||||
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
||||||
})
|
})
|
||||||
).toStrictEqual({
|
).toStrictEqual(webhookStatusesFactory({ enabledWebhooks: ["invoiceSentWebhook"] }));
|
||||||
invoiceSentWebhook: true,
|
|
||||||
notifyWebhook: false,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Status of the NOTIFY webhooks should be set to true, when at least one active configuration has activated one of its related events", async () => {
|
it("Status of the NOTIFY webhooks should be set to true, when at least one active configuration has activated one of its related events", async () => {
|
||||||
|
@ -278,15 +243,6 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
smtpConfigurations: [nonActiveSmtpConfiguration, smtpConfiguration],
|
smtpConfigurations: [nonActiveSmtpConfiguration, smtpConfiguration],
|
||||||
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
sendgridConfigurations: [nonActiveSendgridConfiguration],
|
||||||
})
|
})
|
||||||
).toStrictEqual({
|
).toStrictEqual(webhookStatusesFactory({ enabledWebhooks: ["notifyWebhook"] }));
|
||||||
invoiceSentWebhook: false,
|
|
||||||
notifyWebhook: true,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema";
|
import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema";
|
||||||
import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema";
|
import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema";
|
||||||
import { AppWebhook, eventToWebhookMapping } from "./webhook-management-service";
|
import { AppWebhook, eventToWebhookMapping } from "./webhook-management-service";
|
||||||
|
import { webhookStatusesFactory } from "./webhook-status-dict";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns dictionary of webhook statuses based on passed configurations.
|
* Returns dictionary of webhook statuses based on passed configurations.
|
||||||
|
@ -14,16 +15,7 @@ export const getWebhookStatusesFromConfigurations = ({
|
||||||
sendgridConfigurations: SendgridConfiguration[];
|
sendgridConfigurations: SendgridConfiguration[];
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: this dict should be generated in one place instead of manually edited
|
// TODO: this dict should be generated in one place instead of manually edited
|
||||||
const statuses: Record<AppWebhook, boolean> = {
|
const statuses: Record<AppWebhook, boolean> = webhookStatusesFactory({});
|
||||||
giftCardSentWebhook: false,
|
|
||||||
invoiceSentWebhook: false,
|
|
||||||
notifyWebhook: false,
|
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
smtpConfigurations.forEach(async (config) => {
|
smtpConfigurations.forEach(async (config) => {
|
||||||
if (!config.active) {
|
if (!config.active) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
||||||
import { invoiceSentWebhook } from "../../pages/api/webhooks/invoice-sent";
|
import { invoiceSentWebhook } from "../../pages/api/webhooks/invoice-sent";
|
||||||
import { orderCancelledWebhook } from "../../pages/api/webhooks/order-cancelled";
|
import { orderCancelledWebhook } from "../../pages/api/webhooks/order-cancelled";
|
||||||
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent";
|
import { webhookStatusesFactory } from "./webhook-status-dict";
|
||||||
|
|
||||||
describe("WebhookManagementService", function () {
|
describe("WebhookManagementService", function () {
|
||||||
const mockedClient = {} as Client;
|
const mockedClient = {} as Client;
|
||||||
|
@ -76,16 +76,9 @@ describe("WebhookManagementService", function () {
|
||||||
|
|
||||||
const statuses = await webhookManagementService.getWebhooksStatus();
|
const statuses = await webhookManagementService.getWebhooksStatus();
|
||||||
|
|
||||||
expect(statuses).toStrictEqual({
|
expect(statuses).toStrictEqual(
|
||||||
invoiceSentWebhook: true,
|
webhookStatusesFactory({ enabledWebhooks: ["invoiceSentWebhook"] })
|
||||||
notifyWebhook: false,
|
);
|
||||||
orderCancelledWebhook: false,
|
|
||||||
orderConfirmedWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderFullyPaidWebhook: false,
|
|
||||||
giftCardSentWebhook: false,
|
|
||||||
});
|
|
||||||
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { createLogger } from "@saleor/apps-shared";
|
||||||
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
||||||
import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent";
|
import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent";
|
||||||
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
import { FeatureFlagsState } from "../feature-flag-service/get-feature-flags";
|
import { orderRefundedWebhook } from "../../pages/api/webhooks/order-refunded";
|
||||||
|
|
||||||
export const AppWebhooks = {
|
export const AppWebhooks = {
|
||||||
giftCardSentWebhook,
|
giftCardSentWebhook,
|
||||||
|
@ -23,6 +23,7 @@ export const AppWebhooks = {
|
||||||
orderCreatedWebhook,
|
orderCreatedWebhook,
|
||||||
orderFulfilledWebhook,
|
orderFulfilledWebhook,
|
||||||
orderFullyPaidWebhook,
|
orderFullyPaidWebhook,
|
||||||
|
orderRefundedWebhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppWebhook = keyof typeof AppWebhooks;
|
export type AppWebhook = keyof typeof AppWebhooks;
|
||||||
|
@ -40,6 +41,7 @@ export const eventToWebhookMapping: Record<MessageEventTypes, AppWebhook> = {
|
||||||
ORDER_CREATED: "orderCreatedWebhook",
|
ORDER_CREATED: "orderCreatedWebhook",
|
||||||
ORDER_FULFILLED: "orderFulfilledWebhook",
|
ORDER_FULFILLED: "orderFulfilledWebhook",
|
||||||
ORDER_FULLY_PAID: "orderFullyPaidWebhook",
|
ORDER_FULLY_PAID: "orderFullyPaidWebhook",
|
||||||
|
ORDER_REFUNDED: "orderRefundedWebhook",
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { AppWebhook, AppWebhooks } from "./webhook-management-service";
|
||||||
|
|
||||||
|
export type WebhookStatuses = Record<AppWebhook, boolean>;
|
||||||
|
|
||||||
|
export const webhookStatusesFactory = ({
|
||||||
|
enabledWebhooks,
|
||||||
|
}: {
|
||||||
|
enabledWebhooks?: AppWebhook[];
|
||||||
|
}): WebhookStatuses => ({
|
||||||
|
// TODO: This function clearly deserves a better implementation.
|
||||||
|
giftCardSentWebhook: !!enabledWebhooks?.includes("giftCardSentWebhook"),
|
||||||
|
invoiceSentWebhook: !!enabledWebhooks?.includes("invoiceSentWebhook"),
|
||||||
|
notifyWebhook: !!enabledWebhooks?.includes("notifyWebhook"),
|
||||||
|
orderCancelledWebhook: !!enabledWebhooks?.includes("orderCancelledWebhook"),
|
||||||
|
orderConfirmedWebhook: !!enabledWebhooks?.includes("orderConfirmedWebhook"),
|
||||||
|
orderCreatedWebhook: !!enabledWebhooks?.includes("orderCreatedWebhook"),
|
||||||
|
orderFulfilledWebhook: !!enabledWebhooks?.includes("orderFulfilledWebhook"),
|
||||||
|
orderFullyPaidWebhook: !!enabledWebhooks?.includes("orderFullyPaidWebhook"),
|
||||||
|
orderRefundedWebhook: !!enabledWebhooks?.includes("orderRefundedWebhook"),
|
||||||
|
});
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { OrderDetailsFragmentDoc } from "../../../../generated/graphql";
|
||||||
|
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||||
|
import { gql } from "urql";
|
||||||
|
import { saleorApp } from "../../../saleor-app";
|
||||||
|
import { createLogger, createGraphQLClient } from "@saleor/apps-shared";
|
||||||
|
import { OrderRefundedWebhookPayloadFragment } from "../../../../generated/graphql";
|
||||||
|
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||||
|
|
||||||
|
const OrderRefundedWebhookPayload = gql`
|
||||||
|
${OrderDetailsFragmentDoc}
|
||||||
|
fragment OrderRefundedWebhookPayload on OrderRefunded {
|
||||||
|
order {
|
||||||
|
...OrderDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const OrderRefundedGraphqlSubscription = gql`
|
||||||
|
${OrderRefundedWebhookPayload}
|
||||||
|
subscription OrderRefunded {
|
||||||
|
event {
|
||||||
|
...OrderRefundedWebhookPayload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const orderRefundedWebhook = new SaleorAsyncWebhook<OrderRefundedWebhookPayloadFragment>({
|
||||||
|
name: "Order Refunded in Saleor",
|
||||||
|
webhookPath: "api/webhooks/order-refunded",
|
||||||
|
asyncEvent: "ORDER_REFUNDED",
|
||||||
|
apl: saleorApp.apl,
|
||||||
|
subscriptionQueryAst: OrderRefundedGraphqlSubscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger = createLogger({
|
||||||
|
name: orderRefundedWebhook.webhookPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler: NextWebhookApiHandler<OrderRefundedWebhookPayloadFragment> = async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
logger.debug("Webhook received");
|
||||||
|
|
||||||
|
const { payload, authData } = context;
|
||||||
|
const { order } = payload;
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
logger.error("No order data payload");
|
||||||
|
return res.status(200).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientEmail = order.userEmail || order.user?.email;
|
||||||
|
|
||||||
|
if (!recipientEmail?.length) {
|
||||||
|
logger.error(`The order ${order.number} had no email recipient set. Aborting.`);
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json({ error: "Email recipient has not been specified in the event payload." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = order.channel.slug;
|
||||||
|
const client = createGraphQLClient({
|
||||||
|
saleorApiUrl: authData.saleorApiUrl,
|
||||||
|
token: authData.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendEventMessages({
|
||||||
|
authData,
|
||||||
|
channel,
|
||||||
|
client,
|
||||||
|
event: "ORDER_REFUNDED",
|
||||||
|
payload: { order: payload.order },
|
||||||
|
recipientEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "The event has been handled" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default orderRefundedWebhook.createHandler(handler);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in a new issue