From 790a47ee081e3f565e12c7ab84248ea6a32d3bc4 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Thu, 13 Jul 2023 17:50:00 +0200 Subject: [PATCH] Add order refunded webhook (#751) * Update the app sdk package * Add order refunded webhook * Add changeset --- .changeset/cyan-pianos-hide.md | 5 + .../graphql/schema.graphql | 1401 ++++++++++++++++- apps/emails-and-messages/package.json | 2 +- .../src/lib/get-event-form-status.test.ts | 6 +- .../src/lib/get-event-form-status.ts | 52 +- .../event-handlers/default-payloads.ts | 24 +- .../event-handlers/message-event-types.ts | 38 +- .../feature-flag-service/get-feature-flags.ts | 3 +- .../src/modules/smtp/default-templates.ts | 19 + ...bhook-statuses-from-configurations.test.ts | 56 +- ...et-webhook-statuses-from-configurations.ts | 12 +- .../webhook-management-service.test.ts | 15 +- .../webhook-management-service.ts | 4 +- .../webhook-management/webhook-status-dict.ts | 20 + .../src/pages/api/webhooks/order-refunded.ts | 87 + 15 files changed, 1571 insertions(+), 173 deletions(-) create mode 100644 .changeset/cyan-pianos-hide.md create mode 100644 apps/emails-and-messages/src/modules/webhook-management/webhook-status-dict.ts create mode 100644 apps/emails-and-messages/src/pages/api/webhooks/order-refunded.ts diff --git a/.changeset/cyan-pianos-hide.md b/.changeset/cyan-pianos-hide.md new file mode 100644 index 0000000..d3ef758 --- /dev/null +++ b/.changeset/cyan-pianos-hide.md @@ -0,0 +1,5 @@ +--- +"saleor-app-emails-and-messages": patch +--- + +Added support for new event: order refunded. diff --git a/apps/emails-and-messages/graphql/schema.graphql b/apps/emails-and-messages/graphql/schema.graphql index df3369e..9a3320b 100644 --- a/apps/emails-and-messages/graphql/schema.graphql +++ b/apps/emails-and-messages/graphql/schema.graphql @@ -448,9 +448,25 @@ type Query { """Filtering options for products.""" filter: ProductFilterInput + """ + Where filtering options. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + where: ProductWhereInput + """Sort products.""" sortBy: ProductOrder + """ + Search products. + + Added in Saleor 3.14. + """ + search: String + """Slug of a channel for which the data should be returned.""" channel: String @@ -1430,7 +1446,11 @@ type Query { cityArea: String ): AddressValidationData @doc(category: "Users") - """Look up an address by ID.""" + """ + Look up an address by ID. + + Requires one of the following permissions: MANAGE_USERS, OWNER. + """ address( """ID of an address.""" id: ID! @@ -1755,9 +1775,36 @@ enum WebhookEventTypeEnum @doc(category: "Webhooks") { """ ORDER_CONFIRMED + """ + Payment has been made. The order may be partially or fully paid. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_PAID + """Payment is made and an order is fully paid.""" ORDER_FULLY_PAID + """ + The order received a refund. The order may be partially or fully refunded. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_REFUNDED + + """ + The order is fully refunded. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_FULLY_REFUNDED + """ An order is updated; triggered for all changes related to an order; covers all other order webhooks, except for ORDER_CREATED. """ @@ -1779,6 +1826,15 @@ enum WebhookEventTypeEnum @doc(category: "Webhooks") { """ ORDER_METADATA_UPDATED + """ + Orders are imported. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_BULK_CREATED + """A draft order is created.""" DRAFT_ORDER_CREATED @@ -2350,9 +2406,36 @@ enum WebhookEventTypeAsyncEnum @doc(category: "Webhooks") { """ ORDER_CONFIRMED + """ + Payment has been made. The order may be partially or fully paid. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_PAID + """Payment is made and an order is fully paid.""" ORDER_FULLY_PAID + """ + The order received a refund. The order may be partially or fully refunded. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_REFUNDED + + """ + The order is fully refunded. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_FULLY_REFUNDED + """ An order is updated; triggered for all changes related to an order; covers all other order webhooks, except for ORDER_CREATED. """ @@ -2374,6 +2457,15 @@ enum WebhookEventTypeAsyncEnum @doc(category: "Webhooks") { """ ORDER_METADATA_UPDATED + """ + Orders are imported. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + ORDER_BULK_CREATED + """A draft order is created.""" DRAFT_ORDER_CREATED @@ -2785,6 +2877,15 @@ type App implements Node & ObjectWithMetadata @doc(category: "Apps") { Added in Saleor 3.1. """ extensions: [AppExtension!]! + + """ + App's brand data. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + brand: AppBrand } interface ObjectWithMetadata { @@ -2865,6 +2966,7 @@ enum PermissionEnum @doc(category: "Users") { MANAGE_GIFT_CARD MANAGE_MENUS MANAGE_ORDERS + MANAGE_ORDERS_IMPORT MANAGE_PAGES MANAGE_PAGE_TYPES_AND_ATTRIBUTES HANDLE_PAYMENTS @@ -2961,6 +3063,62 @@ enum AppExtensionTargetEnum @doc(category: "Apps") { APP_PAGE } +""" +Represents the app's brand data. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type AppBrand @doc(category: "Apps") { + """ + App's logos details. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + logo: AppBrandLogo! +} + +""" +Represents the app's brand logo data. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type AppBrandLogo @doc(category: "Apps") { + """ + URL to the default logo image. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + default( + """ + Desired longest side the image in pixels. Defaults to 4096. Images are never cropped. Pass 0 to retrieve the original size (not recommended). + """ + size: Int + + """ + The format of the image. When not provided, format of the original image will be used. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + format: IconThumbnailFormatEnum = ORIGINAL + ): String! +} + +"""Thumbnail formats for icon images.""" +enum IconThumbnailFormatEnum { + ORIGINAL + WEBP +} + type EventDeliveryCountableConnection { """Pagination data for this connection.""" pageInfo: PageInfo! @@ -3161,12 +3319,16 @@ enum WebhookSampleEventTypeEnum @doc(category: "Webhooks") { MENU_ITEM_DELETED ORDER_CREATED ORDER_CONFIRMED + ORDER_PAID ORDER_FULLY_PAID + ORDER_REFUNDED + ORDER_FULLY_REFUNDED ORDER_UPDATED ORDER_CANCELLED ORDER_EXPIRED ORDER_FULFILLED ORDER_METADATA_UPDATED + ORDER_BULK_CREATED DRAFT_ORDER_CREATED DRAFT_ORDER_UPDATED DRAFT_ORDER_DELETED @@ -5038,6 +5200,15 @@ type OrderSettings { Note: this API is currently in Feature Preview and can be subject to changes at later point. """ defaultTransactionFlowStrategy: TransactionFlowStrategyEnum! + + """ + The time in days after expired orders will be deleted. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + deleteExpiredOrdersAfter: Day! } """ @@ -5070,6 +5241,9 @@ enum TransactionFlowStrategyEnum @doc(category: "Payments") { CHARGE } +"""The `Day` scalar type represents number of days by integer value.""" +scalar Day + """Represents shipping method postal code rule.""" type ShippingMethodPostalCodeRule implements Node @doc(category: "Shipping") { """The ID of the object.""" @@ -6194,6 +6368,15 @@ type Category implements Node & ObjectWithMetadata @doc(category: "Products") { """ filter: ProductFilterInput + """ + Filtering options for products. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + where: ProductWhereInput + """ Sort products. @@ -6404,6 +6587,123 @@ input PriceRangeInput { lte: Float } +input ProductWhereInput @doc(category: "Products") { + metadata: [MetadataFilter!] + ids: [ID!] + + """Filter by product name.""" + name: StringFilterInput + + """Filter by product slug.""" + slug: StringFilterInput + + """Filter by product type.""" + productType: GlobalIDFilterInput + + """Filter by product category.""" + category: GlobalIDFilterInput + + """Filter by collection.""" + collection: GlobalIDFilterInput + + """Filter by availability for purchase.""" + isAvailable: Boolean + + """Filter by public visibility.""" + isPublished: Boolean + + """Filter by visibility on the channel.""" + isVisibleInListing: Boolean + + """Filter by the publication date.""" + publishedFrom: DateTime + + """Filter by the date of availability for purchase.""" + availableFrom: DateTime + + """Filter by product with category assigned.""" + hasCategory: Boolean + + """Filter by product variant price.""" + price: DecimalFilterInput + + """Filter by the lowest variant price after discounts.""" + minimalPrice: DecimalFilterInput + + """Filter by attributes associated with the product.""" + attributes: [AttributeInput!] + + """Filter by variants having specific stock status.""" + stockAvailability: StockAvailability + + """Filter by stock of the product variant.""" + stocks: ProductStockFilterInput + + """Filter on whether product is a gift card or not.""" + giftCard: Boolean + + """Filter by product with preordered variants.""" + hasPreorderedVariants: Boolean + + """Filter by when was the most recent update.""" + updatedAt: DateTimeRangeInput + + """List of conditions that must be met.""" + AND: [ProductWhereInput!] + + """A list of conditions of which at least one must be met.""" + OR: [ProductWhereInput!] +} + +""" +Define the filtering options for foreign key fields. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +input GlobalIDFilterInput { + """The value equal to.""" + eq: ID + + """The value included in.""" + oneOf: [ID!] +} + +""" +Define the filtering options for decimal fields. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +input DecimalFilterInput { + """The value equal to.""" + eq: Decimal + + """The value included in.""" + oneOf: [Decimal!] + + """The value in range.""" + range: DecimalRangeInput +} + +""" +Custom Decimal implementation. + +Returns Decimal as a float in the API, +parses float to the Decimal on the way back. +""" +scalar Decimal + +input DecimalRangeInput { + """Decimal value greater than or equal to.""" + gte: Decimal + + """Decimal value less than or equal to.""" + lte: Decimal +} + input ProductOrder @doc(category: "Products") { """Specifies the direction in which to sort products.""" direction: OrderDirection! @@ -6712,7 +7012,7 @@ type ProductVariant implements Node & ObjectWithMetadata @doc(category: "Product externalReference: String } -"""Represents product varaint channel listing.""" +"""Represents product variant channel listing.""" type ProductVariantChannelListing implements Node @doc(category: "Products") { id: ID! channel: Channel! @@ -7273,6 +7573,15 @@ type Collection implements Node & ObjectWithMetadata @doc(category: "Products") """Filtering options for products.""" filter: ProductFilterInput + """ + Filtering options for products. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + where: ProductWhereInput + """Sort products.""" sortBy: ProductOrder @@ -8880,6 +9189,15 @@ type Shop { """ staffNotificationRecipients: [StaffNotificationRecipient!] + """ + Determines if account confirmation by email is enabled. + + Added in Saleor 3.14. + + Requires one of the following permissions: MANAGE_SETTINGS. + """ + enableAccountConfirmationByEmail: Boolean + """ Resource limitations and current usage if any set for a shop @@ -9143,6 +9461,24 @@ type User implements Node & ObjectWithMetadata @doc(category: "Users") { """List of user's permission groups which user can manage.""" editableGroups: [Group!] + + """ + List of channels the user has access to. The sum of channels from all user groups. If at least one group has `restrictedAccessToChannels` set to False - all channels are returned. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + accessibleChannels: [Channel!] + + """ + Determine if user have restricted access to channels. False if at least one user group has `restrictedAccessToChannels` set to False. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + restrictedAccessToChannels: Boolean! avatar( """ Desired longest side the image in pixels. Defaults to 4096. Images are never cropped. Pass 0 to retrieve the original size (not recommended). @@ -9448,7 +9784,7 @@ type GiftCard implements Node & ObjectWithMetadata @doc(category: "Gift cards") Added in Saleor 3.1. """ - usedBy: User + usedBy: User @deprecated(reason: "This field will be removed in Saleor 4.0.") """ Email address of the user who bought or issued gift card. @@ -9464,7 +9800,7 @@ type GiftCard implements Node & ObjectWithMetadata @doc(category: "Gift cards") Added in Saleor 3.1. """ - usedByEmail: String + usedByEmail: String @deprecated(reason: "This field will be removed in Saleor 4.0.") lastUsedOn: DateTime expiryDate: Date @@ -9794,7 +10130,7 @@ type TransactionItem implements Node & ObjectWithMetadata @doc(category: "Paymen refundPendingAmount: Money! """Total amount voided for this payment.""" - voidedAmount: Money! @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature).Use `canceledAmount` instead.") + voidedAmount: Money! @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature).Use `canceledAmount` instead.") """ Total amount canceled for this payment. @@ -9821,10 +10157,10 @@ type TransactionItem implements Node & ObjectWithMetadata @doc(category: "Paymen chargePendingAmount: Money! """Status of transaction.""" - status: String! @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions.") + status: String! @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions.") """Type of transaction.""" - type: String! @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature). Use `name` or `message` instead.") + type: String! @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature). Use `name` or `message` instead.") """ Name of the transaction. @@ -9841,7 +10177,7 @@ type TransactionItem implements Node & ObjectWithMetadata @doc(category: "Paymen message: String! """Reference of transaction.""" - reference: String! @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature).Use `pspReference` instead.") + reference: String! @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature).Use `pspReference` instead.") """ PSP reference of transaction. @@ -10671,6 +11007,7 @@ enum OrderOriginEnum @doc(category: "Orders") { CHECKOUT DRAFT REISSUE + BULK_CREATE } """An enumeration.""" @@ -10959,7 +11296,7 @@ type OrderEvent implements Node @doc(category: "Orders") { discount: OrderEventDiscountObject """The status of payment's transaction.""" - status: TransactionStatus @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature).Use `TransactionEvent` to track the status of `TransactionItem`.") + status: TransactionStatus @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature).Use `TransactionEvent` to track the status of `TransactionItem`.") """The reference of payment's transaction.""" reference: String @@ -11000,13 +11337,13 @@ enum OrderEventsEnum @doc(category: "Orders") { TRANSACTION_CHARGE_REQUESTED """ - This field will be removed in Saleor 3.14 (Preview Feature). Use `TRANSACTION_CHARGE_REQUESTED` instead. + This field will be removed in Saleor 3.15 (Preview Feature). Use `TRANSACTION_CHARGE_REQUESTED` instead. """ TRANSACTION_CAPTURE_REQUESTED TRANSACTION_REFUND_REQUESTED """ - This field will be removed in Saleor 3.14 (Preview Feature). Use `TRANSACTION_CANCEL_REQUESTED` instead. + This field will be removed in Saleor 3.15 (Preview Feature). Use `TRANSACTION_CANCEL_REQUESTED` instead. """ TRANSACTION_VOID_REQUESTED TRANSACTION_CANCEL_REQUESTED @@ -11117,6 +11454,7 @@ type OrderDiscount implements Node @doc(category: "Discounts") { """An enumeration.""" enum OrderDiscountType @doc(category: "Discounts") { + SALE VOUCHER MANUAL } @@ -11226,10 +11564,10 @@ type TransactionEvent implements Node @doc(category: "Payments") { createdAt: DateTime! """Status of transaction's event.""" - status: TransactionStatus @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature). Use `type` instead.") + status: TransactionStatus @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature). Use `type` instead.") """Reference of transaction's event.""" - reference: String! @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature).Use `pspReference` instead.") + reference: String! @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature).Use `pspReference` instead.") """ PSP reference of transaction. @@ -11239,7 +11577,7 @@ type TransactionEvent implements Node @doc(category: "Payments") { pspReference: String! """Name of the transaction's event.""" - name: String @deprecated(reason: "This field will be removed in Saleor 3.14 (Preview Feature). Use `message` instead.") + name: String @deprecated(reason: "This field will be removed in Saleor 3.15 (Preview Feature). Use `message` instead.") """ Message related to the transaction's event. @@ -11455,6 +11793,24 @@ type Group implements Node @doc(category: "Users") { True, if the currently authenticated user has rights to manage a group. """ userCanManage: Boolean! + + """ + List of channels the group has access to. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + accessibleChannels: [Channel!] + + """ + Determine if the group have restricted access to channels. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + restrictedAccessToChannels: Boolean! } """History log of the customer.""" @@ -12586,6 +12942,15 @@ type AppInstallation implements Node & Job @doc(category: "Apps") { message: String appName: String! manifestUrl: String! + + """ + App's brand data. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + brand: AppBrand } type AppCountableConnection @doc(category: "Apps") { @@ -13033,7 +13398,7 @@ type Mutation { """ stockBulkUpdate( """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" - errorPolicy: ErrorPolicyEnum = reject_everything + errorPolicy: ErrorPolicyEnum """Input list of stocks to update.""" stocks: [StockBulkUpdateInput!]! @@ -13530,8 +13895,8 @@ type Mutation { Requires one of the following permissions: MANAGE_PRODUCTS. """ productBulkCreate( - """Policies of error handling.""" - errorPolicy: ErrorPolicyEnum = reject_everything + """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" + errorPolicy: ErrorPolicyEnum """Input list of products to create.""" products: [ProductBulkCreateInput!]! @@ -13831,7 +14196,7 @@ type Mutation { Note: this API is currently in Feature Preview and can be subject to changes at later point. """ - errorPolicy: ErrorPolicyEnum = reject_everything + errorPolicy: ErrorPolicyEnum """ID of the product to create the variants for.""" product: ID! @@ -13851,7 +14216,7 @@ type Mutation { """ productVariantBulkUpdate( """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" - errorPolicy: ErrorPolicyEnum = reject_everything + errorPolicy: ErrorPolicyEnum """ID of the product to update the variants for.""" product: ID! @@ -14849,6 +15214,28 @@ type Mutation { ids: [ID!]! ): OrderBulkCancel @doc(category: "Orders") + """ + Creates multiple orders. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + + Requires one of the following permissions: MANAGE_ORDERS_IMPORT. + """ + orderBulkCreate( + """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" + errorPolicy: ErrorPolicyEnum + + """Input list of orders to create. Orders limit: 50.""" + orders: [OrderBulkCreateInput!]! + + """ + Determine how stock should be updated, while processing the order. DEFAULT: UPDATE - Only do update, if there is enough stocks. + """ + stockUpdatePolicy: StockUpdatePolicyEnum + ): OrderBulkCreate @doc(category: "Orders") + """ Delete metadata of an object. To use it, you need to have access to the modified object. """ @@ -15595,6 +15982,18 @@ type Mutation { input: CheckoutCreateInput! ): CheckoutCreate @doc(category: "Checkout") + """ + Create new checkout from existing order. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + checkoutCreateFromOrder( + """ID of a order that will be used to create the checkout.""" + id: ID! + ): CheckoutCreateFromOrder @doc(category: "Checkout") + """ Sets the customer as the owner of the checkout. @@ -16117,6 +16516,23 @@ type Mutation { languageCode: LanguageCodeEnum! ): AttributeTranslate @doc(category: "Attributes") + """ + Creates/updates translations for attributes. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + + Requires one of the following permissions: MANAGE_TRANSLATIONS. + """ + attributeBulkTranslate( + """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" + errorPolicy: ErrorPolicyEnum + + """List of attributes translations.""" + translations: [AttributeBulkTranslateInput!]! + ): AttributeBulkTranslate @doc(category: "Attributes") + """ Deletes attributes. @@ -16187,6 +16603,23 @@ type Mutation { input: AttributeValueUpdateInput! ): AttributeValueUpdate @doc(category: "Attributes") + """ + Creates/updates translations for attributes values. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + + Requires one of the following permissions: MANAGE_TRANSLATIONS. + """ + attributeValueBulkTranslate( + """Policies of error handling. DEFAULT: REJECT_EVERYTHING""" + errorPolicy: ErrorPolicyEnum + + """List of attribute values translations.""" + translations: [AttributeValueBulkTranslateInput!]! + ): AttributeValueBulkTranslate @doc(category: "Attributes") + """ Creates/updates translations for an attribute value. @@ -16346,11 +16779,11 @@ type Mutation { ): CreateToken @doc(category: "Authentication") """ - Refresh JWT token. Mutation tries to take refreshToken from the input.If it fails it will try to take refreshToken from the http-only cookie -refreshToken. csrfToken is required when refreshToken is provided as a cookie. + Refresh JWT token. Mutation tries to take refreshToken from the input. If it fails it will try to take `refreshToken` from the http-only cookie `refreshToken`. `csrfToken` is required when `refreshToken` is provided as a cookie. """ tokenRefresh( """ - CSRF token required to refresh token. This argument is required when refreshToken is provided as a cookie. + CSRF token required to refresh token. This argument is required when `refreshToken` is provided as a cookie. """ csrfToken: String @@ -17812,6 +18245,13 @@ input ShopSettingsInput { """ limitQuantityPerCheckout: Int + """ + Enable automatic account confirmation by email. + + Added in Saleor 3.14. + """ + enableAccountConfirmationByEmail: Boolean + """ Include taxes in prices. @@ -18923,27 +19363,34 @@ input AttributeValueInput @doc(category: "Attributes") { """ID of the selected attribute.""" id: ID + """ + External ID of this attribute. + + Added in Saleor 3.14. + """ + externalReference: String + """ The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created. This field will be removed in Saleor 4.0. """ values: [String!] """ - Attribute value ID. + Attribute value ID or external reference. Added in Saleor 3.9. """ dropdown: AttributeValueSelectableTypeInput """ - Attribute value ID. + Attribute value ID or external reference. Added in Saleor 3.9. """ swatch: AttributeValueSelectableTypeInput """ - List of attribute value IDs. + List of attribute value IDs or external references. Added in Saleor 3.9. """ @@ -18982,7 +19429,11 @@ input AttributeValueInput @doc(category: "Attributes") { } """ -Represents attribute value. If no ID provided, value will be resolved. +Represents attribute value. +1. If ID is provided, then attribute value will be resolved by ID. +2. If externalReference is provided, then attribute value will be resolved by external reference. +3. If value is provided, then attribute value will be resolved by value. If this attribute value doesn't exist, then it will be created. +4. If externalReference and value is provided then new attribute value will be created. Added in Saleor 3.9. """ @@ -18990,6 +19441,13 @@ input AttributeValueSelectableTypeInput @doc(category: "Attributes") { """ID of an attribute value.""" id: ID + """ + External reference of an attribute value. + + Added in Saleor 3.14. + """ + externalReference: String + """ The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created. """ @@ -19249,6 +19707,13 @@ input BulkAttributeValueInput @doc(category: "Products") { """ID of the selected attribute.""" id: ID + """ + External ID of this attribute. + + Added in Saleor 3.14. + """ + externalReference: String + """ The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created.This field will be removed in Saleor 4.0. """ @@ -20029,6 +20494,13 @@ type ProductVariantBulkError @doc(category: "Products") { """The error code.""" code: ProductVariantBulkErrorCode! + """ + Path to field that caused the error. A value of `null` indicates that the error isn't associated with a particular field. + + Added in Saleor 3.14. + """ + path: String + """List of attributes IDs which causes the error.""" attributes: [ID!] @@ -20665,14 +21137,14 @@ input TransactionCreateInput @doc(category: "Payments") { """ Status of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions. """ status: String """ Payment type used for this transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `name` and `message` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `name` and `message` instead. """ type: String @@ -20693,7 +21165,7 @@ input TransactionCreateInput @doc(category: "Payments") { """ Reference of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `pspReference` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `pspReference` instead. """ reference: String @@ -20719,7 +21191,7 @@ input TransactionCreateInput @doc(category: "Payments") { """ Amount voided by this transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `amountCanceled` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `amountCanceled` instead. """ amountVoided: MoneyInput @@ -20748,14 +21220,14 @@ input TransactionEventInput @doc(category: "Payments") { """ Current status of the payment transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Status will be calculated by Saleor. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Status will be calculated by Saleor. """ status: TransactionStatus """ Reference of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `pspReference` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `pspReference` instead. """ reference: String @@ -20769,7 +21241,7 @@ input TransactionEventInput @doc(category: "Payments") { """ Name of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `message` instead. `name` field will be added to `message`. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `message` instead. `name` field will be added to `message`. """ name: String @@ -20822,14 +21294,14 @@ input TransactionUpdateInput @doc(category: "Payments") { """ Status of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). The `status` is not needed. The amounts can be used to define the current status of transactions. """ status: String """ Payment type used for this transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `name` and `message` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `name` and `message` instead. """ type: String @@ -20850,7 +21322,7 @@ input TransactionUpdateInput @doc(category: "Payments") { """ Reference of the transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `pspReference` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `pspReference` instead. """ reference: String @@ -20876,7 +21348,7 @@ input TransactionUpdateInput @doc(category: "Payments") { """ Amount voided by this transaction. - DEPRECATED: this field will be removed in Saleor 3.14 (Preview Feature). Use `amountCanceled` instead. + DEPRECATED: this field will be removed in Saleor 3.15 (Preview Feature). Use `amountCanceled` instead. """ amountVoided: MoneyInput @@ -21521,6 +21993,15 @@ input OrderLineCreateInput @doc(category: "Orders") { Added in Saleor 3.6. """ forceNewLine: Boolean = false + + """ + Custom price of the item.When the line with the same variant will be provided multiple times, the last price will be used. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + price: PositiveDecimal } """ @@ -21930,14 +22411,6 @@ input OrderGrantRefundCreateInput @doc(category: "Orders") { reason: String } -""" -Custom Decimal implementation. - -Returns Decimal as a float in the API, -parses float to the Decimal on the way back. -""" -scalar Decimal - """ Updates granted refund. @@ -22206,6 +22679,342 @@ type OrderBulkCancel @doc(category: "Orders") { errors: [OrderError!]! } +""" +Creates multiple orders. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. + +Requires one of the following permissions: MANAGE_ORDERS_IMPORT. +""" +type OrderBulkCreate @doc(category: "Orders") { + """Returns how many objects were created.""" + count: Int! + + """List of the created orders.""" + results: [OrderBulkCreateResult!]! + errors: [OrderBulkCreateError!]! +} + +type OrderBulkCreateResult @doc(category: "Orders") { + """Order data.""" + order: Order + + """List of errors occurred on create attempt.""" + errors: [OrderBulkCreateError!] +} + +type OrderBulkCreateError @doc(category: "Orders") { + """ + Path to field that caused the error. A value of `null` indicates that the error isn't associated with a particular field. + """ + path: String + + """The error message.""" + message: String + + """The error code.""" + code: OrderBulkCreateErrorCode +} + +"""An enumeration.""" +enum OrderBulkCreateErrorCode { + GRAPHQL_ERROR + REQUIRED + INVALID + NOT_FOUND + UNIQUE + BULK_LIMIT + TOO_MANY_IDENTIFIERS + FUTURE_DATE + INVALID_QUANTITY + PRICE_ERROR + NOTE_LENGTH + INSUFFICIENT_STOCK + NON_EXISTING_STOCK + NO_RELATED_ORDER_LINE + NEGATIVE_INDEX + ORDER_LINE_FULFILLMENT_LINE_MISMATCH + METADATA_KEY_REQUIRED + INCORRECT_CURRENCY +} + +input OrderBulkCreateInput @doc(category: "Orders") { + """External ID of the order.""" + externalReference: String + + """Slug of the channel associated with the order.""" + channel: String! + + """The date, when the order was inserted to Saleor database.""" + createdAt: DateTime! + + """Status of the order.""" + status: OrderStatus + + """Customer associated with the order.""" + user: OrderBulkCreateUserInput! + + """Tracking ID of the customer.""" + trackingClientId: String + + """Billing address of the customer.""" + billingAddress: AddressInput! + + """Shipping address of the customer.""" + shippingAddress: AddressInput + + """Currency code.""" + currency: String! + + """Metadata of the order.""" + metadata: [MetadataInput!] + + """Private metadata of the order.""" + privateMetadata: [MetadataInput!] + + """Note about customer.""" + customerNote: String + + """Notes related to the order.""" + notes: [OrderBulkCreateNoteInput!] + + """Order language code.""" + languageCode: LanguageCodeEnum! + + """ + Determines whether checkout prices should include taxes, when displayed in a storefront. + """ + displayGrossPrices: Boolean + + """Weight of the order in kg.""" + weight: WeightScalar + + """ + URL of a view, where users should be redirected to see the order details. + """ + redirectUrl: String + + """List of order lines.""" + lines: [OrderBulkCreateOrderLineInput!]! + + """The delivery method selected for this order.""" + deliveryMethod: OrderBulkCreateDeliveryMethodInput + + """List of gift card codes associated with the order.""" + giftCards: [String!] + + """Code of a voucher associated with the order.""" + voucher: String + + """List of discounts.""" + discounts: [OrderDiscountCommonInput!] + + """Fulfillments of the order.""" + fulfillments: [OrderBulkCreateFulfillmentInput!] + + """Transactions related to the order.""" + transactions: [TransactionCreateInput!] + + """Invoices related to the order.""" + invoices: [OrderBulkCreateInvoiceInput!] +} + +input OrderBulkCreateUserInput @doc(category: "Orders") { + """Customer ID associated with the order.""" + id: ID + + """Customer email associated with the order.""" + email: String + + """Customer external ID associated with the order.""" + externalReference: String +} + +input OrderBulkCreateNoteInput @doc(category: "Orders") { + """Note message. Max characters: 255.""" + message: String! + + """The date associated with the message.""" + date: DateTime + + """The user ID associated with the message.""" + userId: ID + + """The user email associated with the message.""" + userEmail: ID + + """The user external ID associated with the message.""" + userExternalReference: ID + + """The app ID associated with the message.""" + appId: ID +} + +input OrderBulkCreateOrderLineInput @doc(category: "Orders") { + """The ID of the product variant.""" + variantId: ID + + """The SKU of the product variant.""" + variantSku: String + + """The external ID of the product variant.""" + variantExternalReference: String + + """The name of the product variant.""" + variantName: String + + """The name of the product.""" + productName: String + + """Translation of the product variant name.""" + translatedVariantName: String + + """Translation of the product name.""" + translatedProductName: String + + """The date, when the order line was created.""" + createdAt: DateTime! + + """Determines whether shipping of the order line items is required.""" + isShippingRequired: Boolean! + + """Gift card flag.""" + isGiftCard: Boolean! + + """Number of items in the order line""" + quantity: Int! + + """Price of the order line.""" + totalPrice: TaxedMoneyInput! + + """Price of the order line excluding applied discount.""" + undiscountedTotalPrice: TaxedMoneyInput! + + """The ID of the warehouse, where the line will be allocated.""" + warehouse: ID! + + """Metadata of the order line.""" + metadata: [MetadataInput!] + + """Private metadata of the order line.""" + privateMetadata: [MetadataInput!] + + """Tax rate of the order line.""" + taxRate: PositiveDecimal + + """The ID of the tax class.""" + taxClassId: ID + + """The name of the tax class.""" + taxClassName: String + + """Metadata of the tax class.""" + taxClassMetadata: [MetadataInput!] + + """Private metadata of the tax class.""" + taxClassPrivateMetadata: [MetadataInput!] +} + +input TaxedMoneyInput @doc(category: "Orders") { + """Gross value of an item.""" + gross: PositiveDecimal! + + """Net value of an item.""" + net: PositiveDecimal! +} + +input OrderBulkCreateDeliveryMethodInput @doc(category: "Orders") { + """The ID of the warehouse.""" + warehouseId: ID + + """The name of the warehouse.""" + warehouseName: String + + """The ID of the shipping method.""" + shippingMethodId: ID + + """The name of the shipping method.""" + shippingMethodName: String + + """The price of the shipping.""" + shippingPrice: TaxedMoneyInput + + """Tax rate of the shipping.""" + shippingTaxRate: PositiveDecimal + + """The ID of the tax class.""" + shippingTaxClassId: ID + + """The name of the tax class.""" + shippingTaxClassName: String + + """Metadata of the tax class.""" + shippingTaxClassMetadata: [MetadataInput!] + + """Private metadata of the tax class.""" + shippingTaxClassPrivateMetadata: [MetadataInput!] +} + +input OrderBulkCreateFulfillmentInput @doc(category: "Orders") { + """Fulfillment's tracking code.""" + trackingCode: String + + """List of items informing how to fulfill the order.""" + lines: [OrderBulkCreateFulfillmentLineInput!] +} + +input OrderBulkCreateFulfillmentLineInput @doc(category: "Orders") { + """The ID of the product variant.""" + variantId: ID + + """The SKU of the product variant.""" + variantSku: String + + """The external ID of the product variant.""" + variantExternalReference: String + + """The number of line items to be fulfilled from given warehouse.""" + quantity: Int! + + """ID of the warehouse from which the item will be fulfilled.""" + warehouse: ID! + + """0-based index of order line, which the fulfillment line refers to.""" + orderLineIndex: Int! +} + +input OrderBulkCreateInvoiceInput @doc(category: "Orders") { + """The date, when the invoice was created.""" + createdAt: DateTime! + + """Invoice number.""" + number: String + + """URL of the invoice to download.""" + url: String + + """Metadata of the invoice.""" + metadata: [MetadataInput!] + + """Private metadata of the invoice.""" + privateMetadata: [MetadataInput!] +} + +""" +Determine how stocks should be updated, while processing an order. + + SKIP - stocks are not checked and not updated. + UPDATE - only do update, if there is enough stock. + FORCE - force update, if there is not enough stock. +""" +enum StockUpdatePolicyEnum { + SKIP + UPDATE + FORCE +} + """ Delete metadata of an object. To use it, you need to have access to the modified object. """ @@ -22561,6 +23370,20 @@ input InvoiceCreateInput @doc(category: "Orders") { """URL of an invoice to download.""" url: String! + + """ + Fields required to update the invoice metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the invoice private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] } """ @@ -22591,6 +23414,20 @@ input UpdateInvoiceInput @doc(category: "Orders") { """URL of an invoice to download.""" url: String + + """ + Fields required to update the invoice metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the invoice private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] } """ @@ -23714,6 +24551,68 @@ input CheckoutValidationRules @doc(category: "Checkout") { billingAddress: CheckoutAddressValidationRules } +""" +Create new checkout from existing order. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type CheckoutCreateFromOrder @doc(category: "Checkout") { + """Variants that were not attached to the checkout.""" + unavailableVariants: [CheckoutCreateFromOrderUnavailableVariant!] + + """Created checkout.""" + checkout: Checkout + errors: [CheckoutCreateFromOrderError!]! +} + +type CheckoutCreateFromOrderUnavailableVariant @doc(category: "Checkout") { + """The error message.""" + message: String! + + """The error code.""" + code: CheckoutCreateFromOrderUnavailableVariantErrorCode! + + """Variant ID that is unavailable.""" + variantId: ID! + + """Order line ID that is unavailable.""" + lineId: ID! +} + +"""An enumeration.""" +enum CheckoutCreateFromOrderUnavailableVariantErrorCode @doc(category: "Checkout") { + NOT_FOUND + PRODUCT_UNAVAILABLE_FOR_PURCHASE + UNAVAILABLE_VARIANT_IN_CHANNEL + PRODUCT_NOT_PUBLISHED + QUANTITY_GREATER_THAN_LIMIT + INSUFFICIENT_STOCK +} + +type CheckoutCreateFromOrderError @doc(category: "Checkout") { + """ + Name of a field that caused the error. A value of `null` indicates that the error isn't associated with a particular field. + """ + field: String + + """The error message.""" + message: String + + """The error code.""" + code: CheckoutCreateFromOrderErrorCode! +} + +"""An enumeration.""" +enum CheckoutCreateFromOrderErrorCode @doc(category: "Checkout") { + GRAPHQL_ERROR + INVALID + ORDER_NOT_FOUND + CHANNEL_INACTIVE + TAX_ERROR +} + """ Sets the customer as the owner of the checkout. @@ -24074,6 +24973,15 @@ input OrderSettingsInput @doc(category: "Orders") { """ expireOrdersAfter: Minute + """ + The time in days after expired orders will be deleted.Allowed range is from 1 to 120. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + deleteExpiredOrdersAfter: Day + """ Determine what strategy will be used to mark the order as paid. Based on the chosen option, the proper object will be created and attached to the order when it's manually marked as paid. `PAYMENT_FLOW` - [default option] creates the `Payment` object. @@ -24473,6 +25381,67 @@ type AttributeTranslate @doc(category: "Attributes") { attribute: Attribute } +""" +Creates/updates translations for attributes. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. + +Requires one of the following permissions: MANAGE_TRANSLATIONS. +""" +type AttributeBulkTranslate @doc(category: "Attributes") { + """Returns how many translations were created/updated.""" + count: Int! + + """List of the translations.""" + results: [AttributeBulkTranslateResult!]! + errors: [AttributeBulkTranslateError!]! +} + +type AttributeBulkTranslateResult @doc(category: "Attributes") { + """Attribute translation data.""" + translation: AttributeTranslation + + """List of errors occurred on translation attempt.""" + errors: [AttributeBulkTranslateError!] +} + +type AttributeBulkTranslateError { + """ + Path to field that caused the error. A value of `null` indicates that the error isn't associated with a particular field. + """ + path: String + + """The error message.""" + message: String + + """The error code.""" + code: AttributeTranslateErrorCode! +} + +"""An enumeration.""" +enum AttributeTranslateErrorCode @doc(category: "Attributes") { + GRAPHQL_ERROR + INVALID + NOT_FOUND + REQUIRED +} + +input AttributeBulkTranslateInput @doc(category: "Attributes") { + """Attribute ID.""" + id: ID + + """External reference of an attribute.""" + externalReference: String + + """Translation language code.""" + languageCode: LanguageCodeEnum! + + """Translation fields.""" + translationFields: NameTranslationInput! +} + """ Deletes attributes. @@ -24537,14 +25506,64 @@ type AttributeValueUpdate @doc(category: "Attributes") { } """ -Creates/updates translations for an attribute value. +Creates/updates translations for attributes values. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. Requires one of the following permissions: MANAGE_TRANSLATIONS. """ -type AttributeValueTranslate @doc(category: "Attributes") { - translationErrors: [TranslationError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.") - errors: [TranslationError!]! - attributeValue: AttributeValue +type AttributeValueBulkTranslate @doc(category: "Attributes") { + """Returns how many translations were created/updated.""" + count: Int! + + """List of the translations.""" + results: [AttributeValueBulkTranslateResult!]! + errors: [AttributeValueBulkTranslateError!]! +} + +type AttributeValueBulkTranslateResult @doc(category: "Attributes") { + """Attribute value translation data.""" + translation: AttributeValueTranslation + + """List of errors occurred on translation attempt.""" + errors: [AttributeValueBulkTranslateError!] +} + +type AttributeValueBulkTranslateError { + """ + Path to field that caused the error. A value of `null` indicates that the error isn't associated with a particular field. + """ + path: String + + """The error message.""" + message: String + + """The error code.""" + code: AttributeValueTranslateErrorCode! +} + +"""An enumeration.""" +enum AttributeValueTranslateErrorCode @doc(category: "Attributes") { + GRAPHQL_ERROR + INVALID + NOT_FOUND + REQUIRED +} + +input AttributeValueBulkTranslateInput @doc(category: "Attributes") { + """Attribute value ID.""" + id: ID + + """External reference of an attribute value.""" + externalReference: String + + """Translation language code.""" + languageCode: LanguageCodeEnum! + + """Translation fields.""" + translationFields: AttributeValueTranslationInput! } input AttributeValueTranslationInput { @@ -24561,6 +25580,17 @@ input AttributeValueTranslationInput { plainText: String } +""" +Creates/updates translations for an attribute value. + +Requires one of the following permissions: MANAGE_TRANSLATIONS. +""" +type AttributeValueTranslate @doc(category: "Attributes") { + translationErrors: [TranslationError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.") + errors: [TranslationError!]! + attributeValue: AttributeValue +} + """ Reorder the values of an attribute. @@ -24796,6 +25826,15 @@ type Manifest @doc(category: "Apps") { Note: this API is currently in Feature Preview and can be subject to changes at later point. """ author: String + + """ + App's brand data. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + brand: AppManifestBrand } type AppManifestExtension @doc(category: "Apps") { @@ -24852,6 +25891,56 @@ type AppManifestRequiredSaleorVersion @doc(category: "Apps") { satisfied: Boolean! } +""" +Represents the app's manifest brand data. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type AppManifestBrand @doc(category: "Apps") { + """ + App's logos details. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + logo: AppManifestBrandLogo! +} + +""" +Represents the app's manifest brand data. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type AppManifestBrandLogo @doc(category: "Apps") { + """ + Data URL with a base64 encoded logo image. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + default( + """ + Desired longest side the image in pixels. Defaults to 4096. Images are never cropped. Pass 0 to retrieve the original size (not recommended). + """ + size: Int + + """ + The format of the image. When not provided, format of the original image will be used. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + format: IconThumbnailFormatEnum = ORIGINAL + ): String! +} + """ Activate the app. @@ -24947,7 +26036,7 @@ enum AccountErrorCode @doc(category: "Users") { } """ -Refresh JWT token. Mutation tries to take refreshToken from the input.If it fails it will try to take refreshToken from the http-only cookie -refreshToken. csrfToken is required when refreshToken is provided as a cookie. +Refresh JWT token. Mutation tries to take refreshToken from the input. If it fails it will try to take `refreshToken` from the http-only cookie `refreshToken`. `csrfToken` is required when `refreshToken` is provided as a cookie. """ type RefreshToken @doc(category: "Authentication") { """JWT token, required to authenticate.""" @@ -25235,6 +26324,13 @@ input AccountInput @doc(category: "Users") { """Shipping address of the customer.""" defaultShippingAddress: AddressInput + + """ + Fields required to update the user metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] } """ @@ -25342,6 +26438,20 @@ input UserCreateInput @doc(category: "Users") { """A note about the user.""" note: String + """ + Fields required to update the user metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the user private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] + """User language code.""" languageCode: LanguageCodeEnum @@ -25396,6 +26506,20 @@ input CustomerInput @doc(category: "Users") { """A note about the user.""" note: String + """ + Fields required to update the user metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the user private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] + """User language code.""" languageCode: LanguageCodeEnum @@ -25545,6 +26669,20 @@ input StaffCreateInput @doc(category: "Users") { """A note about the user.""" note: String + """ + Fields required to update the user metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the user private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] + """List of permission group IDs to which user should be assigned.""" addGroups: [ID!] @@ -25582,6 +26720,20 @@ input StaffUpdateInput @doc(category: "Users") { """A note about the user.""" note: String + """ + Fields required to update the user metadata. + + Added in Saleor 3.14. + """ + metadata: [MetadataInput!] + + """ + Fields required to update the user private metadata. + + Added in Saleor 3.14. + """ + privateMetadata: [MetadataInput!] + """List of permission group IDs to which user should be assigned.""" addGroups: [ID!] @@ -25676,18 +26828,22 @@ type PermissionGroupError @doc(category: "Users") { """List of user IDs which causes the error.""" users: [ID!] + + """List of chnnels IDs which causes the error.""" + channels: [ID!] } """An enumeration.""" enum PermissionGroupErrorCode @doc(category: "Users") { + REQUIRED + UNIQUE ASSIGN_NON_STAFF_MEMBER DUPLICATED_INPUT_ITEM CANNOT_REMOVE_FROM_LAST_GROUP LEFT_NOT_MANAGEABLE_PERMISSION OUT_OF_SCOPE_PERMISSION OUT_OF_SCOPE_USER - REQUIRED - UNIQUE + OUT_OF_SCOPE_CHANNEL } input PermissionGroupCreateInput @doc(category: "Users") { @@ -25697,8 +26853,26 @@ input PermissionGroupCreateInput @doc(category: "Users") { """List of users to assign to this group.""" addUsers: [ID!] + """ + List of channels to assign to this group. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + addChannels: [ID!] + """Group name.""" name: String! + + """ + Determine if the group has restricted access to channels. DEFAULT: False + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + restrictedAccessToChannels: Boolean = false } """ @@ -25719,6 +26893,15 @@ input PermissionGroupUpdateInput @doc(category: "Users") { """List of users to assign to this group.""" addUsers: [ID!] + """ + List of channels to assign to this group. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + addChannels: [ID!] + """Group name.""" name: String @@ -25727,6 +26910,24 @@ input PermissionGroupUpdateInput @doc(category: "Users") { """List of users to unassign from this group.""" removeUsers: [ID!] + + """ + List of channels to unassign from this group. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + removeChannels: [ID!] + + """ + Determine if the group has restricted access to channels. + + Added in Saleor 3.14. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + restrictedAccessToChannels: Boolean } """ @@ -26620,6 +27821,78 @@ type OrderFullyPaid implements Event @doc(category: "Orders") { order: Order } +""" +Payment has been made. The order may be partially or fully paid. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type OrderPaid implements Event @doc(category: "Orders") { + """Time of the event.""" + issuedAt: DateTime + + """Saleor version that triggered the event.""" + version: String + + """The user or application that triggered the event.""" + issuingPrincipal: IssuingPrincipal + + """The application receiving the webhook.""" + recipient: App + + """The order the event relates to.""" + order: Order +} + +""" +The order received a refund. The order may be partially or fully refunded. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type OrderRefunded implements Event @doc(category: "Orders") { + """Time of the event.""" + issuedAt: DateTime + + """Saleor version that triggered the event.""" + version: String + + """The user or application that triggered the event.""" + issuingPrincipal: IssuingPrincipal + + """The application receiving the webhook.""" + recipient: App + + """The order the event relates to.""" + order: Order +} + +""" +The order is fully refunded. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type OrderFullyRefunded implements Event @doc(category: "Orders") { + """Time of the event.""" + issuedAt: DateTime + + """Saleor version that triggered the event.""" + version: String + + """The user or application that triggered the event.""" + issuingPrincipal: IssuingPrincipal + + """The application receiving the webhook.""" + recipient: App + + """The order the event relates to.""" + order: Order +} + """ Event sent when order is fulfilled. @@ -26710,6 +27983,30 @@ type OrderMetadataUpdated implements Event @doc(category: "Orders") { order: Order } +""" +Event sent when orders are imported. + +Added in Saleor 3.14. + +Note: this API is currently in Feature Preview and can be subject to changes at later point. +""" +type OrderBulkCreated implements Event { + """Time of the event.""" + issuedAt: DateTime + + """Saleor version that triggered the event.""" + version: String + + """The user or application that triggered the event.""" + issuingPrincipal: IssuingPrincipal + + """The application receiving the webhook.""" + recipient: App + + """The orders the event relates to.""" + orders: [Order!] +} + """ Event sent when new draft order is created. diff --git a/apps/emails-and-messages/package.json b/apps/emails-and-messages/package.json index 575760b..d01baaa 100644 --- a/apps/emails-and-messages/package.json +++ b/apps/emails-and-messages/package.json @@ -73,6 +73,6 @@ }, "private": true, "saleor": { - "schemaVersion": "3.13" + "schemaVersion": "3.14" } } diff --git a/apps/emails-and-messages/src/lib/get-event-form-status.test.ts b/apps/emails-and-messages/src/lib/get-event-form-status.test.ts index 7b0c7f9..d27d644 100644 --- a/apps/emails-and-messages/src/lib/get-event-form-status.test.ts +++ b/apps/emails-and-messages/src/lib/get-event-form-status.test.ts @@ -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 { PermissionEnum } from "../../generated/graphql"; @@ -10,6 +10,7 @@ describe("getEventFormStatus", function () { appPermissions: [PermissionEnum.ManageGiftCard], featureFlags: { giftCardSentEvent: true, + orderRefundedEvent: true, }, }) ).toEqual({ @@ -22,6 +23,7 @@ describe("getEventFormStatus", function () { appPermissions: [], featureFlags: { giftCardSentEvent: false, + orderRefundedEvent: true, }, }) ).toEqual({ @@ -38,6 +40,7 @@ describe("getEventFormStatus", function () { appPermissions: [], featureFlags: { giftCardSentEvent: true, + orderRefundedEvent: true, }, }) ).toEqual({ @@ -54,6 +57,7 @@ describe("getEventFormStatus", function () { appPermissions: [PermissionEnum.ManageGiftCard], featureFlags: { giftCardSentEvent: false, + orderRefundedEvent: true, }, }) ).toEqual({ diff --git a/apps/emails-and-messages/src/lib/get-event-form-status.ts b/apps/emails-and-messages/src/lib/get-event-form-status.ts index 693906f..236e57a 100644 --- a/apps/emails-and-messages/src/lib/get-event-form-status.ts +++ b/apps/emails-and-messages/src/lib/get-event-form-status.ts @@ -17,24 +17,38 @@ export const getEventFormStatus = ({ isDisabled: boolean; requiredSaleorVersion: string | undefined; } => { - // Since GIFT_CARD_SENT is the only event with such validation, we can exit early - if (eventType !== "GIFT_CARD_SENT") { - return { - isDisabled: false, - missingPermission: undefined, - requiredSaleorVersion: undefined, - }; + switch (eventType) { + case "ORDER_REFUNDED": { + const isUnsupported = !featureFlags?.orderRefundedEvent; + + const hasPermission = (appPermissions || []).includes(PermissionEnum.ManageOrders); + + const isDisabled = isUnsupported || !hasPermission; + + return { + isDisabled, + missingPermission: hasPermission ? undefined : PermissionEnum.ManageOrders, + requiredSaleorVersion: isUnsupported ? ">=3.14" : undefined, + }; + } + case "GIFT_CARD_SENT": { + const isUnsupported = !featureFlags?.giftCardSentEvent; + + const hasPermission = (appPermissions || []).includes(PermissionEnum.ManageGiftCard); + + const isDisabled = isUnsupported || !hasPermission; + + return { + isDisabled, + missingPermission: hasPermission ? undefined : PermissionEnum.ManageGiftCard, + requiredSaleorVersion: isUnsupported ? ">=3.13" : undefined, + }; + } + default: + return { + 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, - }; }; diff --git a/apps/emails-and-messages/src/modules/event-handlers/default-payloads.ts b/apps/emails-and-messages/src/modules/event-handlers/default-payloads.ts index 4e0f207..596d287 100644 --- a/apps/emails-and-messages/src/modules/event-handlers/default-payloads.ts +++ b/apps/emails-and-messages/src/modules/event-handlers/default-payloads.ts @@ -8,6 +8,7 @@ import { OrderFullyPaidWebhookPayloadFragment, InvoiceSentWebhookPayloadFragment, GiftCardSentWebhookPayloadFragment, + OrderRefundedWebhookPayloadFragment, } from "../../../generated/graphql"; import { NotifyEventPayload } from "../../pages/api/webhooks/notify"; @@ -137,6 +138,10 @@ const orderFullyPaidPayload: OrderFullyPaidWebhookPayloadFragment = { order: exampleOrderPayload, }; +const orderRefundedPayload: OrderRefundedWebhookPayloadFragment = { + order: exampleOrderPayload, +}; + const invoiceSentPayload: InvoiceSentWebhookPayloadFragment = { invoice: { id: "SW52b2ljZToxMDE=", @@ -312,16 +317,17 @@ const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = { }; export const examplePayloads: Record = { - ORDER_CREATED: orderCreatedPayload, - ORDER_CONFIRMED: orderConfirmedPayload, + ACCOUNT_CHANGE_EMAIL_CONFIRM: accountChangeEmailConfirmPayload, + 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_CONFIRMED: orderConfirmedPayload, + ORDER_CREATED: orderCreatedPayload, ORDER_FULFILLED: orderFulfilledPayload, ORDER_FULLY_PAID: orderFullyPaidPayload, - INVOICE_SENT: invoiceSentPayload, - GIFT_CARD_SENT: giftCardSentPayload, - ACCOUNT_CONFIRMATION: accountConfirmationPayload, - ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload, - ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload, - ACCOUNT_CHANGE_EMAIL_CONFIRM: accountChangeEmailConfirmPayload, - ACCOUNT_DELETE: accountDeletePayload, + ORDER_REFUNDED: orderRefundedPayload, }; diff --git a/apps/emails-and-messages/src/modules/event-handlers/message-event-types.ts b/apps/emails-and-messages/src/modules/event-handlers/message-event-types.ts index 8a90bf1..00ab398 100644 --- a/apps/emails-and-messages/src/modules/event-handlers/message-event-types.ts +++ b/apps/emails-and-messages/src/modules/event-handlers/message-event-types.ts @@ -1,31 +1,33 @@ 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_FULFILLED", - "ORDER_CONFIRMED", - "ORDER_CANCELLED", "ORDER_FULLY_PAID", - "INVOICE_SENT", - "ACCOUNT_CONFIRMATION", - "ACCOUNT_PASSWORD_RESET", - "ACCOUNT_CHANGE_EMAIL_REQUEST", - "ACCOUNT_CHANGE_EMAIL_CONFIRM", - "ACCOUNT_DELETE", - "GIFT_CARD_SENT", + "ORDER_REFUNDED", ] as const; export type MessageEventTypes = (typeof messageEventTypes)[number]; export const messageEventTypesLabels: Record = { + 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_FULFILLED: "Order fulfilled", - ORDER_CONFIRMED: "Order confirmed", - ORDER_CANCELLED: "Order cancelled", ORDER_FULLY_PAID: "Order fully paid", - INVOICE_SENT: "Invoice sent", - 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", + ORDER_REFUNDED: "Order refunded", }; diff --git a/apps/emails-and-messages/src/modules/feature-flag-service/get-feature-flags.ts b/apps/emails-and-messages/src/modules/feature-flag-service/get-feature-flags.ts index f19074a..602750d 100644 --- a/apps/emails-and-messages/src/modules/feature-flag-service/get-feature-flags.ts +++ b/apps/emails-and-messages/src/modules/feature-flag-service/get-feature-flags.ts @@ -1,6 +1,6 @@ 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]; @@ -17,5 +17,6 @@ interface GetFeatureFlagsArgs { export const getFeatureFlags = ({ saleorVersion }: GetFeatureFlagsArgs): FeatureFlagsState => { return { giftCardSentEvent: new SaleorVersionCompatibilityValidator(">=3.13").isValid(saleorVersion), + orderRefundedEvent: new SaleorVersionCompatibilityValidator(">=3.14").isValid(saleorVersion), }; }; diff --git a/apps/emails-and-messages/src/modules/smtp/default-templates.ts b/apps/emails-and-messages/src/modules/smtp/default-templates.ts index 9c3a369..1d2cb69 100644 --- a/apps/emails-and-messages/src/modules/smtp/default-templates.ts +++ b/apps/emails-and-messages/src/modules/smtp/default-templates.ts @@ -138,6 +138,23 @@ const defaultOrderFullyPaidMjmlTemplate = ` `; +const defaultOrderRefundedMjmlTemplate = ` + + + + + Hello! + + + Order {{ order.number}} has been refunded. + + + + ${addressSection} + ${orderLinesSection} + +`; + const defaultOrderCancelledMjmlTemplate = ` @@ -286,6 +303,7 @@ export const defaultMjmlTemplates: Record = { ORDER_CREATED: defaultOrderCreatedMjmlTemplate, ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate, ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate, + ORDER_REFUNDED: defaultOrderRefundedMjmlTemplate, }; export const defaultMjmlSubjectTemplates: Record = { @@ -301,4 +319,5 @@ export const defaultMjmlSubjectTemplates: Record = { ORDER_CREATED: "Order {{ order.number }} has been created", ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled", ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid", + ORDER_REFUNDED: "Order {{ order.number }} has been refunded", }; diff --git a/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.test.ts b/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.test.ts index 0e0735b..e1cd89a 100644 --- a/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.test.ts +++ b/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.test.ts @@ -2,6 +2,7 @@ import { expect, describe, it } from "vitest"; import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema"; import { getWebhookStatusesFromConfigurations } from "./get-webhook-statuses-from-configurations"; import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema"; +import { webhookStatusesFactory } from "./webhook-status-dict"; export const nonActiveSmtpConfiguration: SmtpConfiguration = { id: "1685343953413npk9p", @@ -169,16 +170,7 @@ describe("getWebhookStatusesFromConfigurations", function () { smtpConfigurations: [], sendgridConfigurations: [], }) - ).toStrictEqual({ - invoiceSentWebhook: false, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderFulfilledWebhook: false, - orderCreatedWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + ).toStrictEqual(webhookStatusesFactory({})); }); it("Statuses should be set to false, when no active configurations passed", async () => { @@ -187,16 +179,7 @@ describe("getWebhookStatusesFromConfigurations", function () { smtpConfigurations: [nonActiveSmtpConfiguration], sendgridConfigurations: [nonActiveSendgridConfiguration], }) - ).toStrictEqual({ - invoiceSentWebhook: false, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderFulfilledWebhook: false, - orderCreatedWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + ).toStrictEqual(webhookStatusesFactory({})); }); 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], sendgridConfigurations: [nonActiveSendgridConfiguration], }) - ).toStrictEqual({ - invoiceSentWebhook: false, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderFulfilledWebhook: false, - orderCreatedWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + ).toStrictEqual(webhookStatusesFactory({})); }); 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], sendgridConfigurations: [nonActiveSendgridConfiguration], }) - ).toStrictEqual({ - invoiceSentWebhook: true, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderFulfilledWebhook: false, - orderCreatedWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + ).toStrictEqual(webhookStatusesFactory({ enabledWebhooks: ["invoiceSentWebhook"] })); }); 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], sendgridConfigurations: [nonActiveSendgridConfiguration], }) - ).toStrictEqual({ - invoiceSentWebhook: false, - notifyWebhook: true, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderFulfilledWebhook: false, - orderCreatedWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + ).toStrictEqual(webhookStatusesFactory({ enabledWebhooks: ["notifyWebhook"] })); }); }); diff --git a/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.ts b/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.ts index 4e53e38..ae095ef 100644 --- a/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.ts +++ b/apps/emails-and-messages/src/modules/webhook-management/get-webhook-statuses-from-configurations.ts @@ -1,6 +1,7 @@ import { SendgridConfiguration } from "../sendgrid/configuration/sendgrid-config-schema"; import { SmtpConfiguration } from "../smtp/configuration/smtp-config-schema"; import { AppWebhook, eventToWebhookMapping } from "./webhook-management-service"; +import { webhookStatusesFactory } from "./webhook-status-dict"; /* * Returns dictionary of webhook statuses based on passed configurations. @@ -14,16 +15,7 @@ export const getWebhookStatusesFromConfigurations = ({ sendgridConfigurations: SendgridConfiguration[]; }) => { // TODO: this dict should be generated in one place instead of manually edited - const statuses: Record = { - giftCardSentWebhook: false, - invoiceSentWebhook: false, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderCreatedWebhook: false, - orderFulfilledWebhook: false, - orderFullyPaidWebhook: false, - }; + const statuses: Record = webhookStatusesFactory({}); smtpConfigurations.forEach(async (config) => { if (!config.active) { diff --git a/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.test.ts b/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.test.ts index 085e9b9..9c2f643 100644 --- a/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.test.ts +++ b/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.test.ts @@ -6,7 +6,7 @@ import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql"; import { invoiceSentWebhook } from "../../pages/api/webhooks/invoice-sent"; import { orderCancelledWebhook } from "../../pages/api/webhooks/order-cancelled"; 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 () { const mockedClient = {} as Client; @@ -76,16 +76,9 @@ describe("WebhookManagementService", function () { const statuses = await webhookManagementService.getWebhooksStatus(); - expect(statuses).toStrictEqual({ - invoiceSentWebhook: true, - notifyWebhook: false, - orderCancelledWebhook: false, - orderConfirmedWebhook: false, - orderCreatedWebhook: false, - orderFulfilledWebhook: false, - orderFullyPaidWebhook: false, - giftCardSentWebhook: false, - }); + expect(statuses).toStrictEqual( + webhookStatusesFactory({ enabledWebhooks: ["invoiceSentWebhook"] }) + ); expect(fetchAppWebhooksMock).toBeCalledTimes(1); }); diff --git a/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.ts b/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.ts index 4ba58e3..fb7d5ca 100644 --- a/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.ts +++ b/apps/emails-and-messages/src/modules/webhook-management/webhook-management-service.ts @@ -12,7 +12,7 @@ import { createLogger } from "@saleor/apps-shared"; import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql"; import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent"; 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 = { giftCardSentWebhook, @@ -23,6 +23,7 @@ export const AppWebhooks = { orderCreatedWebhook, orderFulfilledWebhook, orderFullyPaidWebhook, + orderRefundedWebhook, }; export type AppWebhook = keyof typeof AppWebhooks; @@ -40,6 +41,7 @@ export const eventToWebhookMapping: Record = { ORDER_CREATED: "orderCreatedWebhook", ORDER_FULFILLED: "orderFulfilledWebhook", ORDER_FULLY_PAID: "orderFullyPaidWebhook", + ORDER_REFUNDED: "orderRefundedWebhook", }; const logger = createLogger({ diff --git a/apps/emails-and-messages/src/modules/webhook-management/webhook-status-dict.ts b/apps/emails-and-messages/src/modules/webhook-management/webhook-status-dict.ts new file mode 100644 index 0000000..93f6a75 --- /dev/null +++ b/apps/emails-and-messages/src/modules/webhook-management/webhook-status-dict.ts @@ -0,0 +1,20 @@ +import { AppWebhook, AppWebhooks } from "./webhook-management-service"; + +export type WebhookStatuses = Record; + +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"), +}); diff --git a/apps/emails-and-messages/src/pages/api/webhooks/order-refunded.ts b/apps/emails-and-messages/src/pages/api/webhooks/order-refunded.ts new file mode 100644 index 0000000..0750a0a --- /dev/null +++ b/apps/emails-and-messages/src/pages/api/webhooks/order-refunded.ts @@ -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({ + 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 = 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, + }, +};