653 lines
18 KiB
TypeScript
653 lines
18 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { type ApiCustomerV3, OnDecrease, OnIncrease } from "@autumn/shared";
|
|
import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect.js";
|
|
import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect.js";
|
|
import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect.js";
|
|
import { expectLatestInvoiceCorrect } from "@tests/integration/billing/utils/expectLatestInvoiceCorrect.js";
|
|
import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect.js";
|
|
import { TestFeature } from "@tests/setup/v2Features.js";
|
|
import { items } from "@tests/utils/fixtures/items.js";
|
|
import { products } from "@tests/utils/fixtures/products.js";
|
|
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
|
import chalk from "chalk";
|
|
|
|
/**
|
|
* Multi-Entity Prepaid Quantity Tests
|
|
*
|
|
* These tests verify that prepaid quantity updates work correctly
|
|
* when multiple entities share products or have different products.
|
|
*
|
|
* Key scenarios:
|
|
* - Entity 1 increases quantity while Entity 2 remains unchanged
|
|
* - Entity 2 decreases quantity while Entity 1 remains unchanged
|
|
* - Cross-entity mixed changes (increase one, decrease other)
|
|
* - OnDecrease.None config (no credit invoice on decrease)
|
|
* - Different products per entity with quantity updates
|
|
* - Multiple features per entity
|
|
*/
|
|
|
|
// Test 1: Basic Multi-Entity Increase
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: entity 1 increases quantity")}`, async () => {
|
|
const customerId = "multi-ent-qty-increase";
|
|
const billingUnits = 12;
|
|
const pricePerUnit = 8;
|
|
|
|
const prepaidItem = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: pricePerUnit,
|
|
});
|
|
|
|
const product = products.base({
|
|
id: "prepaid",
|
|
items: [prepaidItem],
|
|
});
|
|
|
|
const initialQuantity1 = 10 * billingUnits; // 120
|
|
const initialQuantity2 = 5 * billingUnits; // 60
|
|
const newQuantity1 = 20 * billingUnits; // 240
|
|
|
|
const {
|
|
autumnV1,
|
|
ctx: testContext,
|
|
entities,
|
|
} = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [product] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity1 },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity2 },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Preview the upgrade for entity 1
|
|
const preview = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
|
|
// Expected: +10 units * $8 = $80
|
|
expect(preview.total).toBe(10 * pricePerUnit);
|
|
|
|
// Execute the upgrade
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
|
|
// Verify entity 1 has new balance
|
|
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantity1,
|
|
});
|
|
|
|
// Verify entity 2 is unchanged
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: initialQuantity2,
|
|
});
|
|
|
|
// Verify invoice
|
|
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectLatestInvoiceCorrect({
|
|
customer,
|
|
productId: product.id,
|
|
amount: 10 * pricePerUnit,
|
|
});
|
|
|
|
// Verify subscription count
|
|
await expectSubToBeCorrect({
|
|
db: testContext.db,
|
|
customerId,
|
|
org: testContext.org,
|
|
env: testContext.env,
|
|
subCount: 1,
|
|
});
|
|
});
|
|
|
|
// Test 2: Basic Multi-Entity Decrease
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: entity 2 decreases quantity")}`, async () => {
|
|
const customerId = "multi-ent-qty-decrease";
|
|
const billingUnits = 12;
|
|
const pricePerUnit = 8;
|
|
|
|
const prepaidItem = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: pricePerUnit,
|
|
});
|
|
|
|
const product = products.base({
|
|
id: "prepaid",
|
|
items: [prepaidItem],
|
|
});
|
|
|
|
const initialQuantity1 = 10 * billingUnits; // 120
|
|
const initialQuantity2 = 15 * billingUnits; // 180
|
|
const newQuantity2 = 5 * billingUnits; // 60
|
|
|
|
const { autumnV1, entities } = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [product] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity1 },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity2 },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Preview the downgrade for entity 2
|
|
const preview = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity2 }],
|
|
});
|
|
|
|
// Expected: -10 units * $8 = -$80 (credit)
|
|
expect(preview.total).toBe(-10 * pricePerUnit);
|
|
|
|
// Execute the downgrade
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity2 }],
|
|
});
|
|
|
|
// Verify entity 2 has new balance
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantity2,
|
|
});
|
|
|
|
// Verify entity 1 is unchanged
|
|
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Messages,
|
|
balance: initialQuantity1,
|
|
});
|
|
|
|
// Verify credit invoice
|
|
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectLatestInvoiceCorrect({
|
|
customer,
|
|
productId: product.id,
|
|
amount: -10 * pricePerUnit,
|
|
});
|
|
});
|
|
|
|
// Test 3: Cross-Entity Mixed Changes
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: mixed changes across entities")}`, async () => {
|
|
const customerId = "multi-ent-qty-mixed";
|
|
const billingUnits = 10;
|
|
const pricePerUnit = 5;
|
|
|
|
const prepaidItem = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: pricePerUnit,
|
|
});
|
|
|
|
const product = products.base({
|
|
id: "prepaid",
|
|
items: [prepaidItem],
|
|
});
|
|
|
|
const initialQuantity1 = 10 * billingUnits; // 100
|
|
const initialQuantity2 = 20 * billingUnits; // 200
|
|
const newQuantity1 = 15 * billingUnits; // 150 (increase)
|
|
const newQuantity2 = 10 * billingUnits; // 100 (decrease)
|
|
|
|
const { autumnV1, entities } = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [product] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity1 },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity2 },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Preview and execute increase for entity 1
|
|
const preview1 = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
expect(preview1.total).toBe(5 * pricePerUnit); // +5 units * $5
|
|
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
|
|
// Preview and execute decrease for entity 2
|
|
const preview2 = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity2 }],
|
|
});
|
|
expect(preview2.total).toBe(-10 * pricePerUnit); // -10 units * $5
|
|
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity2 }],
|
|
});
|
|
|
|
// Verify final balances
|
|
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantity1,
|
|
});
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantity2,
|
|
});
|
|
|
|
// Verify customer has 4 invoices:
|
|
// 2 from initial attaches + 1 from increase + 1 from decrease (credit)
|
|
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectCustomerInvoiceCorrect({
|
|
customer,
|
|
count: 4,
|
|
});
|
|
});
|
|
|
|
// Test 4: OnDecrease.None Config - No Credit Invoice
|
|
// With OnDecrease.None, the balance changes immediately but NO credit invoice is created
|
|
// This differs from OnDecrease.ProrateImmediately which creates a credit invoice
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: OnDecrease.None creates no credit invoice")}`, async () => {
|
|
const customerId = "multi-ent-qty-no-proration";
|
|
const billingUnits = 100;
|
|
const pricePerUnit = 10;
|
|
|
|
const prepaidItem = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: pricePerUnit,
|
|
config: {
|
|
on_increase: OnIncrease.ProrateImmediately,
|
|
on_decrease: OnDecrease.None,
|
|
},
|
|
});
|
|
|
|
const product = products.base({
|
|
id: "prepaid",
|
|
items: [prepaidItem],
|
|
});
|
|
|
|
const initialQuantity1 = 4 * billingUnits; // 400
|
|
const initialQuantity2 = 3 * billingUnits; // 300
|
|
const newQuantity1 = 2 * billingUnits; // 200 (decrease)
|
|
|
|
const { autumnV1, entities } = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [product] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity1 },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantity2 },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
const beforeInvoices =
|
|
await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
const invoiceCountBefore = beforeInvoices.invoices?.length || 0;
|
|
|
|
// Preview the downgrade - should be $0 (no immediate credit)
|
|
const preview = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
|
|
expect(preview.total).toBe(0);
|
|
|
|
// Execute the downgrade
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantity1 }],
|
|
});
|
|
|
|
// Balance is updated immediately with OnDecrease.None
|
|
const entity1After = await autumnV1.entities.get(customerId, entities[0].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1After,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantity1,
|
|
});
|
|
|
|
// No new invoice should be created (the key behavior of OnDecrease.None)
|
|
const afterUpdate = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectCustomerInvoiceCorrect({
|
|
customer: afterUpdate,
|
|
count: invoiceCountBefore,
|
|
});
|
|
|
|
// Entity 2 should be unchanged
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: initialQuantity2,
|
|
});
|
|
});
|
|
|
|
// Test 5: Different Products Per Entity
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: different products per entity")}`, async () => {
|
|
const customerId = "multi-ent-qty-diff-products";
|
|
const billingUnits = 10;
|
|
|
|
const prepaidBase = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: 5,
|
|
});
|
|
|
|
const prepaidPro = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits,
|
|
price: 8,
|
|
});
|
|
|
|
const baseProduct = products.base({
|
|
id: "base-prepaid",
|
|
items: [prepaidBase],
|
|
});
|
|
|
|
const proProduct = products.pro({
|
|
id: "pro-prepaid",
|
|
items: [prepaidPro],
|
|
});
|
|
|
|
const initialQuantityBase = 5 * billingUnits; // 50
|
|
const initialQuantityPro = 10 * billingUnits; // 100
|
|
const newQuantityPro = 15 * billingUnits; // 150 (increase)
|
|
|
|
const { autumnV1, entities } = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [baseProduct, proProduct] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: baseProduct.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantityBase },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: proProduct.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{ feature_id: TestFeature.Messages, quantity: initialQuantityPro },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Preview upgrade for entity 2 (pro product)
|
|
const preview = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: proProduct.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantityPro }],
|
|
});
|
|
|
|
// +5 units * $8 = $40
|
|
expect(preview.total).toBe(5 * 8);
|
|
|
|
// Execute the upgrade
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[1].id,
|
|
product_id: proProduct.id,
|
|
options: [{ feature_id: TestFeature.Messages, quantity: newQuantityPro }],
|
|
});
|
|
|
|
// Verify entity 2 (pro) has new balance
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
await expectProductActive({
|
|
customer: entity2,
|
|
productId: proProduct.id,
|
|
});
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: newQuantityPro,
|
|
});
|
|
|
|
// Verify entity 1 (base) is unchanged
|
|
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
|
await expectProductActive({
|
|
customer: entity1,
|
|
productId: baseProduct.id,
|
|
});
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Messages,
|
|
balance: initialQuantityBase,
|
|
});
|
|
|
|
// Verify invoice
|
|
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectLatestInvoiceCorrect({
|
|
customer,
|
|
productId: proProduct.id,
|
|
amount: 5 * 8,
|
|
});
|
|
});
|
|
|
|
// Test 6: Multiple Features Per Entity
|
|
test.concurrent(`${chalk.yellowBright("multi-entity-quantity: multiple features per entity")}`, async () => {
|
|
const customerId = "multi-ent-qty-multi-feat";
|
|
const messagesBillingUnits = 10;
|
|
const wordsBillingUnits = 100;
|
|
const messagesPrice = 5;
|
|
const wordsPrice = 10;
|
|
|
|
const messagesItem = items.prepaid({
|
|
featureId: TestFeature.Messages,
|
|
billingUnits: messagesBillingUnits,
|
|
price: messagesPrice,
|
|
});
|
|
|
|
const wordsItem = items.prepaid({
|
|
featureId: TestFeature.Words,
|
|
billingUnits: wordsBillingUnits,
|
|
price: wordsPrice,
|
|
});
|
|
|
|
const product = products.base({
|
|
id: "multi-feature",
|
|
items: [messagesItem, wordsItem],
|
|
});
|
|
|
|
const { autumnV1, entities } = await initScenario({
|
|
customerId,
|
|
setup: [
|
|
s.customer({ paymentMethod: "success" }),
|
|
s.products({ list: [product] }),
|
|
s.entities({ count: 2, featureId: TestFeature.Users }),
|
|
],
|
|
actions: [
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 0,
|
|
options: [
|
|
{
|
|
feature_id: TestFeature.Messages,
|
|
quantity: 5 * messagesBillingUnits,
|
|
},
|
|
{ feature_id: TestFeature.Words, quantity: 2 * wordsBillingUnits },
|
|
],
|
|
}),
|
|
s.attach({
|
|
productId: product.id,
|
|
entityIndex: 1,
|
|
options: [
|
|
{
|
|
feature_id: TestFeature.Messages,
|
|
quantity: 10 * messagesBillingUnits,
|
|
},
|
|
{ feature_id: TestFeature.Words, quantity: 5 * wordsBillingUnits },
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Update entity 1: increase messages (+5 units), decrease words (-1 unit)
|
|
const preview = await autumnV1.subscriptions.previewUpdate({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [
|
|
{
|
|
feature_id: TestFeature.Messages,
|
|
quantity: 10 * messagesBillingUnits,
|
|
}, // +5 units
|
|
{ feature_id: TestFeature.Words, quantity: 1 * wordsBillingUnits }, // -1 unit
|
|
],
|
|
});
|
|
|
|
// Expected: +5 * $5 - 1 * $10 = $25 - $10 = $15
|
|
expect(preview.total).toBe(5 * messagesPrice - 1 * wordsPrice);
|
|
|
|
await autumnV1.subscriptions.update({
|
|
customer_id: customerId,
|
|
entity_id: entities[0].id,
|
|
product_id: product.id,
|
|
options: [
|
|
{
|
|
feature_id: TestFeature.Messages,
|
|
quantity: 10 * messagesBillingUnits,
|
|
},
|
|
{ feature_id: TestFeature.Words, quantity: 1 * wordsBillingUnits },
|
|
],
|
|
});
|
|
|
|
// Verify entity 1 features
|
|
const entity1 = await autumnV1.entities.get(customerId, entities[0].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Messages,
|
|
balance: 100,
|
|
});
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity1,
|
|
featureId: TestFeature.Words,
|
|
balance: 100,
|
|
});
|
|
|
|
// Verify entity 2 is unchanged
|
|
const entity2 = await autumnV1.entities.get(customerId, entities[1].id);
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Messages,
|
|
balance: 100,
|
|
});
|
|
await expectCustomerFeatureCorrect({
|
|
customer: entity2,
|
|
featureId: TestFeature.Words,
|
|
balance: 500,
|
|
});
|
|
|
|
// Verify invoice
|
|
const customer = await autumnV1.customers.get<ApiCustomerV3>(customerId);
|
|
expectLatestInvoiceCorrect({
|
|
customer,
|
|
productId: product.id,
|
|
amount: 5 * messagesPrice - 1 * wordsPrice,
|
|
});
|
|
});
|