feat: ✨ add createOrAdjustTransaction refund flow
This commit is contained in:
parent
2f8051bacc
commit
c302f41f3e
15 changed files with 140 additions and 184 deletions
22
apps/taxes/graphql/fragments/OrderLine.graphql
Normal file
22
apps/taxes/graphql/fragments/OrderLine.graphql
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
fragment OrderLine on OrderLine {
|
||||||
|
id
|
||||||
|
productSku
|
||||||
|
productName
|
||||||
|
quantity
|
||||||
|
taxClass {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
unitPrice {
|
||||||
|
net {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalPrice {
|
||||||
|
net {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
tax {
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
fragment OrderConfirmedSubscription on Order {
|
||||||
id
|
id
|
||||||
number
|
number
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
fragment Payment on Payment {
|
|
||||||
transactions {
|
|
||||||
kind
|
|
||||||
amount {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment OrderRefundedSubscription on Order {
|
fragment OrderRefundedSubscription on Order {
|
||||||
id
|
id
|
||||||
avataxId: metafield(key: "avataxId")
|
avataxId: metafield(key: "avataxId")
|
||||||
...AvataxOrderMetadata
|
...AvataxOrderMetadata
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
}
|
||||||
channel {
|
channel {
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
lines {
|
transactions {
|
||||||
productSku
|
id
|
||||||
}
|
refundedAmount {
|
||||||
totalRefunded {
|
|
||||||
...Money
|
...Money
|
||||||
}
|
}
|
||||||
totalGrantedRefund {
|
|
||||||
...Money
|
|
||||||
}
|
}
|
||||||
totalRefundPending {
|
shippingAddress {
|
||||||
...Money
|
...Address
|
||||||
}
|
|
||||||
grantedRefunds {
|
|
||||||
amount {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
reason
|
|
||||||
}
|
|
||||||
payments {
|
|
||||||
...Payment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
|
||||||
},
|
},
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
|
id: "T3JkZXJMaW5lOjE=",
|
||||||
productSku: "328223580",
|
productSku: "328223580",
|
||||||
productName: "Monospace Tee",
|
productName: "Monospace Tee",
|
||||||
quantity: 3,
|
quantity: 3,
|
||||||
|
@ -71,6 +72,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "T3JkZXJMaW5lOjI=",
|
||||||
productSku: "328223581",
|
productSku: "328223581",
|
||||||
productName: "Monospace Tee",
|
productName: "Monospace Tee",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
@ -89,6 +91,7 @@ export const defaultOrder: OrderConfirmedSubscriptionFragment = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "T3JkZXJMaW5lOjM=",
|
||||||
productSku: "118223581",
|
productSku: "118223581",
|
||||||
productName: "Paul's Balance 420",
|
productName: "Paul's Balance 420",
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
|
|
|
@ -55,7 +55,10 @@ export type VoidTransactionArgs = {
|
||||||
companyCode: string;
|
companyCode: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RefundTransactionParams = Parameters<Avatax["refundTransaction"]>[0];
|
export type RefundTransactionParams = Pick<
|
||||||
|
CreateTransactionModel,
|
||||||
|
"customerCode" | "lines" | "date" | "addresses" | "code" | "companyCode"
|
||||||
|
>;
|
||||||
|
|
||||||
export class AvataxClient {
|
export class AvataxClient {
|
||||||
private client: Avatax;
|
private client: Avatax;
|
||||||
|
@ -116,6 +119,15 @@ export class AvataxClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async refundTransaction(params: RefundTransactionParams) {
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { AddressesModel } from "avatax/lib/models/AddressesModel";
|
||||||
import { AddressFragment } from "../../../../generated/graphql";
|
import { AddressFragment } from "../../../../generated/graphql";
|
||||||
|
import { taxProviderUtils } from "../../taxes/tax-provider-utils";
|
||||||
import { avataxAddressFactory } from "../address-factory";
|
import { avataxAddressFactory } from "../address-factory";
|
||||||
import { AvataxConfig } from "../avatax-connection-schema";
|
import { AvataxConfig } from "../avatax-connection-schema";
|
||||||
import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel";
|
|
||||||
|
|
||||||
export class AvataxAddressResolver {
|
export class AvataxAddressResolver {
|
||||||
resolve({
|
resolve({
|
||||||
|
@ -9,11 +10,11 @@ export class AvataxAddressResolver {
|
||||||
to,
|
to,
|
||||||
}: {
|
}: {
|
||||||
from: AvataxConfig["address"];
|
from: AvataxConfig["address"];
|
||||||
to: AddressFragment;
|
to: AddressFragment | undefined | null;
|
||||||
}): CreateTransactionModel["addresses"] {
|
}): AddressesModel {
|
||||||
return {
|
return {
|
||||||
shipFrom: avataxAddressFactory.fromChannelAddress(from),
|
shipFrom: avataxAddressFactory.fromChannelAddress(from),
|
||||||
shipTo: avataxAddressFactory.fromSaleorAddress(to),
|
shipTo: avataxAddressFactory.fromSaleorAddress(taxProviderUtils.resolveOptionalOrThrow(to)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,9 @@
|
||||||
import { RefundType } from "avatax/lib/enums/RefundType";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Logger, createLogger } from "../../../lib/logger";
|
import { Logger, createLogger } from "../../../lib/logger";
|
||||||
import { OrderRefundedPayload } from "../../../pages/api/webhooks/order-refunded";
|
import { OrderRefundedPayload } from "../../../pages/api/webhooks/order-refunded";
|
||||||
import { WebhookAdapter } from "../../taxes/tax-webhook-adapter";
|
import { WebhookAdapter } from "../../taxes/tax-webhook-adapter";
|
||||||
import { AvataxClient, RefundTransactionParams } from "../avatax-client";
|
import { AvataxClient } from "../avatax-client";
|
||||||
import { AvataxConfig, defaultAvataxConfig } from "../avatax-connection-schema";
|
import { AvataxConfig } from "../avatax-connection-schema";
|
||||||
import { taxProviderUtils } from "../../taxes/tax-provider-utils";
|
import { AvataxOrderRefundedPayloadTransformer } from "./avatax-order-refunded-payload-transformer";
|
||||||
|
|
||||||
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(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AvataxOrderRefundedAdapter implements WebhookAdapter<OrderRefundedPayload, void> {
|
export class AvataxOrderRefundedAdapter implements WebhookAdapter<OrderRefundedPayload, void> {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
@ -79,11 +28,15 @@ export class AvataxOrderRefundedAdapter implements WebhookAdapter<OrderRefundedP
|
||||||
const payloadTransformer = new AvataxOrderRefundedPayloadTransformer();
|
const payloadTransformer = new AvataxOrderRefundedPayloadTransformer();
|
||||||
const target = payloadTransformer.transform(payload, this.config);
|
const target = payloadTransformer.transform(payload, this.config);
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
{
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
`Refunding the transaction...`,
|
||||||
|
);
|
||||||
|
|
||||||
const response = await client.refundTransaction(target);
|
const response = await client.refundTransaction(target);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug({ response }, `Succesfully refunded the transaction`);
|
||||||
{ response },
|
|
||||||
`Succesfully refunded the transaction of id: ${target.transactionCode}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
import { PaymentFragment, TransactionKind } from "../../../../generated/graphql";
|
|
||||||
import { AvataxRefundsResolver } from "./avatax-refunds-resolver";
|
|
||||||
import { expect, describe, it } from "vitest";
|
|
||||||
|
|
||||||
describe("AvataxRefundsResolver", () => {
|
|
||||||
it("returns transaction amounts for refunds", () => {
|
|
||||||
const resolver = new AvataxRefundsResolver();
|
|
||||||
const mockPayments: PaymentFragment[] = [
|
|
||||||
{
|
|
||||||
transactions: [
|
|
||||||
{
|
|
||||||
kind: TransactionKind.Refund,
|
|
||||||
amount: {
|
|
||||||
amount: 20.0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: TransactionKind.Capture,
|
|
||||||
amount: {
|
|
||||||
amount: 20.0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
transactions: [
|
|
||||||
{
|
|
||||||
kind: TransactionKind.Refund,
|
|
||||||
amount: {
|
|
||||||
amount: 35.0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const refunds = resolver.resolve(mockPayments);
|
|
||||||
|
|
||||||
expect(refunds).toEqual([
|
|
||||||
{
|
|
||||||
amount: 20.0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
amount: 35.0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { PaymentFragment, TransactionKind } from "../../../../generated/graphql";
|
|
||||||
|
|
||||||
export class AvataxRefundsResolver {
|
|
||||||
resolve(payments: PaymentFragment[]) {
|
|
||||||
return payments
|
|
||||||
.flatMap(
|
|
||||||
(payment) => payment.transactions?.filter((t) => t.kind === TransactionKind.Refund) ?? [],
|
|
||||||
)
|
|
||||||
.map((t) => t.amount);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ function joinAddresses(address1: string, address2: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapSaleorAddressToTaxJarAddress(
|
function mapSaleorAddressToTaxJarAddress(
|
||||||
address: SaleorAddress
|
address: SaleorAddress,
|
||||||
): Pick<TaxParams, "to_city" | "to_country" | "to_state" | "to_street" | "to_zip"> {
|
): Pick<TaxParams, "to_city" | "to_country" | "to_state" | "to_street" | "to_zip"> {
|
||||||
return {
|
return {
|
||||||
to_street: joinAddresses(address.streetAddress1, address.streetAddress2),
|
to_street: joinAddresses(address.streetAddress1, address.streetAddress2),
|
||||||
|
@ -20,7 +20,7 @@ function mapSaleorAddressToTaxJarAddress(
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapChannelAddressToTaxJarAddress(
|
function mapChannelAddressToTaxJarAddress(
|
||||||
address: TaxJarConfig["address"]
|
address: TaxJarConfig["address"],
|
||||||
): Pick<TaxParams, "from_city" | "from_country" | "from_state" | "from_street" | "from_zip"> {
|
): Pick<TaxParams, "from_city" | "from_country" | "from_state" | "from_street" | "from_zip"> {
|
||||||
return {
|
return {
|
||||||
from_city: address.city,
|
from_city: address.city,
|
||||||
|
@ -31,7 +31,7 @@ function mapChannelAddressToTaxJarAddress(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapChannelAddressToAddressParams(address: TaxJarConfig["address"]): AddressParams {
|
function mapChannelAddresstoParams(address: TaxJarConfig["address"]): AddressParams {
|
||||||
return {
|
return {
|
||||||
city: address.city,
|
city: address.city,
|
||||||
country: address.country,
|
country: address.country,
|
||||||
|
@ -44,5 +44,5 @@ function mapChannelAddressToAddressParams(address: TaxJarConfig["address"]): Add
|
||||||
export const taxJarAddressFactory = {
|
export const taxJarAddressFactory = {
|
||||||
fromSaleorToTax: mapSaleorAddressToTaxJarAddress,
|
fromSaleorToTax: mapSaleorAddressToTaxJarAddress,
|
||||||
fromChannelToTax: mapChannelAddressToTaxJarAddress,
|
fromChannelToTax: mapChannelAddressToTaxJarAddress,
|
||||||
fromChannelToParams: mapChannelAddressToAddressParams,
|
fromChannelToParams: mapChannelAddresstoParams,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,19 +9,19 @@ export class TaxJarCalculateTaxesPayloadTransformer {
|
||||||
constructor(private readonly config: TaxJarConfig) {}
|
constructor(private readonly config: TaxJarConfig) {}
|
||||||
|
|
||||||
transform(taxBase: TaxBaseFragment, matches: TaxJarTaxCodeMatches): TaxJarCalculateTaxesTarget {
|
transform(taxBase: TaxBaseFragment, matches: TaxJarTaxCodeMatches): TaxJarCalculateTaxesTarget {
|
||||||
const fromAddress = taxJarAddressFactory.fromChannelToTax(this.config.address);
|
const from = taxJarAddressFactory.fromChannelToTax(this.config.address);
|
||||||
|
|
||||||
if (!taxBase.address) {
|
if (!taxBase.address) {
|
||||||
throw new Error("Customer address is required to calculate taxes in TaxJar.");
|
throw new Error("Customer address is required to calculate taxes in TaxJar.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineTransformer = new TaxJarCalculateTaxesPayloadLinesTransformer();
|
const lineTransformer = new TaxJarCalculateTaxesPayloadLinesTransformer();
|
||||||
const toAddress = taxJarAddressFactory.fromSaleorToTax(taxBase.address);
|
const to = taxJarAddressFactory.fromSaleorToTax(taxBase.address);
|
||||||
|
|
||||||
const taxParams: TaxJarCalculateTaxesTarget = {
|
const taxParams: TaxJarCalculateTaxesTarget = {
|
||||||
params: {
|
params: {
|
||||||
...fromAddress,
|
...from,
|
||||||
...toAddress,
|
...to,
|
||||||
shipping: taxBase.shippingPrice.amount,
|
shipping: taxBase.shippingPrice.amount,
|
||||||
line_items: lineTransformer.transform(taxBase, matches),
|
line_items: lineTransformer.transform(taxBase, matches),
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ const transformer = new TaxJarOrderConfirmedPayloadLinesTransformer();
|
||||||
|
|
||||||
const mockedLines: OrderConfirmedSubscriptionFragment["lines"] = [
|
const mockedLines: OrderConfirmedSubscriptionFragment["lines"] = [
|
||||||
{
|
{
|
||||||
|
id: "T3JkZXJMaW5lOjE=",
|
||||||
productSku: "sku",
|
productSku: "sku",
|
||||||
productName: "Test product",
|
productName: "Test product",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
@ -29,6 +30,7 @@ const mockedLines: OrderConfirmedSubscriptionFragment["lines"] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "T3JkZXJMaW5lOjF=",
|
||||||
productSku: "sku-2",
|
productSku: "sku-2",
|
||||||
productName: "Test product 2",
|
productName: "Test product 2",
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { TaxJarOrderConfirmedTaxCodeMatcher } from "./taxjar-order-confirmed-tax
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const mockedLine: OrderLineFragment = {
|
const mockedLine: OrderLineFragment = {
|
||||||
|
id: "T3JkZXJMaW5lOjE=",
|
||||||
productSku: "sku",
|
productSku: "sku",
|
||||||
productName: "Test product",
|
productName: "Test product",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
|
Loading…
Reference in a new issue