EAM: Fulfillment updated event (#810)
* Add fulfillment update event * Add changeset * Improve comments
This commit is contained in:
parent
4c7c1c15d3
commit
c07ddb33d6
8 changed files with 583 additions and 71 deletions
5
.changeset/eight-windows-turn.md
Normal file
5
.changeset/eight-windows-turn.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-emails-and-messages": patch
|
||||
---
|
||||
|
||||
Added support for the new event: fulfillment updated.
|
310
apps/emails-and-messages/src/lib/notify-event-types.ts
Normal file
310
apps/emails-and-messages/src/lib/notify-event-types.ts
Normal file
|
@ -0,0 +1,310 @@
|
|||
import { MessageEventTypes } from "../modules/event-handlers/message-event-types";
|
||||
|
||||
// Notify webhook event groups multiple event types under the one webhook. We need to map it to events recognized by the App
|
||||
export 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",
|
||||
order_fulfillment_update: "ORDER_FULFILLMENT_UPDATE",
|
||||
};
|
||||
|
||||
interface IssuingPrincipal {
|
||||
id?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface Meta {
|
||||
issued_at: string;
|
||||
version: string;
|
||||
issuing_principal: IssuingPrincipal;
|
||||
}
|
||||
|
||||
export type NotifySubscriptionPayload = {
|
||||
meta: Meta;
|
||||
} & (
|
||||
| {
|
||||
notify_event: "account_confirmation";
|
||||
payload: NotifyPayloadAccountConfirmation;
|
||||
}
|
||||
| {
|
||||
notify_event: "account_delete";
|
||||
payload: NotifyPayloadAccountDelete;
|
||||
}
|
||||
| {
|
||||
notify_event: "account_password_reset";
|
||||
payload: NotifyPayloadAccountPasswordReset;
|
||||
}
|
||||
| {
|
||||
notify_event: "account_change_email";
|
||||
payload: NotifyPayloadAccountChangeEmailRequest;
|
||||
}
|
||||
| {
|
||||
notify_event: "account_change_email_confirm";
|
||||
payload: NotifyPayloadAccountChangeEmailConfirmation;
|
||||
}
|
||||
| {
|
||||
notify_event: "order_fulfillment_update";
|
||||
payload: NotifyPayloadFulfillmentUpdate;
|
||||
}
|
||||
);
|
||||
|
||||
export interface NotifyPayloadAccountConfirmation {
|
||||
channel_slug: string;
|
||||
confirm_url: string;
|
||||
domain: string;
|
||||
logo_url: string;
|
||||
recipient_email: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface NotifyPayloadAccountDelete {
|
||||
channel_slug: string;
|
||||
delete_url: string;
|
||||
domain: string;
|
||||
logo_url: string;
|
||||
recipient_email: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface NotifyPayloadAccountPasswordReset {
|
||||
channel_slug: string;
|
||||
domain: string;
|
||||
logo_url: string;
|
||||
recipient_email: string;
|
||||
reset_url: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface NotifyPayloadAccountChangeEmailRequest {
|
||||
channel_slug: string;
|
||||
domain: string;
|
||||
logo_url: string;
|
||||
new_email: string;
|
||||
old_email: string;
|
||||
recipient_email: string;
|
||||
reset_url: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface NotifyPayloadAccountChangeEmailConfirmation {
|
||||
channel_slug: string;
|
||||
domain: string;
|
||||
logo_url: string;
|
||||
recipient_email: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface NotifyPayloadFulfillmentUpdate {
|
||||
channel_slug: string;
|
||||
digital_lines: DigitalLine[];
|
||||
domain: string;
|
||||
fulfillment: Fulfillment;
|
||||
logo_url: string;
|
||||
order: Order;
|
||||
physical_lines: PhysicalLine[];
|
||||
recipient_email: string;
|
||||
site_name: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
first_name: string;
|
||||
id: string;
|
||||
is_active: boolean;
|
||||
is_staff: boolean;
|
||||
language_code: string;
|
||||
last_name: string;
|
||||
metadata: Metadata | null;
|
||||
private_metadata: Metadata | null;
|
||||
}
|
||||
|
||||
type Metadata = Record<string, string>;
|
||||
|
||||
interface Order {
|
||||
billing_address: Address;
|
||||
channel_slug: string;
|
||||
collection_point_name: string | null;
|
||||
created: string;
|
||||
currency: string;
|
||||
discount_amount: number;
|
||||
display_gross_prices: boolean;
|
||||
email: string;
|
||||
id: string;
|
||||
language_code: string;
|
||||
lines: Line[];
|
||||
metadata: Metadata | null;
|
||||
number: number;
|
||||
order_details_url: string;
|
||||
private_metadata: Metadata | null;
|
||||
shipping_address: Address;
|
||||
shipping_method_name: string;
|
||||
shipping_price_gross_amount: string;
|
||||
shipping_price_net_amount: string;
|
||||
status: string;
|
||||
subtotal_gross_amount: string;
|
||||
subtotal_net_amount: string;
|
||||
tax_amount: string;
|
||||
token: string;
|
||||
total_gross_amount: string;
|
||||
total_net_amount: string;
|
||||
undiscounted_total_gross_amount: string;
|
||||
undiscounted_total_net_amount: string;
|
||||
voucher_discount: number | null;
|
||||
}
|
||||
|
||||
interface Line {
|
||||
currency: string;
|
||||
digital_url: string | null;
|
||||
id: string;
|
||||
is_digital: boolean;
|
||||
is_shipping_required: boolean;
|
||||
metadata: Metadata | null;
|
||||
product_name: string;
|
||||
product_sku: string;
|
||||
product_variant_id: string;
|
||||
product: Product;
|
||||
quantity_fulfilled: number;
|
||||
quantity: number;
|
||||
tax_rate: string;
|
||||
total_gross_amount: string;
|
||||
total_net_amount: string;
|
||||
total_tax_amount: string;
|
||||
translated_product_name: string;
|
||||
translated_variant_name: string;
|
||||
unit_discount_amount: string;
|
||||
unit_discount_reason: string | null;
|
||||
unit_discount_type: string;
|
||||
unit_discount_value: string;
|
||||
unit_price_gross_amount: string;
|
||||
unit_price_net_amount: string;
|
||||
unit_tax_amount: string;
|
||||
variant_name: string;
|
||||
variant: Variant;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
attributes: AttributeWithAssignment[];
|
||||
first_image: Image;
|
||||
id: string;
|
||||
images: Image[];
|
||||
weight: string;
|
||||
}
|
||||
|
||||
interface Variant {
|
||||
first_image: Image;
|
||||
id: string;
|
||||
images: Image[];
|
||||
is_preorder: boolean;
|
||||
preorder_end_date: string | null;
|
||||
weight: string;
|
||||
}
|
||||
|
||||
interface AttributeWithAssignment {
|
||||
assignment: AttributeAssignment;
|
||||
values: AttributeValue[];
|
||||
}
|
||||
|
||||
interface AttributeAssignment {
|
||||
attribute: Attribute;
|
||||
}
|
||||
|
||||
interface Attribute {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface AttributeValue {
|
||||
file_url: string | null;
|
||||
name: string;
|
||||
slug: string | null;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface ImageSizeMapping {
|
||||
"32": string;
|
||||
"64": string;
|
||||
"128": string;
|
||||
"256": string;
|
||||
"512": string;
|
||||
"1024": string;
|
||||
"2048": string;
|
||||
"4096": string;
|
||||
}
|
||||
|
||||
interface Image {
|
||||
original: ImageSizeMapping;
|
||||
}
|
||||
|
||||
interface Address {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company_name: string;
|
||||
street_address_1: string;
|
||||
street_address_2: string;
|
||||
city: string;
|
||||
city_area: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
country_area: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
interface Fulfillment {
|
||||
tracking_number: string;
|
||||
is_tracking_number_url: boolean;
|
||||
}
|
||||
|
||||
interface PhysicalLine {
|
||||
id: string;
|
||||
order_line: OrderLine;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
interface DigitalLine {
|
||||
id: string;
|
||||
order_line: OrderLine;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
interface OrderLine {
|
||||
currency: string;
|
||||
digital_url: string | null;
|
||||
id: string;
|
||||
is_digital: boolean;
|
||||
is_shipping_required: boolean;
|
||||
metadata: Metadata | null;
|
||||
product_name: string;
|
||||
product_sku: string;
|
||||
product_variant_id: string;
|
||||
product: Product;
|
||||
quantity_fulfilled: number;
|
||||
quantity: number;
|
||||
tax_rate: string;
|
||||
total_gross_amount: string;
|
||||
total_net_amount: string;
|
||||
total_tax_amount: string;
|
||||
translated_product_name: string;
|
||||
translated_variant_name: string;
|
||||
unit_discount_amount: string;
|
||||
unit_discount_reason: string | null;
|
||||
unit_discount_type: string;
|
||||
unit_discount_value: string;
|
||||
unit_price_gross_amount: string;
|
||||
unit_price_net_amount: string;
|
||||
unit_tax_amount: string;
|
||||
variant_name: string;
|
||||
variant: Variant;
|
||||
}
|
|
@ -10,7 +10,13 @@ import {
|
|||
GiftCardSentWebhookPayloadFragment,
|
||||
OrderRefundedWebhookPayloadFragment,
|
||||
} from "../../../generated/graphql";
|
||||
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
||||
import {
|
||||
NotifyPayloadAccountChangeEmailRequest,
|
||||
NotifyPayloadAccountConfirmation,
|
||||
NotifyPayloadAccountDelete,
|
||||
NotifyPayloadAccountPasswordReset,
|
||||
NotifyPayloadFulfillmentUpdate,
|
||||
} from "../../lib/notify-event-types";
|
||||
|
||||
const exampleOrderPayload: OrderDetailsFragment = {
|
||||
id: "T3JkZXI6NTdiNTBhNDAtYzRmYi00YjQzLWIxODgtM2JhZmRlMTc3MGQ5",
|
||||
|
@ -167,7 +173,7 @@ const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = {
|
|||
order: exampleOrderPayload,
|
||||
};
|
||||
|
||||
const accountConfirmationPayload: NotifyEventPayload = {
|
||||
const accountConfirmationPayload: NotifyPayloadAccountConfirmation = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
|
@ -189,7 +195,7 @@ const accountConfirmationPayload: NotifyEventPayload = {
|
|||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountPasswordResetPayload: NotifyEventPayload = {
|
||||
const accountPasswordResetPayload: NotifyPayloadAccountPasswordReset = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
|
@ -211,7 +217,7 @@ const accountPasswordResetPayload: NotifyEventPayload = {
|
|||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountChangeEmailRequestPayload: NotifyEventPayload = {
|
||||
const accountChangeEmailRequestPayload: NotifyPayloadAccountChangeEmailRequest = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
|
@ -227,15 +233,15 @@ const accountChangeEmailRequestPayload: NotifyEventPayload = {
|
|||
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",
|
||||
reset_url:
|
||||
"http://example.com/reset?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 = {
|
||||
const accountChangeEmailConfirmPayload: NotifyPayloadAccountChangeEmailRequest = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
|
@ -248,14 +254,18 @@ const accountChangeEmailConfirmPayload: NotifyEventPayload = {
|
|||
language_code: "en",
|
||||
},
|
||||
recipient_email: "user@example.com",
|
||||
old_email: "old@example.com",
|
||||
new_email: "new@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
reset_url:
|
||||
"http://example.com/reset?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
const accountDeletePayload: NotifyEventPayload = {
|
||||
const accountDeletePayload: NotifyPayloadAccountDelete = {
|
||||
user: {
|
||||
id: "VXNlcjoxOTY=",
|
||||
email: "user@example.com",
|
||||
|
@ -277,6 +287,184 @@ const accountDeletePayload: NotifyEventPayload = {
|
|||
logo_url: "",
|
||||
};
|
||||
|
||||
const orderLineMonospaceTeePayloadFragment: NotifyPayloadFulfillmentUpdate["order"]["lines"][0] = {
|
||||
id: "T3JkZXJMaW5lOjIwMDg4MmMzLWU3NjItNGE0NS05ZjUxLTUyZDAxYTE2ODZjOQ==",
|
||||
product: {
|
||||
id: "UHJvZHVjdDoxMzQ=",
|
||||
attributes: [
|
||||
{
|
||||
assignment: {
|
||||
attribute: {
|
||||
slug: "material",
|
||||
name: "Material",
|
||||
},
|
||||
},
|
||||
values: [
|
||||
{
|
||||
name: "Cotton",
|
||||
value: "",
|
||||
slug: "cotton",
|
||||
file_url: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
weight: "",
|
||||
first_image: {
|
||||
original: {
|
||||
"32": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/32/",
|
||||
"64": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/64/",
|
||||
"128": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/128/",
|
||||
"256": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/256/",
|
||||
"512": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/512/",
|
||||
"1024": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/1024/",
|
||||
"2048": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/2048/",
|
||||
"4096": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/4096/",
|
||||
},
|
||||
},
|
||||
images: [
|
||||
{
|
||||
original: {
|
||||
"32": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/32/",
|
||||
"64": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/64/",
|
||||
"128": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/128/",
|
||||
"256": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/256/",
|
||||
"512": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/512/",
|
||||
"1024": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/1024/",
|
||||
"2048": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/2048/",
|
||||
"4096": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE4/4096/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
product_name: "Monospace Tee",
|
||||
translated_product_name: "Monospace Tee",
|
||||
variant_name: "S",
|
||||
variant: {
|
||||
id: "UHJvZHVjdFZhcmlhbnQ6MzQ4",
|
||||
weight: "",
|
||||
is_preorder: false,
|
||||
preorder_end_date: null,
|
||||
first_image: {
|
||||
original: {
|
||||
"32": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/32/",
|
||||
"64": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/64/",
|
||||
"128": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/128/",
|
||||
"256": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/256/",
|
||||
"512": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/512/",
|
||||
"1024": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/1024/",
|
||||
"2048": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/2048/",
|
||||
"4096": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/4096/",
|
||||
},
|
||||
},
|
||||
images: [
|
||||
{
|
||||
original: {
|
||||
"32": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/32/",
|
||||
"64": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/64/",
|
||||
"128": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/128/",
|
||||
"256": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/256/",
|
||||
"512": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/512/",
|
||||
"1024": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/1024/",
|
||||
"2048": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/2048/",
|
||||
"4096": "https://example.eu.saleor.cloud/thumbnail/UHJvZHVjdE1lZGlhOjE3/4096/",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
translated_variant_name: "S",
|
||||
product_sku: "328223580",
|
||||
product_variant_id: "UHJvZHVjdFZhcmlhbnQ6MzQ4",
|
||||
quantity: 1,
|
||||
quantity_fulfilled: 1,
|
||||
currency: "PLN",
|
||||
unit_price_net_amount: "90.00",
|
||||
unit_price_gross_amount: "90.00",
|
||||
unit_tax_amount: "0.00",
|
||||
total_gross_amount: "90.00",
|
||||
total_net_amount: "90.00",
|
||||
total_tax_amount: "0.00",
|
||||
tax_rate: "0.0000",
|
||||
is_shipping_required: true,
|
||||
is_digital: false,
|
||||
digital_url: null,
|
||||
unit_discount_value: "0.000",
|
||||
unit_discount_reason: null,
|
||||
unit_discount_type: "fixed",
|
||||
unit_discount_amount: "0.000",
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
const addressPayloadFragment: NotifyPayloadFulfillmentUpdate["order"]["billing_address"] = {
|
||||
first_name: "Caitlin",
|
||||
last_name: "Johnson",
|
||||
company_name: "",
|
||||
street_address_1: "8518 Pamela Track Apt. 164",
|
||||
street_address_2: "",
|
||||
city: "APRILSHIRE",
|
||||
city_area: "",
|
||||
postal_code: "28290",
|
||||
country: "US",
|
||||
country_area: "NC",
|
||||
phone: "",
|
||||
};
|
||||
|
||||
const orderPayloadFragment: NotifyPayloadFulfillmentUpdate["order"] = {
|
||||
private_metadata: {},
|
||||
metadata: {},
|
||||
status: "fulfilled",
|
||||
language_code: "en",
|
||||
currency: "PLN",
|
||||
total_net_amount: "468.68",
|
||||
undiscounted_total_net_amount: "468.68",
|
||||
total_gross_amount: "468.68",
|
||||
undiscounted_total_gross_amount: "468.68",
|
||||
display_gross_prices: true,
|
||||
id: "T3JkZXI6MzU4YzcxNTktZmZlYy00ODI3LWI2MzYtYTNmYTEwMTA2MTI5",
|
||||
token: "358c7159-ffec-4827-b636-a3fa10106129",
|
||||
number: 231,
|
||||
channel_slug: "channel-pln",
|
||||
created: "2023-07-13 10:54:32.527314+00:00",
|
||||
shipping_price_net_amount: "18.680",
|
||||
shipping_price_gross_amount: "18.680",
|
||||
order_details_url: "",
|
||||
email: "caitlin.johnson@example.com",
|
||||
subtotal_gross_amount: "450.00",
|
||||
subtotal_net_amount: "450.00",
|
||||
tax_amount: "0.00",
|
||||
lines: [orderLineMonospaceTeePayloadFragment],
|
||||
billing_address: addressPayloadFragment,
|
||||
shipping_address: addressPayloadFragment,
|
||||
shipping_method_name: "FedEx",
|
||||
collection_point_name: null,
|
||||
voucher_discount: null,
|
||||
discount_amount: 0,
|
||||
};
|
||||
|
||||
const fulfillmentPayloadFragment = {
|
||||
is_tracking_number_url: false,
|
||||
tracking_number: "1111-1111-1111-1111",
|
||||
};
|
||||
|
||||
const fulfillmentUpdatePayload: NotifyPayloadFulfillmentUpdate = {
|
||||
fulfillment: fulfillmentPayloadFragment,
|
||||
order: orderPayloadFragment,
|
||||
physical_lines: [
|
||||
{
|
||||
id: "XXXXXXXX",
|
||||
order_line: orderLineMonospaceTeePayloadFragment,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
digital_lines: [],
|
||||
recipient_email: "user@example.com",
|
||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||
channel_slug: "default-channel",
|
||||
domain: "demo.saleor.cloud",
|
||||
site_name: "Saleor e-commerce",
|
||||
logo_url: "",
|
||||
};
|
||||
|
||||
// TODO: UPDATE WITH BETTER DATA
|
||||
const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = {
|
||||
channel: "default_channel",
|
||||
|
@ -329,5 +517,6 @@ export const examplePayloads: Record<MessageEventTypes, any> = {
|
|||
ORDER_CREATED: orderCreatedPayload,
|
||||
ORDER_FULFILLED: orderFulfilledPayload,
|
||||
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
||||
ORDER_FULFILLMENT_UPDATE: fulfillmentUpdatePayload,
|
||||
ORDER_REFUNDED: orderRefundedPayload,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ export const messageEventTypes = [
|
|||
"ORDER_CONFIRMED",
|
||||
"ORDER_CREATED",
|
||||
"ORDER_FULFILLED",
|
||||
"ORDER_FULFILLMENT_UPDATE",
|
||||
"ORDER_FULLY_PAID",
|
||||
"ORDER_REFUNDED",
|
||||
] as const;
|
||||
|
@ -28,6 +29,7 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
|||
ORDER_CONFIRMED: "Order confirmed",
|
||||
ORDER_CREATED: "Order created",
|
||||
ORDER_FULFILLED: "Order fulfilled",
|
||||
ORDER_FULFILLMENT_UPDATE: "Order fulfillment updated",
|
||||
ORDER_FULLY_PAID: "Order fully paid",
|
||||
ORDER_REFUNDED: "Order refunded",
|
||||
};
|
||||
|
|
|
@ -36,6 +36,42 @@ const addressSection = `<mj-section>
|
|||
</mj-section>
|
||||
`;
|
||||
|
||||
const addressSectionForNotify = `<mj-section>
|
||||
<mj-column>
|
||||
<mj-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Billing address
|
||||
</th>
|
||||
<th>
|
||||
Shipping address
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{{#if order.billing_address}}
|
||||
{{ order.billing_address.street_address_1 }}
|
||||
{{else}}
|
||||
No billing address
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if order.shipping_address}}
|
||||
{{ order.shipping_address.street_address_1}}
|
||||
{{else}}
|
||||
No shipping required
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</mj-table>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
`;
|
||||
|
||||
const orderLinesSection = `<mj-section>
|
||||
<mj-column>
|
||||
<mj-table>
|
||||
|
@ -290,6 +326,27 @@ const defaultAccountDeleteMjmlTemplate = `<mjml>
|
|||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
const defaultOrderFulfillmentUpdatedMjmlTemplate = `<mjml>
|
||||
<mj-body>
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-text font-size="16px">
|
||||
Hello!
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
Fulfillment for the order {{ order.number }} has been updated.
|
||||
</mj-text>
|
||||
{{#if fulfillment.tracking_number }}
|
||||
<mj-text>
|
||||
Tracking number: {{ fulfillment.tracking_number }}
|
||||
</mj-text>
|
||||
{{/if}}
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
${addressSectionForNotify}
|
||||
</mj-body>
|
||||
</mjml>`;
|
||||
|
||||
export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
||||
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
||||
|
@ -302,6 +359,7 @@ export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
|||
ORDER_CONFIRMED: defaultOrderConfirmedMjmlTemplate,
|
||||
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
||||
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
||||
ORDER_FULFILLMENT_UPDATE: defaultOrderFulfillmentUpdatedMjmlTemplate,
|
||||
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
||||
ORDER_REFUNDED: defaultOrderRefundedMjmlTemplate,
|
||||
};
|
||||
|
@ -318,6 +376,7 @@ export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
|||
ORDER_CONFIRMED: "Order {{ order.number }} has been confirmed",
|
||||
ORDER_CREATED: "Order {{ order.number }} has been created",
|
||||
ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled",
|
||||
ORDER_FULFILLMENT_UPDATE: "Fulfillment for order {{ order.number }} has been updated",
|
||||
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
||||
ORDER_REFUNDED: "Order {{ order.number }} has been refunded",
|
||||
};
|
||||
|
|
|
@ -42,6 +42,7 @@ export const eventToWebhookMapping: Record<MessageEventTypes, AppWebhook> = {
|
|||
ORDER_FULFILLED: "orderFulfilledWebhook",
|
||||
ORDER_FULLY_PAID: "orderFullyPaidWebhook",
|
||||
ORDER_REFUNDED: "orderRefundedWebhook",
|
||||
ORDER_FULFILLMENT_UPDATE: "notifyWebhook",
|
||||
};
|
||||
|
||||
const logger = createLogger({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppWebhook, AppWebhooks } from "./webhook-management-service";
|
||||
import { AppWebhook } from "./webhook-management-service";
|
||||
|
||||
export type WebhookStatuses = Record<AppWebhook, boolean>;
|
||||
|
||||
|
|
|
@ -2,67 +2,13 @@ import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handl
|
|||
import { saleorApp } from "../../../saleor-app";
|
||||
import { createLogger, createGraphQLClient } from "@saleor/apps-shared";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { MessageEventTypes } from "../../../modules/event-handlers/message-event-types";
|
||||
|
||||
// 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",
|
||||
};
|
||||
import { NotifySubscriptionPayload, notifyEventMapping } from "../../../lib/notify-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.
|
||||
* The Notify webhook is triggered on multiple Saleor events.
|
||||
* Type of the message is determined by `notify_event` field in the payload.
|
||||
*/
|
||||
|
||||
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",
|
||||
|
@ -89,10 +35,10 @@ const handler: NextWebhookApiHandler<NotifySubscriptionPayload> = async (req, re
|
|||
.json({ error: "Email recipient has not been specified in the event payload." });
|
||||
}
|
||||
|
||||
// Since NOTIFY can be send on events unrelated to this app, lack of mapping means the App does not support it
|
||||
const event = notifyEventMapping[payload.notify_event];
|
||||
|
||||
if (!event) {
|
||||
// NOTIFY webhook sends multiple events to the same endpoint. The app supports only a subset of them.
|
||||
logger.debug(`The type of received notify event (${payload.notify_event}) is not supported.`);
|
||||
return res.status(200).json({ message: `${payload.notify_event} event is not supported.` });
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue