Notify event webhook for customer emails (#408)
* Support Notify webhook for account operations * Fix the comment * Do not expose internal types of the event * Remove debug message
This commit is contained in:
parent
a3636f73ef
commit
ede7a2e808
11 changed files with 479 additions and 86 deletions
4
apps/emails-and-messages/src/lib/get-base-url.ts
Normal file
4
apps/emails-and-messages/src/lib/get-base-url.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
|
||||
const { host, "x-forwarded-proto": protocol = "http" } = headers;
|
||||
return `${protocol}://${host}`;
|
||||
};
|
39
apps/emails-and-messages/src/lib/register-notify-webhook.ts
Normal file
39
apps/emails-and-messages/src/lib/register-notify-webhook.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { Client, gql } from "urql";
|
||||
import { WebhookCreateMutationDocument, WebhookEventTypeEnum } from "../../generated/graphql";
|
||||
import { notifyWebhook } from "../pages/api/webhooks/notify";
|
||||
|
||||
const webhookCreateMutation = gql`
|
||||
mutation webhookCreateMutation($input: WebhookCreateInput!) {
|
||||
webhookCreate(input: $input) {
|
||||
webhook {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface RegisterNotifyWebhookArgs {
|
||||
client: Client;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
export const registerNotifyWebhook = async ({ client, baseUrl }: RegisterNotifyWebhookArgs) => {
|
||||
const manifest = notifyWebhook.getWebhookManifest(baseUrl);
|
||||
|
||||
return await client
|
||||
.mutation(WebhookCreateMutationDocument, {
|
||||
input: {
|
||||
name: manifest.name,
|
||||
targetUrl: manifest.targetUrl,
|
||||
events: [WebhookEventTypeEnum.NotifyUser],
|
||||
isActive: true,
|
||||
},
|
||||
})
|
||||
.toPromise();
|
||||
};
|
|
@ -18,7 +18,7 @@ const useStyles = makeStyles((theme) => {
|
|||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 20,
|
||||
maxWidth: 600,
|
||||
maxWidth: 700,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
OrderFullyPaidWebhookPayloadFragment,
|
||||
InvoiceSentWebhookPayloadFragment,
|
||||
} from "../../../generated/graphql";
|
||||
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
||||
|
||||
const exampleOrderPayload: OrderDetailsFragment = {
|
||||
id: "T3JkZXI6NTdiNTBhNDAtYzRmYi00YjQzLWIxODgtM2JhZmRlMTc3MGQ5",
|
||||
|
@ -136,6 +137,116 @@ const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = {
|
|||
order: exampleOrderPayload,
|
||||
};
|
||||
|
||||
const accountConfirmationPayload: NotifyEventPayload = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
is_staff: false,
|
||||
is_active: false,
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
confirm_url:
|
||||
"http://example.com?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountPasswordResetPayload: NotifyEventPayload = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
is_staff: false,
|
||||
is_active: false,
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
reset_url:
|
||||
"http://example.com?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountChangeEmailRequestPayload: NotifyEventPayload = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
is_staff: false,
|
||||
is_active: false,
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
old_email: "test@example.com1",
|
||||
new_email: "new.email@example.com1",
|
||||
redirect_url:
|
||||
"http://example.com?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountChangeEmailConfirmPayload: NotifyEventPayload = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
is_staff: false,
|
||||
is_active: false,
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountDeletePayload: NotifyEventPayload = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
is_staff: false,
|
||||
is_active: false,
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
delete_url:
|
||||
"http://example.com?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
export const examplePayloads: Record<MessageEventTypes, any> = {
|
||||
ORDER_CREATED: orderCreatedPayload,
|
||||
ORDER_CONFIRMED: orderConfirmedPayload,
|
||||
|
@ -143,4 +254,9 @@ export const examplePayloads: Record<MessageEventTypes, any> = {
|
|||
ORDER_FULFILLED: orderFulfilledPayload,
|
||||
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
||||
INVOICE_SENT: invoiceSentPayload,
|
||||
ACCOUNT_CONFIRMATION: accountConfirmationPayload,
|
||||
ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload,
|
||||
ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload,
|
||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: accountChangeEmailConfirmPayload,
|
||||
ACCOUNT_DELETE: accountDeletePayload,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { AsyncWebhookEventType } from "@saleor/app-sdk/types";
|
||||
|
||||
export const messageEventTypes = [
|
||||
"ORDER_CREATED",
|
||||
"ORDER_FULFILLED",
|
||||
|
@ -7,11 +5,14 @@ export const messageEventTypes = [
|
|||
"ORDER_CANCELLED",
|
||||
"ORDER_FULLY_PAID",
|
||||
"INVOICE_SENT",
|
||||
"ACCOUNT_CONFIRMATION",
|
||||
"ACCOUNT_PASSWORD_RESET",
|
||||
"ACCOUNT_CHANGE_EMAIL_REQUEST",
|
||||
"ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
||||
"ACCOUNT_DELETE",
|
||||
] as const;
|
||||
|
||||
type Subset<K, T extends K> = T;
|
||||
|
||||
export type MessageEventTypes = Subset<AsyncWebhookEventType, (typeof messageEventTypes)[number]>;
|
||||
export type MessageEventTypes = (typeof messageEventTypes)[number];
|
||||
|
||||
export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
||||
ORDER_CREATED: "Order created",
|
||||
|
@ -20,4 +21,9 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
|||
ORDER_CANCELLED: "Order cancelled",
|
||||
ORDER_FULLY_PAID: "Order fully paid",
|
||||
INVOICE_SENT: "Invoice sent",
|
||||
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
||||
ACCOUNT_PASSWORD_RESET: "Customer account password reset",
|
||||
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,7 +1,6 @@
|
|||
import { MessageEventTypes } from "../event-handlers/message-event-types";
|
||||
|
||||
const addressSection = `
|
||||
<mj-section>
|
||||
const addressSection = `<mj-section>
|
||||
<mj-column>
|
||||
<mj-table>
|
||||
<thead>
|
||||
|
@ -37,8 +36,7 @@ const addressSection = `
|
|||
</mj-section>
|
||||
`;
|
||||
|
||||
const orderLinesSection = `
|
||||
<mj-section>
|
||||
const orderLinesSection = `<mj-section>
|
||||
<mj-column>
|
||||
<mj-table>
|
||||
<tbody>
|
||||
|
@ -72,8 +70,7 @@ const orderLinesSection = `
|
|||
</mj-section>
|
||||
`;
|
||||
|
||||
const defaultOrderCreatedMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultOrderCreatedMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -90,8 +87,7 @@ const defaultOrderCreatedMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultOrderFulfilledMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultOrderFulfilledMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -108,8 +104,7 @@ const defaultOrderFulfilledMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultOrderConfirmedMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultOrderConfirmedMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -126,8 +121,7 @@ const defaultOrderConfirmedMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultOrderFullyPaidMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultOrderFullyPaidMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -144,8 +138,7 @@ const defaultOrderFullyPaidMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultOrderCancelledMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultOrderCancelledMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -162,8 +155,7 @@ const defaultOrderCancelledMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultInvoiceSentMjmlTemplate = `
|
||||
<mjml>
|
||||
const defaultInvoiceSentMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
|
@ -178,6 +170,93 @@ const defaultInvoiceSentMjmlTemplate = `
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultAccountConfirmationMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hi {{user.first_name}}!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Your account has been created. Please follow the link to activate it:
|
||||
</mj-text>
|
||||
<mj-button href="{{confirm_url}}" background-color="black" color="white" padding-top="50px" inner-padding="20px" width="70%">
|
||||
Activate the account
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultAccountPasswordResetMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hi {{user.first_name}}!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Password reset has been requested. Please follow the link to proceed:
|
||||
</mj-text>
|
||||
<mj-button href="{{confirm_url}}" background-color="black" color="white" padding-top="50px" inner-padding="20px" width="70%">
|
||||
Reset the password
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultAccountChangeEmailRequestMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hi {{user.first_name}}!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Email address change has been requested. If you want to confirm changing the email address to {{new_email}}, please follow the link:
|
||||
</mj-text>
|
||||
<mj-button href="{{redirect_url}}" background-color="black" color="white" padding-top="50px" inner-padding="20px" width="70%">
|
||||
Change the email
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultAccountChangeEmailConfirmationMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hi {{user.first_name}}!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Email address change has been confirmed.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultAccountDeleteMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hi {{user.first_name}}!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Account deletion has been requested. If you want to confirm, please follow the link:
|
||||
</mj-text>
|
||||
<mj-button href="{{redirect_url}}" background-color="black" color="white" padding-top="50px" inner-padding="20px" width="70%">
|
||||
Delete the account
|
||||
</mj-button>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
||||
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
||||
|
@ -185,6 +264,11 @@ export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
|||
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
||||
ORDER_CANCELLED: defaultOrderCancelledMjmlTemplate,
|
||||
INVOICE_SENT: defaultInvoiceSentMjmlTemplate,
|
||||
ACCOUNT_CONFIRMATION: defaultAccountConfirmationMjmlTemplate,
|
||||
ACCOUNT_PASSWORD_RESET: defaultAccountPasswordResetMjmlTemplate,
|
||||
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
||||
ACCOUNT_DELETE: defaultAccountDeleteMjmlTemplate,
|
||||
};
|
||||
|
||||
export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
||||
|
@ -194,4 +278,9 @@ export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
|||
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
||||
ORDER_CANCELLED: "Order {{ order.number }} has been cancelled",
|
||||
INVOICE_SENT: "New invoice has been created",
|
||||
ACCOUNT_CONFIRMATION: "Account activation",
|
||||
ACCOUNT_PASSWORD_RESET: "Password reset request",
|
||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "Email change request",
|
||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Email change confirmation",
|
||||
ACCOUNT_DELETE: "Account deletion",
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { messageEventTypes } from "../../event-handlers/message-event-types";
|
|||
export const sendgridConfigurationEventObjectSchema = z.object({
|
||||
active: z.boolean(),
|
||||
eventType: z.enum(messageEventTypes),
|
||||
template: z.string().min(1),
|
||||
template: z.string(),
|
||||
});
|
||||
|
||||
export const sendgridConfigurationBaseObjectSchema = z.object({
|
||||
|
|
|
@ -27,7 +27,7 @@ const useStyles = makeStyles((theme) => {
|
|||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 20,
|
||||
maxWidth: 600,
|
||||
maxWidth: 700,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,12 +2,12 @@ import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
|
|||
import { AppManifest } from "@saleor/app-sdk/types";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
import { invoiceSentWebhook } from "./webhooks/invoice-sent";
|
||||
import { orderCancelledWebhook } from "./webhooks/order-cancelled";
|
||||
import { orderConfirmedWebhook } from "./webhooks/order-confirmed";
|
||||
import { orderCreatedWebhook } from "./webhooks/order-created";
|
||||
import { orderFulfilledWebhook } from "./webhooks/order-fulfilled";
|
||||
import { orderConfirmedWebhook } from "./webhooks/order-confirmed";
|
||||
import { orderCancelledWebhook } from "./webhooks/order-cancelled";
|
||||
import { orderFullyPaidWebhook } from "./webhooks/order-fully-paid";
|
||||
import { invoiceSentWebhook } from "./webhooks/invoice-sent";
|
||||
|
||||
export default createManifestHandler({
|
||||
async manifestFactory(context) {
|
||||
|
@ -15,7 +15,7 @@ export default createManifestHandler({
|
|||
name: "Emails & Messages",
|
||||
tokenTargetUrl: `${context.appBaseUrl}/api/register`,
|
||||
appUrl: context.appBaseUrl,
|
||||
permissions: ["MANAGE_ORDERS"],
|
||||
permissions: ["MANAGE_ORDERS", "MANAGE_USERS"],
|
||||
id: "saleor.app.emails-and-messages",
|
||||
version: packageJson.version,
|
||||
webhooks: [
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
|
||||
|
||||
import { saleorApp } from "../../saleor-app";
|
||||
import { createClient } from "../../lib/create-graphql-client";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { getBaseUrl } from "../../lib/get-base-url";
|
||||
import { registerNotifyWebhook } from "../../lib/register-notify-webhook";
|
||||
|
||||
const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
|
||||
|
||||
|
@ -21,4 +25,18 @@ export default createAppRegisterHandler({
|
|||
return true;
|
||||
},
|
||||
],
|
||||
onAuthAplSaved: async (request, ctx) => {
|
||||
// Subscribe to Notify using the mutation since it does not use subscriptions and can't be subscribed via manifest
|
||||
logger.debug("onAuthAplSaved executing");
|
||||
|
||||
const baseUrl = getBaseUrl(request.headers);
|
||||
const client = createClient(ctx.authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: ctx.authData.token })
|
||||
);
|
||||
await registerNotifyWebhook({
|
||||
client: client,
|
||||
baseUrl: baseUrl,
|
||||
});
|
||||
logger.debug("Webhook registered");
|
||||
},
|
||||
});
|
||||
|
|
121
apps/emails-and-messages/src/pages/api/webhooks/notify.ts
Normal file
121
apps/emails-and-messages/src/pages/api/webhooks/notify.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { logger as pinoLogger } from "../../../lib/logger";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
import { MessageEventTypes } from "../../../modules/event-handlers/message-event-types";
|
||||
|
||||
// Notify event handles multiple event types which are recognized based on payload field `notify_event`.
|
||||
// Handler recognizes if event is one of the supported typed and sends appropriate message.
|
||||
|
||||
interface NotifySubscriptionPayload {
|
||||
notify_event: string;
|
||||
payload: NotifyEventPayload;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
interface Meta {
|
||||
issued_at: Date;
|
||||
version: string;
|
||||
issuing_principal: IssuingPrincipal;
|
||||
}
|
||||
|
||||
interface IssuingPrincipal {
|
||||
id: null | string;
|
||||
type: null | string;
|
||||
}
|
||||
|
||||
export interface NotifyEventPayload {
|
||||
user: User;
|
||||
recipient_email: string;
|
||||
channel_slug: string;
|
||||
domain: string;
|
||||
site_name: string;
|
||||
logo_url: string;
|
||||
token?: string;
|
||||
confirm_url?: string;
|
||||
reset_url?: string;
|
||||
delete_url?: string;
|
||||
old_email?: string;
|
||||
new_email?: string;
|
||||
redirect_url?: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
is_staff: boolean;
|
||||
is_active: boolean;
|
||||
private_metadata: Record<string, string>;
|
||||
metadata: Record<string, string>;
|
||||
language_code: string;
|
||||
}
|
||||
|
||||
export const notifyWebhook = new SaleorAsyncWebhook<NotifySubscriptionPayload>({
|
||||
name: "notify",
|
||||
webhookPath: "api/webhooks/notify",
|
||||
asyncEvent: "NOTIFY_USER",
|
||||
apl: saleorApp.apl,
|
||||
query: "{}", // We are using the default payload instead of subscription
|
||||
});
|
||||
|
||||
const handler: NextWebhookApiHandler<NotifySubscriptionPayload> = async (req, res, context) => {
|
||||
const logger = pinoLogger.child({
|
||||
webhook: notifyWebhook.name,
|
||||
});
|
||||
|
||||
logger.debug("Webhook received");
|
||||
|
||||
const { payload, authData } = context;
|
||||
|
||||
const { channel_slug: channel, recipient_email: recipientEmail } = payload.payload;
|
||||
|
||||
if (!recipientEmail?.length) {
|
||||
logger.error(`The email recipient has not been specified in the event payload.`);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ error: "Email recipient has not been specified in the event payload." });
|
||||
}
|
||||
|
||||
// Notify webhook event groups multiple event types under the one webhook. We need to map it to events recognized by the App
|
||||
const notifyEventMapping: Record<string, MessageEventTypes> = {
|
||||
account_confirmation: "ACCOUNT_CONFIRMATION",
|
||||
account_delete: "ACCOUNT_DELETE",
|
||||
account_password_reset: "ACCOUNT_PASSWORD_RESET",
|
||||
account_change_email_request: "ACCOUNT_CHANGE_EMAIL_REQUEST",
|
||||
account_change_email_confirm: "ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
||||
};
|
||||
|
||||
const event = notifyEventMapping[payload.notify_event];
|
||||
if (!event) {
|
||||
logger.error(`The type of received notify event (${payload.notify_event}) is not supported.`);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ error: "Email recipient has not been specified in the event payload." });
|
||||
}
|
||||
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event,
|
||||
payload: payload.payload,
|
||||
recipientEmail,
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: "The event has been handled" });
|
||||
};
|
||||
|
||||
export default notifyWebhook.createHandler(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
Loading…
Reference in a new issue