diff --git a/.changeset/soft-steaks-know.md b/.changeset/soft-steaks-know.md index 538ade0..203d5c9 100644 --- a/.changeset/soft-steaks-know.md +++ b/.changeset/soft-steaks-know.md @@ -2,5 +2,4 @@ "saleor-app-taxes": minor --- -Changed the order_created to order_confirmed webhook event. Now, the provider transactions will be created based on the order confirmation (either automatic or manual). - +Changed the order_created to order_confirmed webhook event. Now, the provider transactions will be created based on the order confirmation (either automatic or manual). Also, removed the order_fulfilled webhook event handler. The value of the "commit" field is now set only based on the "isAutocommit" setting in the provider configuration. diff --git a/apps/taxes/src/modules/avatax/avatax-webhook.service.ts b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts index e8cd249..4975c92 100644 --- a/apps/taxes/src/modules/avatax/avatax-webhook.service.ts +++ b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts @@ -12,7 +12,6 @@ import { AvataxConfig, defaultAvataxConfig } from "./avatax-connection-schema"; import { AvataxCalculateTaxesAdapter } from "./calculate-taxes/avatax-calculate-taxes-adapter"; import { AvataxOrderCancelledAdapter } from "./order-cancelled/avatax-order-cancelled-adapter"; import { AvataxOrderConfirmedAdapter } from "./order-confirmed/avatax-order-confirmed-adapter"; -import { AvataxOrderFulfilledAdapter } from "./order-fulfilled/avatax-order-fulfilled-adapter"; export class AvataxWebhookService implements ProviderWebhookService { config = defaultAvataxConfig; @@ -45,14 +44,6 @@ export class AvataxWebhookService implements ProviderWebhookService { return response; } - async fulfillOrder(order: OrderFulfilledSubscriptionFragment) { - const adapter = new AvataxOrderFulfilledAdapter(this.config); - - const response = await adapter.send({ order }); - - return response; - } - async cancelOrder(payload: OrderCancelledPayload) { const adapter = new AvataxOrderCancelledAdapter(this.config); diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-adapter.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-adapter.ts deleted file mode 100644 index 7ce181e..0000000 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-adapter.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { OrderFulfilledSubscriptionFragment } from "../../../../generated/graphql"; -import { Logger, createLogger } from "../../../lib/logger"; -import { WebhookAdapter } from "../../taxes/tax-webhook-adapter"; -import { AvataxClient, CommitTransactionArgs } from "../avatax-client"; -import { AvataxConfig } from "../avatax-connection-schema"; -import { AvataxOrderFulfilledPayloadTransformer } from "./avatax-order-fulfilled-payload-transformer"; -import { AvataxOrderFulfilledResponseTransformer } from "./avatax-order-fulfilled-response-transformer"; - -export type AvataxOrderFulfilledPayload = { - order: OrderFulfilledSubscriptionFragment; -}; -export type AvataxOrderFulfilledTarget = CommitTransactionArgs; -export type AvataxOrderFulfilledResponse = { ok: true }; - -export class AvataxOrderFulfilledAdapter - implements WebhookAdapter -{ - private logger: Logger; - - constructor(private readonly config: AvataxConfig) { - this.logger = createLogger({ name: "AvataxOrderFulfilledAdapter" }); - } - - async send(payload: AvataxOrderFulfilledPayload): Promise { - this.logger.debug("Transforming the Saleor payload for commiting transaction with Avatax..."); - - const payloadTransformer = new AvataxOrderFulfilledPayloadTransformer(this.config); - const target = payloadTransformer.transform({ ...payload }); - - this.logger.debug("Calling Avatax commitTransaction with transformed payload..."); - - const client = new AvataxClient(this.config); - const response = await client.commitTransaction(target); - - this.logger.debug("Avatax commitTransaction succesfully responded"); - - const responseTransformer = new AvataxOrderFulfilledResponseTransformer(); - const transformedResponse = responseTransformer.transform(response); - - this.logger.debug("Transformed Avatax commitTransaction response"); - - return transformedResponse; - } -} diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts deleted file mode 100644 index 2c39f22..0000000 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.test.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { DocumentType } from "avatax/lib/enums/DocumentType"; -import { describe, expect, it } from "vitest"; -import { OrderFulfilledSubscriptionFragment } from "../../../../generated/graphql"; -import { AvataxConfig } from "../avatax-connection-schema"; -import { AvataxOrderFulfilledPayloadTransformer } from "./avatax-order-fulfilled-payload-transformer"; - -// todo: add AvataxOrderFulfilledMockGenerator - -const MOCK_AVATAX_CONFIG: AvataxConfig = { - companyCode: "DEFAULT", - isDocumentRecordingEnabled: true, - isAutocommit: false, - isSandbox: true, - name: "Avatax-1", - shippingTaxCode: "FR000000", - address: { - country: "US", - zip: "10118", - state: "NY", - city: "New York", - street: "350 5th Avenue", - }, - credentials: { - password: "password", - username: "username", - }, -}; - -type OrderFulfilled = OrderFulfilledSubscriptionFragment; - -const ORDER_FULFILLED_MOCK: OrderFulfilled = { - id: "T3JkZXI6OTU4MDA5YjQtNDUxZC00NmQ1LThhMWUtMTRkMWRmYjFhNzI5", - created: "2023-04-11T11:03:09.304109+00:00", - avataxId: "transaction-code", - channel: { - id: "Q2hhbm5lbDoy", - slug: "channel-pln", - }, - shippingAddress: { - streetAddress1: "123 Palm Grove Ln", - streetAddress2: "", - city: "LOS ANGELES", - countryArea: "CA", - postalCode: "90002", - country: { - code: "US", - }, - }, - billingAddress: { - streetAddress1: "123 Palm Grove Ln", - streetAddress2: "", - city: "LOS ANGELES", - countryArea: "CA", - postalCode: "90002", - country: { - code: "US", - }, - }, - total: { - net: { - amount: 183.33, - }, - tax: { - amount: 12.83, - }, - }, - shippingPrice: { - net: { - amount: 48.33, - }, - }, - lines: [ - { - productSku: "328223581", - productName: "Monospace Tee", - quantity: 1, - unitPrice: { - net: { - amount: 90, - }, - }, - totalPrice: { - net: { - amount: 90, - }, - tax: { - amount: 8.55, - }, - }, - }, - { - productSku: "328223580", - productName: "Polyspace Tee", - quantity: 1, - unitPrice: { - net: { - amount: 45, - }, - }, - totalPrice: { - net: { - amount: 45, - }, - tax: { - amount: 4.28, - }, - }, - }, - ], -}; - -const MOCKED_ORDER_FULFILLED_PAYLOAD: { - order: OrderFulfilledSubscriptionFragment; -} = { - order: ORDER_FULFILLED_MOCK, -}; - -describe("AvataxOrderFulfilledPayloadTransformer", () => { - it("throws error when no avataxId", () => { - const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); - - expect(() => - transformer.transform({ - ...MOCKED_ORDER_FULFILLED_PAYLOAD, - order: { - ...MOCKED_ORDER_FULFILLED_PAYLOAD.order, - avataxId: null, - }, - }) - ).toThrow(); - }); - it("returns document type of SalesOrder when isDocumentRecordingEnabled is false", () => { - const transformer = new AvataxOrderFulfilledPayloadTransformer({ - ...MOCK_AVATAX_CONFIG, - isDocumentRecordingEnabled: false, - }); - - const payload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); - - expect(payload.documentType).toBe(DocumentType.SalesOrder); - }), - it("returns document type of SalesInvoice when isDocumentRecordingEnabled is true", () => { - const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); - - const payload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); - - expect(payload.documentType).toBe(DocumentType.SalesInvoice); - }), - it("returns transformed payload", () => { - const transformer = new AvataxOrderFulfilledPayloadTransformer(MOCK_AVATAX_CONFIG); - - const mappedPayload = transformer.transform(MOCKED_ORDER_FULFILLED_PAYLOAD); - - expect(mappedPayload).toEqual({ - transactionCode: "transaction-code", - companyCode: "DEFAULT", - documentType: DocumentType.SalesInvoice, - model: { - commit: true, - }, - }); - }); -}); diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts deleted file mode 100644 index ca9b3af..0000000 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-payload-transformer.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { DocumentType } from "avatax/lib/enums/DocumentType"; -import { z } from "zod"; -import { AvataxConfig } from "../avatax-connection-schema"; -import { - AvataxOrderFulfilledPayload, - AvataxOrderFulfilledTarget, -} from "./avatax-order-fulfilled-adapter"; - -export class AvataxOrderFulfilledPayloadTransformer { - constructor(private readonly config: AvataxConfig) {} - private matchDocumentType(config: AvataxConfig): DocumentType { - if (!config.isDocumentRecordingEnabled) { - return DocumentType.SalesOrder; - } - - return DocumentType.SalesInvoice; - } - transform({ order }: AvataxOrderFulfilledPayload): AvataxOrderFulfilledTarget { - const transactionCode = z.string().min(1).parse(order.avataxId); - - return { - transactionCode, - companyCode: this.config.companyCode, - documentType: this.matchDocumentType(this.config), - model: { - commit: true, - }, - }; - } -} diff --git a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-response-transformer.ts b/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-response-transformer.ts deleted file mode 100644 index 2d3a5bb..0000000 --- a/apps/taxes/src/modules/avatax/order-fulfilled/avatax-order-fulfilled-response-transformer.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TransactionModel } from "avatax/lib/models/TransactionModel"; -import { AvataxOrderFulfilledResponse } from "./avatax-order-fulfilled-adapter"; - -export class AvataxOrderFulfilledResponseTransformer { - transform(response: TransactionModel): AvataxOrderFulfilledResponse { - return { ok: true }; - } -} diff --git a/apps/taxes/src/modules/taxes/get-active-connection-service.ts b/apps/taxes/src/modules/taxes/get-active-connection-service.ts index f9aae1a..a8145e5 100644 --- a/apps/taxes/src/modules/taxes/get-active-connection-service.ts +++ b/apps/taxes/src/modules/taxes/get-active-connection-service.ts @@ -54,10 +54,6 @@ class ActiveTaxProviderService implements ProviderWebhookService { return this.client.confirmOrder(order); } - async fulfillOrder(payload: OrderFulfilledSubscriptionFragment) { - return this.client.fulfillOrder(payload); - } - async cancelOrder(payload: OrderCancelledPayload) { this.client.cancelOrder(payload); } diff --git a/apps/taxes/src/modules/taxes/tax-provider-webhook.ts b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts index af44988..43139aa 100644 --- a/apps/taxes/src/modules/taxes/tax-provider-webhook.ts +++ b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts @@ -13,6 +13,5 @@ export type CreateOrderResponse = { id: string }; export interface ProviderWebhookService { calculateTaxes: (payload: TaxBaseFragment) => Promise; confirmOrder: (payload: OrderConfirmedSubscriptionFragment) => Promise; - fulfillOrder: (payload: OrderFulfilledSubscriptionFragment) => Promise<{ ok: boolean }>; cancelOrder: (payload: OrderCancelledPayload) => Promise; } diff --git a/apps/taxes/src/pages/api/manifest.ts b/apps/taxes/src/pages/api/manifest.ts index 2a34991..0e31a9a 100644 --- a/apps/taxes/src/pages/api/manifest.ts +++ b/apps/taxes/src/pages/api/manifest.ts @@ -5,7 +5,6 @@ import packageJson from "../../../package.json"; import { checkoutCalculateTaxesSyncWebhook } from "./webhooks/checkout-calculate-taxes"; import { orderCalculateTaxesSyncWebhook } from "./webhooks/order-calculate-taxes"; import { orderConfirmedAsyncWebhook } from "./webhooks/order-confirmed"; -import { orderFulfilledAsyncWebhook } from "./webhooks/order-fulfilled"; import { REQUIRED_SALEOR_VERSION } from "../../../saleor-app"; import { orderCancelledAsyncWebhook } from "./webhooks/order-cancelled"; @@ -37,7 +36,6 @@ export default createManifestHandler({ orderCalculateTaxesSyncWebhook.getWebhookManifest(apiBaseURL), checkoutCalculateTaxesSyncWebhook.getWebhookManifest(apiBaseURL), orderConfirmedAsyncWebhook.getWebhookManifest(apiBaseURL), - orderFulfilledAsyncWebhook.getWebhookManifest(apiBaseURL), orderCancelledAsyncWebhook.getWebhookManifest(apiBaseURL), ], }; diff --git a/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts b/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts deleted file mode 100644 index 29c68d6..0000000 --- a/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { - OrderFulfilledEventSubscriptionFragment, - UntypedOrderFulfilledSubscriptionDocument, -} from "../../../../generated/graphql"; -import { saleorApp } from "../../../../saleor-app"; -import { createLogger } from "../../../lib/logger"; -import { getActiveConnectionService } from "../../../modules/taxes/get-active-connection-service"; -import { WebhookResponse } from "../../../modules/app/webhook-response"; -export const config = { - api: { - bodyParser: false, - }, -}; - -type OrderFulfilledPayload = Extract< - OrderFulfilledEventSubscriptionFragment, - { __typename: "OrderFulfilled" } ->; - -export const orderFulfilledAsyncWebhook = new SaleorAsyncWebhook({ - name: "OrderFulfilled", - apl: saleorApp.apl, - event: "ORDER_FULFILLED", - query: UntypedOrderFulfilledSubscriptionDocument, - webhookPath: "/api/webhooks/order-fulfilled", -}); - -export default orderFulfilledAsyncWebhook.createHandler(async (req, res, ctx) => { - const logger = createLogger({ event: ctx.event }); - const { payload } = ctx; - const webhookResponse = new WebhookResponse(res); - - logger.info("Handler called with payload"); - - try { - const appMetadata = payload.recipient?.privateMetadata ?? []; - const channelSlug = payload.order?.channel.slug; - const taxProvider = getActiveConnectionService(channelSlug, appMetadata, ctx.authData); - - // todo: figure out what fields are needed and add validation - if (!payload.order) { - return webhookResponse.error(new Error("Insufficient order data")); - } - logger.info("Fulfilling order..."); - - await taxProvider.fulfillOrder(payload.order); - - logger.info("Order fulfilled"); - - return webhookResponse.success(); - } catch (error) { - return webhookResponse.error(error); - } -});