Add order refunded webhook (#751)

* Update the app sdk package

* Add order refunded webhook

* Add changeset
This commit is contained in:
Krzysztof Wolski 2023-07-13 17:50:00 +02:00 committed by GitHub
parent 3c6cd4ccec
commit 790a47ee08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1571 additions and 173 deletions

View 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

View file

@ -73,6 +73,6 @@
}, },
"private": true, "private": true,
"saleor": { "saleor": {
"schemaVersion": "3.13" "schemaVersion": "3.14"
} }
} }

View file

@ -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({

View file

@ -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": {
return { const isUnsupported = !featureFlags?.orderRefundedEvent;
isDisabled: false,
missingPermission: undefined, const hasPermission = (appPermissions || []).includes(PermissionEnum.ManageOrders);
requiredSaleorVersion: undefined,
}; 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 {
isDisabled: false,
missingPermission: 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,
};
}; };

View file

@ -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,
}; };

View file

@ -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",
}; };

View file

@ -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),
}; };
}; };

View file

@ -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",
}; };

View file

@ -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,
});
}); });
}); });

View file

@ -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) {

View file

@ -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);
}); });

View file

@ -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({

View file

@ -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"),
});

View file

@ -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,
},
};