Skip to content

Commit

Permalink
feat: add invoiceImmediately and disableProrations parameters to …
Browse files Browse the repository at this point in the history
…`updateSubscriptionItem` function. (#61)

* feat(orders): add setup_fee, setup_fee_usd, tax_inclusive and setup_fee_formatted

* feat(subscriptionInvoices): add tax_inclusive

* feat(subscriptionItems): add new invoiceImmediately and disableProrations parameters

* docs: update changeset
  • Loading branch information
keyding authored Feb 28, 2024
1 parent 27cbaf4 commit 93381d9
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-beds-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lemonsqueezy/lemonsqueezy.js": minor
---

Added invoice_immediately and disable_prorations parameters to Subscription item update object.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export type {
ListSubscriptionItems,
GetSubscriptionItemParams,
ListSubscriptionItemsParams,
UpdateSubscriptionItem,
} from "./subscriptionItems/types";
export {
getSubscriptionItem,
Expand Down
71 changes: 68 additions & 3 deletions src/subscriptionItems/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
convertIncludeToQueryString,
convertListParamsToQueryString,
requiredCheck,
convertKeys,
} from "../internal";

import type {
Expand All @@ -11,6 +12,7 @@ import type {
ListSubscriptionItemsParams,
SubscriptionItem,
SubscriptionItemCurrentUsage,
UpdateSubscriptionItem,
} from "./types";

/**
Expand Down Expand Up @@ -76,23 +78,86 @@ export function listSubscriptionItems(
*
* @param subscriptionItemId The given subscription item id.
* @param quantity The unit quantity of the subscription.
* @deprecated It will be removed with the next major version. Use the 'updateSubscriptionItem' parameter instead.
* @returns A subscription item object.
*/
export function updateSubscriptionItem(
subscriptionItemId: string | number,
quantity: number
): ReturnType<typeof _updateSubscriptionItem>;

/**
* Update a subscription item.
*
* Note: this endpoint is only used with quantity-based billing.
* If the related subscription's product/variant has usage-based billing
* enabled, this endpoint will return a `422 Unprocessable Entity` response.
*
* @param subscriptionItemId The given subscription item id.
* @param updateSubscriptionItem (Required) Update subscription item info.
* @param updateSubscriptionItem.quantity (Required) The unit quantity of the subscription.
* @param [updateSubscriptionItem.invoiceImmediately] (Optional) If `true`, any updates to the subscription will be charged immediately. A new prorated invoice will be generated and payment attempted. Defaults to `false`. Note that this will be overridden by the `disable_prorations` option if used.
* @param [updateSubscriptionItem.disableProrations] (Optional) If `true`, no proration will be charged and the customer will simply be charged the new price at the next renewal. Defaults to `false`. Note that this will override the `invoice_immediately` option if used.
* @returns A subscription item object.
*/
export function updateSubscriptionItem(
subscriptionItemId: string | number,
updateSubscriptionItem: UpdateSubscriptionItem
): ReturnType<typeof _updateSubscriptionItem>;

/**
* Update a subscription item.
*
* Note: this endpoint is only used with quantity-based billing.
* If the related subscription's product/variant has usage-based billing
* enabled, this endpoint will return a `422 Unprocessable Entity` response.
*
* @param subscriptionItemId The given subscription item id.
* @param updateSubscriptionItem (Required) Update subscription item info.
* @param updateSubscriptionItem.quantity (Required) The unit quantity of the subscription.
* @param [updateSubscriptionItem.invoiceImmediately] (Optional) If `true`, any updates to the subscription will be charged immediately. A new prorated invoice will be generated and payment attempted. Defaults to `false`. Note that this will be overridden by the `disable_prorations` option if used.
* @param [updateSubscriptionItem.disableProrations] (Optional) If `true`, no proration will be charged and the customer will simply be charged the new price at the next renewal. Defaults to `false`. Note that this will override the `invoice_immediately` option if used.
* @returns A subscription item object.
*/
export function updateSubscriptionItem(
subscriptionItemId: string | number,
updateSubscriptionItem: number | UpdateSubscriptionItem
) {
return _updateSubscriptionItem(subscriptionItemId, updateSubscriptionItem);
}

async function _updateSubscriptionItem(
subscriptionItemId: string | number,
updateSubscriptionItem: number | UpdateSubscriptionItem
) {
requiredCheck({ subscriptionItemId });

let attributes;
if (typeof updateSubscriptionItem === "number") {
attributes = {
quantity: updateSubscriptionItem,
};
} else {
const {
quantity,
invoiceImmediately = false,
disableProrations = false,
} = updateSubscriptionItem;
attributes = convertKeys({
quantity,
invoiceImmediately,
disableProrations,
});
}

return $fetch<SubscriptionItem>({
path: `/v1/subscription-items/${subscriptionItemId}`,
method: "PATCH",
body: {
data: {
type: "subscription-items",
id: subscriptionItemId.toString(),
attributes: {
quantity,
},
attributes,
},
},
});
Expand Down
17 changes: 17 additions & 0 deletions src/subscriptionItems/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,20 @@ export type ListSubscriptionItems = LemonSqueezyResponse<
Pick<Meta, "page">,
Pick<Links, "first" | "last">
>;
export type UpdateSubscriptionItem = {
/**
* The unit quantity of the subscription.
*/
quantity: number;
/**
* If `true`, any updates to the subscription will be charged immediately. A new prorated invoice will be generated and payment attempted. Defaults to `false`. Note that this will be overridden by the `disable_prorations` option if used.
*
* [Read about proration in the Developer Guide.](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#handling-proration)
*/
invoiceImmediately?: boolean;
/**
* If `true`, no proration will be charged and the customer will simply be charged the new price at the next renewal. Defaults to `false`. Note that this will override the `invoice_immediately` option if used.
* [Read about proration in the Developer Guide.](https://docs.lemonsqueezy.com/guides/developer-guide/managing-subscriptions#handling-proration)
*/
disableProrations?: boolean;
};
59 changes: 58 additions & 1 deletion test/subscriptionItems/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ describe(`Retrieve a subscription item's current usage`, () => {
describe("Update a subscription item", () => {
it("Throw an error about a parameter that must be provided", async () => {
try {
await updateSubscriptionItem("", {} as any);
await updateSubscriptionItem("", 10);
} catch (error) {
expect((error as Error).message).toMatch(
"Please provide the required parameter:"
Expand All @@ -315,6 +315,7 @@ describe("Update a subscription item", () => {
statusCode,
data: _data,
} = await updateSubscriptionItem(noUsageBasedSubscriptionItemId, 10);

expect(statusCode).toEqual(200);
expect(error).toBeNull();
expect(_data).toBeDefined();
Expand Down Expand Up @@ -360,4 +361,60 @@ describe("Update a subscription item", () => {
expect(Object.keys(relationships).length).toEqual(relationshipItems.length);
for (const item of relationshipItems) expect(item.links).toBeDefined();
});

it("Should return a updated subscription item object with `invoice_immediately` is `true`", async () => {
const {
error,
statusCode,
data: _data,
} = await updateSubscriptionItem(noUsageBasedSubscriptionItemId, {
quantity: 20,
invoiceImmediately: true,
});

expect(statusCode).toEqual(200);
expect(error).toBeNull();
expect(_data).toBeDefined();

const { links, data } = _data!;
expect(data).toBeDefined();
expect(links.self).toEqual(
`${API_BASE_URL}${PATH}${noUsageBasedSubscriptionItemId}`
);

const { id, type, attributes, relationships } = data;
expect(id).toEqual(noUsageBasedSubscriptionItemId.toString());
expect(type).toEqual(DATA_TYPE);
expect(attributes).toBeDefined();
expect(relationships).toBeDefined();

const {
subscription_id,
price_id,
quantity,
is_usage_based,
created_at,
updated_at,
} = attributes;
const items = [
subscription_id,
price_id,
quantity,
is_usage_based,
created_at,
updated_at,
];
expect(quantity).toEqual(20);
expect(Object.keys(attributes).length).toEqual(items.length);
for (const item of items) expect(item).toBeDefined();

const {
subscription,
price,
"usage-records": usageRecords,
} = relationships;
const relationshipItems = [subscription, price, usageRecords];
expect(Object.keys(relationships).length).toEqual(relationshipItems.length);
for (const item of relationshipItems) expect(item.links).toBeDefined();
});
});

0 comments on commit 93381d9

Please sign in to comment.