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,
|
GiftCardSentWebhookPayloadFragment,
|
||||||
OrderRefundedWebhookPayloadFragment,
|
OrderRefundedWebhookPayloadFragment,
|
||||||
} from "../../../generated/graphql";
|
} from "../../../generated/graphql";
|
||||||
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
import {
|
||||||
|
NotifyPayloadAccountChangeEmailRequest,
|
||||||
|
NotifyPayloadAccountConfirmation,
|
||||||
|
NotifyPayloadAccountDelete,
|
||||||
|
NotifyPayloadAccountPasswordReset,
|
||||||
|
NotifyPayloadFulfillmentUpdate,
|
||||||
|
} from "../../lib/notify-event-types";
|
||||||
|
|
||||||
const exampleOrderPayload: OrderDetailsFragment = {
|
const exampleOrderPayload: OrderDetailsFragment = {
|
||||||
id: "T3JkZXI6NTdiNTBhNDAtYzRmYi00YjQzLWIxODgtM2JhZmRlMTc3MGQ5",
|
id: "T3JkZXI6NTdiNTBhNDAtYzRmYi00YjQzLWIxODgtM2JhZmRlMTc3MGQ5",
|
||||||
|
@ -167,7 +173,7 @@ const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = {
|
||||||
order: exampleOrderPayload,
|
order: exampleOrderPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountConfirmationPayload: NotifyEventPayload = {
|
const accountConfirmationPayload: NotifyPayloadAccountConfirmation = {
|
||||||
user: {
|
user: {
|
||||||
id: "VXNlcjoxOTY=",
|
id: "VXNlcjoxOTY=",
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
@ -189,7 +195,7 @@ const accountConfirmationPayload: NotifyEventPayload = {
|
||||||
logo_url: "",
|
logo_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountPasswordResetPayload: NotifyEventPayload = {
|
const accountPasswordResetPayload: NotifyPayloadAccountPasswordReset = {
|
||||||
user: {
|
user: {
|
||||||
id: "VXNlcjoxOTY=",
|
id: "VXNlcjoxOTY=",
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
@ -211,7 +217,7 @@ const accountPasswordResetPayload: NotifyEventPayload = {
|
||||||
logo_url: "",
|
logo_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountChangeEmailRequestPayload: NotifyEventPayload = {
|
const accountChangeEmailRequestPayload: NotifyPayloadAccountChangeEmailRequest = {
|
||||||
user: {
|
user: {
|
||||||
id: "VXNlcjoxOTY=",
|
id: "VXNlcjoxOTY=",
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
@ -227,15 +233,15 @@ const accountChangeEmailRequestPayload: NotifyEventPayload = {
|
||||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||||
old_email: "test@example.com1",
|
old_email: "test@example.com1",
|
||||||
new_email: "new.email@example.com1",
|
new_email: "new.email@example.com1",
|
||||||
redirect_url:
|
reset_url:
|
||||||
"http://example.com?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
"http://example.com/reset?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||||
channel_slug: "default-channel",
|
channel_slug: "default-channel",
|
||||||
domain: "demo.saleor.cloud",
|
domain: "demo.saleor.cloud",
|
||||||
site_name: "Saleor e-commerce",
|
site_name: "Saleor e-commerce",
|
||||||
logo_url: "",
|
logo_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountChangeEmailConfirmPayload: NotifyEventPayload = {
|
const accountChangeEmailConfirmPayload: NotifyPayloadAccountChangeEmailRequest = {
|
||||||
user: {
|
user: {
|
||||||
id: "VXNlcjoxOTY=",
|
id: "VXNlcjoxOTY=",
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
@ -248,14 +254,18 @@ const accountChangeEmailConfirmPayload: NotifyEventPayload = {
|
||||||
language_code: "en",
|
language_code: "en",
|
||||||
},
|
},
|
||||||
recipient_email: "user@example.com",
|
recipient_email: "user@example.com",
|
||||||
|
old_email: "old@example.com",
|
||||||
|
new_email: "new@example.com",
|
||||||
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
token: "bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||||
|
reset_url:
|
||||||
|
"http://example.com/reset?email=user%40example.com&token=bmt4kc-d6e379b762697f6aa357527af36bb9f6",
|
||||||
channel_slug: "default-channel",
|
channel_slug: "default-channel",
|
||||||
domain: "demo.saleor.cloud",
|
domain: "demo.saleor.cloud",
|
||||||
site_name: "Saleor e-commerce",
|
site_name: "Saleor e-commerce",
|
||||||
logo_url: "",
|
logo_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountDeletePayload: NotifyEventPayload = {
|
const accountDeletePayload: NotifyPayloadAccountDelete = {
|
||||||
user: {
|
user: {
|
||||||
id: "VXNlcjoxOTY=",
|
id: "VXNlcjoxOTY=",
|
||||||
email: "user@example.com",
|
email: "user@example.com",
|
||||||
|
@ -277,6 +287,184 @@ const accountDeletePayload: NotifyEventPayload = {
|
||||||
logo_url: "",
|
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
|
// TODO: UPDATE WITH BETTER DATA
|
||||||
const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = {
|
const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = {
|
||||||
channel: "default_channel",
|
channel: "default_channel",
|
||||||
|
@ -329,5 +517,6 @@ export const examplePayloads: Record<MessageEventTypes, any> = {
|
||||||
ORDER_CREATED: orderCreatedPayload,
|
ORDER_CREATED: orderCreatedPayload,
|
||||||
ORDER_FULFILLED: orderFulfilledPayload,
|
ORDER_FULFILLED: orderFulfilledPayload,
|
||||||
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
||||||
|
ORDER_FULFILLMENT_UPDATE: fulfillmentUpdatePayload,
|
||||||
ORDER_REFUNDED: orderRefundedPayload,
|
ORDER_REFUNDED: orderRefundedPayload,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const messageEventTypes = [
|
||||||
"ORDER_CONFIRMED",
|
"ORDER_CONFIRMED",
|
||||||
"ORDER_CREATED",
|
"ORDER_CREATED",
|
||||||
"ORDER_FULFILLED",
|
"ORDER_FULFILLED",
|
||||||
|
"ORDER_FULFILLMENT_UPDATE",
|
||||||
"ORDER_FULLY_PAID",
|
"ORDER_FULLY_PAID",
|
||||||
"ORDER_REFUNDED",
|
"ORDER_REFUNDED",
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -28,6 +29,7 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CONFIRMED: "Order confirmed",
|
ORDER_CONFIRMED: "Order confirmed",
|
||||||
ORDER_CREATED: "Order created",
|
ORDER_CREATED: "Order created",
|
||||||
ORDER_FULFILLED: "Order fulfilled",
|
ORDER_FULFILLED: "Order fulfilled",
|
||||||
|
ORDER_FULFILLMENT_UPDATE: "Order fulfillment updated",
|
||||||
ORDER_FULLY_PAID: "Order fully paid",
|
ORDER_FULLY_PAID: "Order fully paid",
|
||||||
ORDER_REFUNDED: "Order refunded",
|
ORDER_REFUNDED: "Order refunded",
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,42 @@ const addressSection = `<mj-section>
|
||||||
</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>
|
const orderLinesSection = `<mj-section>
|
||||||
<mj-column>
|
<mj-column>
|
||||||
<mj-table>
|
<mj-table>
|
||||||
|
@ -290,6 +326,27 @@ const defaultAccountDeleteMjmlTemplate = `<mjml>
|
||||||
</mj-body>
|
</mj-body>
|
||||||
</mjml>`;
|
</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> = {
|
export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
||||||
|
@ -302,6 +359,7 @@ export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CONFIRMED: defaultOrderConfirmedMjmlTemplate,
|
ORDER_CONFIRMED: defaultOrderConfirmedMjmlTemplate,
|
||||||
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
||||||
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
||||||
|
ORDER_FULFILLMENT_UPDATE: defaultOrderFulfillmentUpdatedMjmlTemplate,
|
||||||
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
||||||
ORDER_REFUNDED: defaultOrderRefundedMjmlTemplate,
|
ORDER_REFUNDED: defaultOrderRefundedMjmlTemplate,
|
||||||
};
|
};
|
||||||
|
@ -318,6 +376,7 @@ export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CONFIRMED: "Order {{ order.number }} has been confirmed",
|
ORDER_CONFIRMED: "Order {{ order.number }} has been confirmed",
|
||||||
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_FULFILLMENT_UPDATE: "Fulfillment for order {{ order.number }} has been updated",
|
||||||
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",
|
ORDER_REFUNDED: "Order {{ order.number }} has been refunded",
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,6 +42,7 @@ export const eventToWebhookMapping: Record<MessageEventTypes, AppWebhook> = {
|
||||||
ORDER_FULFILLED: "orderFulfilledWebhook",
|
ORDER_FULFILLED: "orderFulfilledWebhook",
|
||||||
ORDER_FULLY_PAID: "orderFullyPaidWebhook",
|
ORDER_FULLY_PAID: "orderFullyPaidWebhook",
|
||||||
ORDER_REFUNDED: "orderRefundedWebhook",
|
ORDER_REFUNDED: "orderRefundedWebhook",
|
||||||
|
ORDER_FULFILLMENT_UPDATE: "notifyWebhook",
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = createLogger({
|
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>;
|
export type WebhookStatuses = Record<AppWebhook, boolean>;
|
||||||
|
|
||||||
|
|
|
@ -2,67 +2,13 @@ import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handl
|
||||||
import { saleorApp } from "../../../saleor-app";
|
import { saleorApp } from "../../../saleor-app";
|
||||||
import { createLogger, createGraphQLClient } from "@saleor/apps-shared";
|
import { createLogger, createGraphQLClient } from "@saleor/apps-shared";
|
||||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||||
import { MessageEventTypes } from "../../../modules/event-handlers/message-event-types";
|
import { NotifySubscriptionPayload, notifyEventMapping } from "../../../lib/notify-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",
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Notify event handles multiple event types which are recognized based on payload field `notify_event`.
|
* The Notify webhook is triggered on multiple Saleor events.
|
||||||
* Handler recognizes if event is one of the supported typed and sends appropriate message.
|
* 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>({
|
export const notifyWebhook = new SaleorAsyncWebhook<NotifySubscriptionPayload>({
|
||||||
name: "notify",
|
name: "notify",
|
||||||
webhookPath: "api/webhooks/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." });
|
.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];
|
const event = notifyEventMapping[payload.notify_event];
|
||||||
|
|
||||||
if (!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.`);
|
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.` });
|
return res.status(200).json({ message: `${payload.notify_event} event is not supported.` });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue