Skip to content

Commit

Permalink
feat: ✨ add createOrAdjustTransaction refund flow
Browse files Browse the repository at this point in the history
  • Loading branch information
peelar committed Aug 30, 2023
1 parent 2f8051b commit c302f41
Show file tree
Hide file tree
Showing 15 changed files with 140 additions and 184 deletions.
22 changes: 22 additions & 0 deletions apps/taxes/graphql/fragments/OrderLine.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
fragment OrderLine on OrderLine {
id
productSku
productName
quantity
taxClass {
id
}
unitPrice {
net {
amount
}
}
totalPrice {
net {
amount
}
tax {
amount
}
}
}
22 changes: 0 additions & 22 deletions apps/taxes/graphql/subscriptions/OrderConfirmed.graphql
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
fragment OrderLine on OrderLine {
productSku
productName
quantity
taxClass {
id
}
unitPrice {
net {
amount
}
}
totalPrice {
net {
amount
}
tax {
amount
}
}
}

fragment OrderConfirmedSubscription on Order {
id
number
Expand Down
35 changes: 9 additions & 26 deletions apps/taxes/graphql/subscriptions/OrderRefunded.graphql
Original file line number Diff line number Diff line change
@@ -1,40 +1,23 @@
fragment Payment on Payment {
transactions {
kind
amount {
...Money
}
}
}

fragment OrderRefundedSubscription on Order {
id
avataxId: metafield(key: "avataxId")
...AvataxOrderMetadata
user {
id
email
}
channel {
id
slug
}
lines {
productSku
}
totalRefunded {
...Money
}
totalGrantedRefund {
...Money
}
totalRefundPending {
...Money
}
grantedRefunds {
amount {
transactions {
id
refundedAmount {
...Money
}
reason
}
payments {
...Payment
shippingAddress {
...Address
}
}

Expand Down
3 changes: 3 additions & 0 deletions apps/taxes/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
},
lines: [
{
id: "T3JkZXJMaW5lOjE=",
productSku: "328223580",
productName: "Monospace Tee",
quantity: 3,
Expand All @@ -71,6 +72,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
},
},
{
id: "T3JkZXJMaW5lOjI=",
productSku: "328223581",
productName: "Monospace Tee",
quantity: 1,
Expand All @@ -89,6 +91,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
},
},
{
id: "T3JkZXJMaW5lOjM=",
productSku: "118223581",
productName: "Paul's Balance 420",
quantity: 2,
Expand Down
16 changes: 14 additions & 2 deletions apps/taxes/src/modules/avatax/avatax-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ export type VoidTransactionArgs = {
companyCode: string;
};

export type RefundTransactionParams = Parameters<Avatax["refundTransaction"]>[0];
export type RefundTransactionParams = Pick<
CreateTransactionModel,
"customerCode" | "lines" | "date" | "addresses" | "code" | "companyCode"
>;

export class AvataxClient {
private client: Avatax;
Expand Down Expand Up @@ -116,6 +119,15 @@ export class AvataxClient {
}

async refundTransaction(params: RefundTransactionParams) {
return this.client.refundTransaction(params);
// https://developer.avalara.com/erp-integration-guide/refunds-badge/refunds-with-create-transactions/
return this.client.createOrAdjustTransaction({
model: {
createTransactionModel: {
type: DocumentType.ReturnInvoice,
commit: true,
...params,
},
},
});
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { AddressesModel } from "avatax/lib/models/AddressesModel";
import { AddressFragment } from "../../../../generated/graphql";
import { taxProviderUtils } from "../../taxes/tax-provider-utils";
import { avataxAddressFactory } from "../address-factory";
import { AvataxConfig } from "../avatax-connection-schema";
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";

export class AvataxAddressResolver {
resolve({
from,
to,
}: {
from: AvataxConfig["address"];
to: AddressFragment;
}): CreateTransactionModel["addresses"] {
to: AddressFragment | undefined | null;
}): AddressesModel {
return {
shipFrom: avataxAddressFactory.fromChannelAddress(from),
shipTo: avataxAddressFactory.fromSaleorAddress(to),
shipTo: avataxAddressFactory.fromSaleorAddress(taxProviderUtils.resolveOptionalOrThrow(to)),
};
}
}
Original file line number Diff line number Diff line change
@@ -1,60 +1,9 @@
import { RefundType } from "avatax/lib/enums/RefundType";
import { z } from "zod";
import { Logger, createLogger } from "../../../lib/logger";
import { OrderRefundedPayload } from "../../../pages/api/webhooks/order-refunded";
import { WebhookAdapter } from "../../taxes/tax-webhook-adapter";
import { AvataxClient, RefundTransactionParams } from "../avatax-client";
import { AvataxConfig, defaultAvataxConfig } from "../avatax-connection-schema";
import { taxProviderUtils } from "../../taxes/tax-provider-utils";

class AvataxOrderRefundedPayloadTransformer {
private logger: Logger;

constructor() {
this.logger = createLogger({ name: "AvataxOrderRefundedPayloadTransformer" });
}

transform(payload: OrderRefundedPayload, avataxConfig: AvataxConfig): RefundTransactionParams {
this.logger.debug(
{ payload },
"Transforming the Saleor payload for refunding order with AvaTax...",
);

const isFull = true;

const transactionCode = z
.string()
.min(1, "Unable to refund transaction. Avatax id not found in order metadata")
.parse(payload.order?.avataxId);

const baseParams: Pick<RefundTransactionParams, "transactionCode" | "companyCode"> = {
transactionCode,
companyCode: avataxConfig.companyCode ?? defaultAvataxConfig.companyCode,
};

if (!isFull) {
return {
...baseParams,
model: {
refundType: RefundType.Partial,
refundDate: new Date(),
refundLines: payload.order?.lines?.map((line) =>
// todo: replace with some other code
taxProviderUtils.resolveStringOrThrow(line.productSku),
),
},
};
}

return {
...baseParams,
model: {
refundType: RefundType.Full,
refundDate: new Date(),
},
};
}
}
import { AvataxClient } from "../avatax-client";
import { AvataxConfig } from "../avatax-connection-schema";
import { AvataxOrderRefundedPayloadTransformer } from "./avatax-order-refunded-payload-transformer";

export class AvataxOrderRefundedAdapter implements WebhookAdapter<OrderRefundedPayload, void> {
private logger: Logger;
Expand All @@ -79,11 +28,15 @@ export class AvataxOrderRefundedAdapter implements WebhookAdapter<OrderRefundedP
const payloadTransformer = new AvataxOrderRefundedPayloadTransformer();
const target = payloadTransformer.transform(payload, this.config);

const response = await client.refundTransaction(target);

this.logger.debug(
{ response },
`Succesfully refunded the transaction of id: ${target.transactionCode}`,
{
target,
},
`Refunding the transaction...`,
);

const response = await client.refundTransaction(target);

this.logger.debug({ response }, `Succesfully refunded the transaction`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { OrderRefundedPayload } from "../../../pages/api/webhooks/order-refunded";
import { RefundTransactionParams } from "../avatax-client";

export class AvataxOrderRefundedLinesTransformer {
transform(payload: OrderRefundedPayload): RefundTransactionParams["lines"] {
const refundTransactions =
payload.order?.transactions.filter((t) => t.refundedAmount.amount > 0) ?? [];

if (!refundTransactions.length) {
throw new Error("Cannot refund order without any refund transactions");
}

return refundTransactions.map((t) => ({
amount: -t.refundedAmount.amount,
taxIncluded: true,
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Logger, createLogger } from "../../../lib/logger";
import { OrderRefundedPayload } from "../../../pages/api/webhooks/order-refunded";
import { taxProviderUtils } from "../../taxes/tax-provider-utils";
import { RefundTransactionParams } from "../avatax-client";
import { AvataxConfig, defaultAvataxConfig } from "../avatax-connection-schema";
import { AvataxDocumentCodeResolver } from "../avatax-document-code-resolver";
import { AvataxAddressResolver } from "../order-confirmed/avatax-address-resolver";
import { AvataxOrderRefundedLinesTransformer } from "./avatax-order-refunded-lines-transformer";

export class AvataxOrderRefundedPayloadTransformer {
private logger: Logger;

constructor() {
this.logger = createLogger({ name: "AvataxOrderRefundedPayloadTransformer" });
}

transform(payload: OrderRefundedPayload, avataxConfig: AvataxConfig): RefundTransactionParams {
this.logger.debug(
{ payload },
"Transforming the Saleor payload for refunding order with AvaTax...",
);

const addressResolver = new AvataxAddressResolver();
const linesTransformer = new AvataxOrderRefundedLinesTransformer();
const documentCodeResolver = new AvataxDocumentCodeResolver();

const addresses = addressResolver.resolve({
from: avataxConfig.address,
to: payload.order?.shippingAddress,
});
const lines = linesTransformer.transform(payload);
const customerCode = taxProviderUtils.resolveStringOrThrow(payload.order?.user?.id);
const code = documentCodeResolver.resolve({
avataxDocumentCode: payload.order?.avataxDocumentCode,
orderId: payload.order?.id,
});

return {
code,
lines,
customerCode,
addresses,
date: new Date(),
companyCode: avataxConfig.companyCode ?? defaultAvataxConfig.companyCode,
};
}
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit c302f41

Please sign in to comment.