diff --git a/apps/taxes/graphql/subscriptions/OrderCancelled.graphql b/apps/taxes/graphql/subscriptions/OrderCancelled.graphql index 07b2bc8..09e4176 100644 --- a/apps/taxes/graphql/subscriptions/OrderCancelled.graphql +++ b/apps/taxes/graphql/subscriptions/OrderCancelled.graphql @@ -1,9 +1,6 @@ fragment OrderCancelledSubscription on Order { id - privateMetadata { - key - value - } + externalId: metafield(key: "externalId") channel { id slug diff --git a/apps/taxes/src/modules/avatax/avatax-client.ts b/apps/taxes/src/modules/avatax/avatax-client.ts index 53631d1..f57b8b8 100644 --- a/apps/taxes/src/modules/avatax/avatax-client.ts +++ b/apps/taxes/src/modules/avatax/avatax-client.ts @@ -53,6 +53,11 @@ export type ValidateAddressArgs = { address: AvataxAddress; }; +export type VoidTransactionArgs = { + transactionCode: string; + companyCode: string; +}; + export class AvataxClient { private client: Avatax; private logger: Logger; @@ -73,6 +78,16 @@ export class AvataxClient { return this.client.commitTransaction(args); } + async voidTransaction({ + transactionCode, + companyCode, + }: { + transactionCode: string; + companyCode: string; + }) { + return this.client.voidTransaction({ transactionCode, companyCode }); + } + async validateAddress({ address }: ValidateAddressArgs) { return this.client.resolveAddress(address); } diff --git a/apps/taxes/src/modules/avatax/avatax-webhook.service.ts b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts index 68ae0fa..08e890b 100644 --- a/apps/taxes/src/modules/avatax/avatax-webhook.service.ts +++ b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts @@ -12,6 +12,8 @@ import { AvataxConfig, defaultAvataxConfig } from "./avatax-connection-schema"; import { AvataxCalculateTaxesAdapter } from "./calculate-taxes/avatax-calculate-taxes-adapter"; import { AvataxOrderCreatedAdapter } from "./order-created/avatax-order-created-adapter"; import { AvataxOrderFulfilledAdapter } from "./order-fulfilled/avatax-order-fulfilled-adapter"; +import { AvataxOrderCancelledAdapter } from "./order-cancelled/avatax-order-cancelled-adapter"; +import { OrderCancelledPayload } from "../../pages/api/webhooks/order-cancelled"; export class AvataxWebhookService implements ProviderWebhookService { config = defaultAvataxConfig; @@ -52,9 +54,10 @@ export class AvataxWebhookService implements ProviderWebhookService { return response; } - async cancelOrder(payload: OrderCancelledEventSubscriptionFragment) { - // todo: implement + async cancelOrder(payload: OrderCancelledPayload) { this.logger.debug("cancelOrder", payload); - return { ok: true }; + const adapter = new AvataxOrderCancelledAdapter(this.config); + + await adapter.send(payload); } } diff --git a/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-adapter.ts b/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-adapter.ts new file mode 100644 index 0000000..d34cb73 --- /dev/null +++ b/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-adapter.ts @@ -0,0 +1,31 @@ +import { Logger, createLogger } from "../../../lib/logger"; +import { OrderCancelledPayload } from "../../../pages/api/webhooks/order-cancelled"; +import { WebhookAdapter } from "../../taxes/tax-webhook-adapter"; +import { AvataxClient, VoidTransactionArgs } from "../avatax-client"; +import { AvataxConfig } from "../avatax-connection-schema"; +import { AvataxOrderCancelledPayloadTransformer } from "./avatax-order-cancelled-payload-transformer"; + +export type AvataxOrderCancelledTarget = VoidTransactionArgs; + +export class AvataxOrderCancelledAdapter implements WebhookAdapter { + private logger: Logger; + + constructor(private readonly config: AvataxConfig) { + this.logger = createLogger({ name: "AvataxOrderCancelledAdapter" }); + } + + async send(payload: OrderCancelledPayload) { + this.logger.debug("Transforming the Saleor payload for cancelling transaction with Avatax..."); + + const payloadTransformer = new AvataxOrderCancelledPayloadTransformer(this.config); + const target = payloadTransformer.transform({ ...payload }); + + this.logger.debug("Calling Avatax commitTransaction with transformed payload..."); + + const client = new AvataxClient(this.config); + + await client.voidTransaction(target); + + this.logger.debug("Avatax commitTransaction succesfully responded"); + } +} diff --git a/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-payload-transformer.ts b/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-payload-transformer.ts new file mode 100644 index 0000000..779c530 --- /dev/null +++ b/apps/taxes/src/modules/avatax/order-cancelled/avatax-order-cancelled-payload-transformer.ts @@ -0,0 +1,21 @@ +import { OrderCancelledPayload } from "../../../pages/api/webhooks/order-cancelled"; +import { taxProviderUtils } from "../../taxes/tax-provider-utils"; +import { AvataxConfig } from "../avatax-connection-schema"; +import { AvataxOrderCancelledTarget } from "./avatax-order-cancelled-adapter"; + +export class AvataxOrderCancelledPayloadTransformer { + constructor(private readonly config: AvataxConfig) {} + + transform({ order }: OrderCancelledPayload): AvataxOrderCancelledTarget { + if (!order) { + throw new Error("Order is required"); + } + + const transactionCode = taxProviderUtils.resolveOptionalOrThrow(order.externalId); + + return { + transactionCode, + companyCode: this.config.companyCode ?? "", + }; + } +} 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 0a8e6e8..a50458e 100644 --- a/apps/taxes/src/modules/taxes/get-active-connection-service.ts +++ b/apps/taxes/src/modules/taxes/get-active-connection-service.ts @@ -13,6 +13,7 @@ import { AvataxWebhookService } from "../avatax/avatax-webhook.service"; import { ProviderConnection } from "../provider-connections/provider-connections"; import { TaxJarWebhookService } from "../taxjar/taxjar-webhook.service"; import { ProviderWebhookService } from "./tax-provider-webhook"; +import { OrderCancelledPayload } from "../../pages/api/webhooks/order-cancelled"; // todo: refactor to a factory class ActiveTaxProviderService implements ProviderWebhookService { @@ -57,8 +58,8 @@ class ActiveTaxProviderService implements ProviderWebhookService { return this.client.fulfillOrder(payload); } - async cancelOrder(payload: OrderCancelledEventSubscriptionFragment) { - return this.client.cancelOrder(payload); + async cancelOrder(payload: OrderCancelledPayload) { + this.client.cancelOrder(payload); } } diff --git a/apps/taxes/src/modules/taxes/tax-provider-utils.ts b/apps/taxes/src/modules/taxes/tax-provider-utils.ts index 568ee54..8b11080 100644 --- a/apps/taxes/src/modules/taxes/tax-provider-utils.ts +++ b/apps/taxes/src/modules/taxes/tax-provider-utils.ts @@ -3,8 +3,8 @@ * If it ever happens, we have nothing to fall back to, so we throw an error. * Should only be used for values that are required for further calculation. */ -function resolveOptionalOrThrow(value: T | undefined, error?: Error): T { - if (value === undefined) { +function resolveOptionalOrThrow(value: T | undefined | null, error?: Error): T { + if (value === undefined || value === null) { throw error ? error : new Error("Could not resolve data. Value needed for further calculation is undefined."); diff --git a/apps/taxes/src/modules/taxes/tax-provider-webhook.ts b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts index e942722..8a4d070 100644 --- a/apps/taxes/src/modules/taxes/tax-provider-webhook.ts +++ b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts @@ -1,10 +1,10 @@ import { SyncWebhookResponsesMap } from "@saleor/app-sdk/handlers/next"; import { - OrderCancelledEventSubscriptionFragment, OrderCreatedSubscriptionFragment, OrderFulfilledSubscriptionFragment, TaxBaseFragment, } from "../../../generated/graphql"; +import { OrderCancelledPayload } from "../../pages/api/webhooks/order-cancelled"; export type CalculateTaxesResponse = SyncWebhookResponsesMap["ORDER_CALCULATE_TAXES"]; @@ -14,5 +14,5 @@ export interface ProviderWebhookService { calculateTaxes: (payload: TaxBaseFragment) => Promise; createOrder: (payload: OrderCreatedSubscriptionFragment) => Promise; fulfillOrder: (payload: OrderFulfilledSubscriptionFragment) => Promise<{ ok: boolean }>; - cancelOrder: (payload: OrderCancelledEventSubscriptionFragment) => Promise; + cancelOrder: (payload: OrderCancelledPayload) => Promise; } diff --git a/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts b/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts index dfb1ac3..320aa48 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts @@ -50,6 +50,5 @@ export class TaxJarWebhookService implements ProviderWebhookService { async cancelOrder(payload: OrderCancelledEventSubscriptionFragment) { // todo: implement this.logger.debug("cancelOrder", payload); - return { ok: true }; } } diff --git a/apps/taxes/src/pages/api/webhooks/order-cancelled.ts b/apps/taxes/src/pages/api/webhooks/order-cancelled.ts index c9f0186..184b6d9 100644 --- a/apps/taxes/src/pages/api/webhooks/order-cancelled.ts +++ b/apps/taxes/src/pages/api/webhooks/order-cancelled.ts @@ -13,7 +13,7 @@ export const config = { }, }; -type OrderCancelledPayload = Extract< +export type OrderCancelledPayload = Extract< OrderCancelledEventSubscriptionFragment, { __typename: "OrderCancelled" } >;