From eebbeaaa8f8e455590e61f453939b5d71c8a4eb4 Mon Sep 17 00:00:00 2001 From: Carsten Lebek <59960385+carstenlebek@users.noreply.github.com> Date: Fri, 30 Jun 2023 13:04:43 +0200 Subject: [PATCH 1/7] fix: types --- src/modules/checkout/checkout.types.ts | 76 ++++++++++++++------------ 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/src/modules/checkout/checkout.types.ts b/src/modules/checkout/checkout.types.ts index d27a04a..e7fa4bf 100644 --- a/src/modules/checkout/checkout.types.ts +++ b/src/modules/checkout/checkout.types.ts @@ -11,19 +11,19 @@ export interface LemonsqueezyBillingAddress { * * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 */ - country: string; + country?: string; /** * A pre-filled billing address zip/postal code */ - zip: string; + zip?: string; } export interface LemonsqueezyCheckoutData { - billing_address: LemonsqueezyBillingAddress; + billing_address?: LemonsqueezyBillingAddress; /** * An object containing any custom data to be passed to the checkout */ - custom?: Array; + custom?: Record, /** * A pre-filled discount code */ @@ -31,11 +31,11 @@ export interface LemonsqueezyCheckoutData { /** * A pre-filled email address */ - email: string; + email?: string; /** * A pre-filled name */ - name: string; + name?: string; /** * A pre-filled tax number */ @@ -43,36 +43,40 @@ export interface LemonsqueezyCheckoutData { } export interface LemonsqueezyCheckoutOptions { - /** - * A custom hex color to use for the checkout button - */ - button_color?: `#${string}`; - /** - * If `true`, use the dark theme - */ - dark?: boolean; - /** - * If `false`, hide the product description - */ - desc?: boolean; - /** - * If `false`, hide the discount code field - */ - discount?: boolean; - /** - * If `true`, show the checkout overlay - * - * @docs https://docs.lemonsqueezy.com/help/checkout/checkout-overlay - */ - embed?: boolean; - /** - * If `false`, hide the store logo - */ - logo?: boolean; - /** - * If `false`, hide the product media - */ - media?: boolean; + /** + * A custom hex color to use for the checkout button + */ + button_color?: `#${string}`; + /** + * If `true`, use the dark theme + */ + dark?: boolean; + /** + * If `false`, hide the product description + */ + desc?: boolean; + /** + * If `false`, hide the discount code field + */ + discount?: boolean; + /** + * If `true`, show the checkout overlay + * + * @docs https://docs.lemonsqueezy.com/help/checkout/checkout-overlay + */ + embed?: boolean; + /** + * If `false`, hide the store logo + */ + logo?: boolean; + /** + * If `false`, hide the product media + */ + media?: boolean; + /** + * If false, hide the "You will be charged..." subscription preview text + */ + subscription_preview?: boolean; } export interface LemonsqueezyCheckoutPreview { From ba951950a3b04fbbdadd3e61c66ce11e2e3bf789 Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Fri, 30 Jun 2023 13:10:06 +0200 Subject: [PATCH 2/7] fix: types --- src/modules/checkout/checkout.types.ts | 456 ++++++++++++------------- 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/src/modules/checkout/checkout.types.ts b/src/modules/checkout/checkout.types.ts index e7fa4bf..1f9b11c 100644 --- a/src/modules/checkout/checkout.types.ts +++ b/src/modules/checkout/checkout.types.ts @@ -1,45 +1,45 @@ import type { - BaseLemonsqueezyResponse, - LemonsqueezyDataType, - PaginatedBaseLemonsqueezyResponse, - SharedLemonsqueezyOptions, -} from "~/shared"; + BaseLemonsqueezyResponse, + LemonsqueezyDataType, + PaginatedBaseLemonsqueezyResponse, + SharedLemonsqueezyOptions, +} from '~/shared'; export interface LemonsqueezyBillingAddress { - /** - * A pre-filled billing address country in a ISO 3166-1 alpha-2 format - * - * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - */ - country?: string; - /** - * A pre-filled billing address zip/postal code - */ - zip?: string; + /** + * A pre-filled billing address country in a ISO 3166-1 alpha-2 format + * + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country?: string; + /** + * A pre-filled billing address zip/postal code + */ + zip?: string; } export interface LemonsqueezyCheckoutData { - billing_address?: LemonsqueezyBillingAddress; - /** - * An object containing any custom data to be passed to the checkout - */ - custom?: Record, - /** - * A pre-filled discount code - */ - discount_code?: string; - /** - * A pre-filled email address - */ - email?: string; - /** - * A pre-filled name - */ - name?: string; - /** - * A pre-filled tax number - */ - tax_number?: string; + billing_address?: LemonsqueezyBillingAddress; + /** + * An object containing any custom data to be passed to the checkout + */ + custom?: Record; + /** + * A pre-filled discount code + */ + discount_code?: string; + /** + * A pre-filled email address + */ + email?: string; + /** + * A pre-filled name + */ + name?: string; + /** + * A pre-filled tax number + */ + tax_number?: string; } export interface LemonsqueezyCheckoutOptions { @@ -76,227 +76,227 @@ export interface LemonsqueezyCheckoutOptions { /** * If false, hide the "You will be charged..." subscription preview text */ - subscription_preview?: boolean; + subscription_preview?: boolean; } export interface LemonsqueezyCheckoutPreview { - currency_rate: number; - currency: string; - discount_total_formatted: string; - discount_total_usd: number; - discount_total: number; - subtotal_formatted: string; - subtotal_usd: number; - subtotal: number; - tax_formatted: string; - tax_usd: number; - tax: number; - total_formatted: string; - total_usd: number; - total: number; + currency_rate: number; + currency: string; + discount_total_formatted: string; + discount_total_usd: number; + discount_total: number; + subtotal_formatted: string; + subtotal_usd: number; + subtotal: number; + tax_formatted: string; + tax_usd: number; + tax: number; + total_formatted: string; + total_usd: number; + total: number; } export interface LemonsqueezyProductOptions { - /** - * A custom description for the product - */ - description: string; - /** - * An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled - */ - enabled_variants?: Array; - /** - * An array of image URLs to use as the product's media - */ - media?: Array; - /** - * A custom name for the product - */ - name: string; - /** - * A custom text to use for the order receipt email button - */ - receipt_button_text: string; - /** - * A custom URL to use for the order receipt email button - */ - receipt_link_url: string; - /** - * A custom thank you note to use for the order receipt email - */ - receipt_thank_you_note: string; - /** - * A custom URL to redirect to after a successful purchase - */ - redirect_url: string; + /** + * A custom description for the product + */ + description?: string; + /** + * An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled + */ + enabled_variants?: Array; + /** + * An array of image URLs to use as the product's media + */ + media?: Array; + /** + * A custom name for the product + */ + name?: string; + /** + * A custom text to use for the order receipt email button + */ + receipt_button_text?: string; + /** + * A custom URL to use for the order receipt email button + */ + receipt_link_url?: string; + /** + * A custom thank you note to use for the order receipt email + */ + receipt_thank_you_note?: string; + /** + * A custom URL to redirect to after a successful purchase + */ + redirect_url?: string; } /** * @docs https://docs.lemonsqueezy.com/api/checkouts#the-checkout-object */ export interface LemonsqueezyCheckout { - attributes: { - /** - * An object containing any prefill or custom data to be used in the checkout - * - * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields - * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data - */ - checkout_data: LemonsqueezyCheckoutData; - /** - * An object containing checkout options for this checkout - */ - checkout_options: LemonsqueezyCheckoutOptions; - /** - * An ISO-8601 formatted date-time string indicating when the object was created - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - created_at: Date; - /** - * If the value is not `null`, this represents a positive integer in cents representing the custom price of the variant - */ - custom_price: number | null; - /** - * An ISO-8601 formatted date-time string indicating when the checkout expires - * - * Can be `null` if the checkout is perpetual - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - expires_at: Date | null; - preview: LemonsqueezyCheckoutPreview; - /** - * An object containing any overridden product options for this checkout - */ - product_options: LemonsqueezyProductOptions; - /** - * The ID of the store this checkout belongs to - */ - store_id: number; - /** - * A boolean indicating if the returned checkout object was created within test mode - */ - test_mode: boolean; - /** - * An ISO-8601 formatted date-time string indicating when the object was last updated - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - updated_at: Date; - /** - * The unique URL to access the checkout - * - * Note: for security reasons, download URLs are signed - * - * If the checkout `expires_at` is set, the URL will expire after the specified time - */ - url: string; - /** - * The ID of the variant associated with this checkout - */ - variant_id: number; - }; - type: LemonsqueezyDataType.checkouts; - id: string; + attributes: { + /** + * An object containing any prefill or custom data to be used in the checkout + * + * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields + * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data + */ + checkout_data: LemonsqueezyCheckoutData; + /** + * An object containing checkout options for this checkout + */ + checkout_options: LemonsqueezyCheckoutOptions; + /** + * An ISO-8601 formatted date-time string indicating when the object was created + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + created_at: Date; + /** + * If the value is not `null`, this represents a positive integer in cents representing the custom price of the variant + */ + custom_price: number | null; + /** + * An ISO-8601 formatted date-time string indicating when the checkout expires + * + * Can be `null` if the checkout is perpetual + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + expires_at: Date | null; + preview: LemonsqueezyCheckoutPreview; + /** + * An object containing any overridden product options for this checkout + */ + product_options: LemonsqueezyProductOptions; + /** + * The ID of the store this checkout belongs to + */ + store_id: number; + /** + * A boolean indicating if the returned checkout object was created within test mode + */ + test_mode: boolean; + /** + * An ISO-8601 formatted date-time string indicating when the object was last updated + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + updated_at: Date; + /** + * The unique URL to access the checkout + * + * Note: for security reasons, download URLs are signed + * + * If the checkout `expires_at` is set, the URL will expire after the specified time + */ + url: string; + /** + * The ID of the variant associated with this checkout + */ + variant_id: number; + }; + type: LemonsqueezyDataType.checkouts; + id: string; } export interface CreateCheckoutOptions extends SharedLemonsqueezyOptions { - /** - * An object containing any prefill or custom data to be used in the checkout - * - * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields - * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data - */ - checkout_data?: LemonsqueezyCheckoutData; - /** - * An object containing checkout options for this checkout - */ - checkout_options?: LemonsqueezyCheckoutOptions; - /** - * A positive integer in cents representing the custom price of the variant. - * - * Note: If the product purchased is a subscription, this custom price is used - * for all renewal payments. If the subscription's variant changes in the - * future (i.e. the customer is moved to a different subscription "tier") the - * new variant's price will be used from that moment forward. - */ - custom_price: number; - /** - * An ISO-8601 formatted date-time string indicating when the checkout expires - * - * Can be `null` if the checkout is perpetual - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - expires_at?: Date | null; - /** - * A boolean indicating whether to return a preview of the checkout. - * - * If `true`, the checkout will include a `preview` object with the checkout preview data. - */ - preview?: boolean; - /** - * An object containing any overridden product options for this checkout. - */ - product_options?: LemonsqueezyProductOptions; - /** - * The ID of the store this checkout belongs to. - */ - store: string; - /** - * The ID of the variant associated with this checkout. - * - * Note: by default, all variants of the related product will be shown in the checkout, with - * your selected variant highlighted. If you want hide to other variants, you can utilise - * the `product_options.enabled_variants` option to determine which variant(s) are - * displayed in the checkout. - */ - variant: string; + /** + * An object containing any prefill or custom data to be used in the checkout + * + * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields + * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data + */ + checkout_data?: LemonsqueezyCheckoutData; + /** + * An object containing checkout options for this checkout + */ + checkout_options?: LemonsqueezyCheckoutOptions; + /** + * A positive integer in cents representing the custom price of the variant. + * + * Note: If the product purchased is a subscription, this custom price is used + * for all renewal payments. If the subscription's variant changes in the + * future (i.e. the customer is moved to a different subscription "tier") the + * new variant's price will be used from that moment forward. + */ + custom_price: number; + /** + * An ISO-8601 formatted date-time string indicating when the checkout expires + * + * Can be `null` if the checkout is perpetual + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + expires_at?: Date | null; + /** + * A boolean indicating whether to return a preview of the checkout. + * + * If `true`, the checkout will include a `preview` object with the checkout preview data. + */ + preview?: boolean; + /** + * An object containing any overridden product options for this checkout. + */ + product_options?: LemonsqueezyProductOptions; + /** + * The ID of the store this checkout belongs to. + */ + store: string; + /** + * The ID of the variant associated with this checkout. + * + * Note: by default, all variants of the related product will be shown in the checkout, with + * your selected variant highlighted. If you want hide to other variants, you can utilise + * the `product_options.enabled_variants` option to determine which variant(s) are + * displayed in the checkout. + */ + variant: string; } export interface CreateCheckoutBody { - data: { - type: LemonsqueezyDataType.checkouts; - attributes: Omit; - relationships: { - store: { - data: { - id: string; - type: LemonsqueezyDataType.stores; - }; - }; - variant: { - data: { - id: string; - type: LemonsqueezyDataType.variants; - }; - }; - }; - }; + data: { + type: LemonsqueezyDataType.checkouts; + attributes: Omit; + relationships: { + store: { + data: { + id: string; + type: LemonsqueezyDataType.stores; + }; + }; + variant: { + data: { + id: string; + type: LemonsqueezyDataType.variants; + }; + }; + }; + }; } export type CreateCheckoutResult = - BaseLemonsqueezyResponse; + BaseLemonsqueezyResponse; export interface ListAllCheckoutsOptions extends SharedLemonsqueezyOptions { - /** - * Only return checkouts belonging to the store with this ID - */ - storeId?: string; - /** - * Only return checkouts belonging to the variant with this ID - */ - variantId?: string; + /** + * Only return checkouts belonging to the store with this ID + */ + storeId?: string; + /** + * Only return checkouts belonging to the variant with this ID + */ + variantId?: string; } export type ListAllCheckoutsResult = PaginatedBaseLemonsqueezyResponse< - Array + Array >; export interface RetrieveCheckoutOptions extends SharedLemonsqueezyOptions { - id: string; + id: string; } export type RetrieveCheckoutResult = - BaseLemonsqueezyResponse; + BaseLemonsqueezyResponse; From 44bb1111a3c749e35b36a86b6d6e1fec301a355d Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Fri, 30 Jun 2023 13:38:47 +0200 Subject: [PATCH 3/7] feat: add customer module --- src/client/client.class.ts | 1180 ++++++++++++----------- src/index.ts | 56 +- src/modules/customer/README.md | 28 + src/modules/customer/customer.action.ts | 56 ++ src/modules/customer/customer.test.ts | 38 + src/modules/customer/customer.types.ts | 103 ++ src/modules/customer/index.ts | 16 + src/modules/index.ts | 27 +- src/shared/shared.types.ts | 131 +-- 9 files changed, 960 insertions(+), 675 deletions(-) create mode 100644 src/modules/customer/README.md create mode 100644 src/modules/customer/customer.action.ts create mode 100644 src/modules/customer/customer.test.ts create mode 100644 src/modules/customer/customer.types.ts create mode 100644 src/modules/customer/index.ts diff --git a/src/client/client.class.ts b/src/client/client.class.ts index c5ea0f0..d3e3a03 100644 --- a/src/client/client.class.ts +++ b/src/client/client.class.ts @@ -1,576 +1,616 @@ import { - createCheckout, - getUser, - listAllCheckouts, - listAllDiscounts, - listAllFiles, - listAllLicenseKeyInstances, - listAllLicenseKeys, - listAllOrderItems, - listAllOrders, - listAllProducts, - listAllStores, - listAllSubscriptionInvoices, - listAllSubscriptions, - listAllVariants, - retrieveCheckout, - retrieveDiscount, - retrieveFile, - retrieveLicenseKey, - retrieveLicenseKeyInstance, - retrieveOrder, - retrieveOrderItem, - retrieveProduct, - retrieveStore, - retrieveSubscription, - retrieveSubscriptionInvoice, - retrieveVariant, - updateSubscription, -} from "~/modules"; + createCheckout, + getUser, + listAllCheckouts, + listAllCustomers, + listAllDiscounts, + listAllFiles, + listAllLicenseKeyInstances, + listAllLicenseKeys, + listAllOrderItems, + listAllOrders, + listAllProducts, + listAllStores, + listAllSubscriptionInvoices, + listAllSubscriptions, + listAllVariants, + retrieveCheckout, + retrieveCustomer, + retrieveDiscount, + retrieveFile, + retrieveLicenseKey, + retrieveLicenseKeyInstance, + retrieveOrder, + retrieveOrderItem, + retrieveProduct, + retrieveStore, + retrieveSubscription, + retrieveSubscriptionInvoice, + retrieveVariant, + updateSubscription, +} from '~/modules'; import type { - CreateCheckoutOptions, - GetUserOptions, - ListAllCheckoutsOptions, - ListAllDiscountsOptions, - ListAllFilesOptions, - ListAllLicenseKeyInstancesOptions, - ListAllLicenseKeysOptions, - ListAllOrderItemsOptions, - ListAllOrdersOptions, - ListAllProductsOptions, - ListAllStoresOptions, - ListAllSubscriptionInvoicesOptions, - ListAllSubscriptionsOptions, - ListAllVariantsOptions, - RetrieveCheckoutOptions, - RetrieveDiscountOptions, - RetrieveFileOptions, - RetrieveLicenseKeyInstanceOptions, - RetrieveLicenseKeyOptions, - RetrieveOrderItemOptions, - RetrieveOrderOptions, - RetrieveProductOptions, - RetrieveStoreOptions, - RetrieveSubscriptionInvoiceOptions, - RetrieveSubscriptionOptions, - RetrieveVariantOptions, - UpdateSubscriptionOptions, -} from "~/modules"; + CreateCheckoutOptions, + GetUserOptions, + ListAllCheckoutsOptions, + ListAllCustomersOptions, + ListAllDiscountsOptions, + ListAllFilesOptions, + ListAllLicenseKeyInstancesOptions, + ListAllLicenseKeysOptions, + ListAllOrderItemsOptions, + ListAllOrdersOptions, + ListAllProductsOptions, + ListAllStoresOptions, + ListAllSubscriptionInvoicesOptions, + ListAllSubscriptionsOptions, + ListAllVariantsOptions, + RetrieveCheckoutOptions, + RetrieveCustomerOptions, + RetrieveDiscountOptions, + RetrieveFileOptions, + RetrieveLicenseKeyInstanceOptions, + RetrieveLicenseKeyOptions, + RetrieveOrderItemOptions, + RetrieveOrderOptions, + RetrieveProductOptions, + RetrieveStoreOptions, + RetrieveSubscriptionInvoiceOptions, + RetrieveSubscriptionOptions, + RetrieveVariantOptions, + UpdateSubscriptionOptions, +} from '~/modules'; export class LemonsqueezyClient { - private _apiKey: string; - - constructor(apiKey: string) { - this._apiKey = apiKey; - } - - /** - * Get User - * - * @description Retrieves the currently authenticated user - * - * @docs https://docs.lemonsqueezy.com/api/users#retrieve-the-authenticated-user - * - * @param {Object} [options] - * - * @returns A user object - */ - public async getUser(options: GetUserOptions = {}) { - return getUser({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve store - * - * @description Retrieves the store with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/stores#retrieve-a-store - * - * @param {String} options.id - The ID of the store to retrieve - * - * @returns A store object - */ - public async retrieveStore(options: RetrieveStoreOptions) { - return retrieveStore({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all stores - * - * @description Returns a paginated list of stores - * - * @docs https://docs.lemonsqueezy.com/api/stores#list-all-stores - * - * @param {Object} [options] - * - * @returns Returns a paginated list of `store` objects ordered by name - */ - public async listAllStores(options: ListAllStoresOptions = {}) { - return listAllStores({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve product - * - * @description Retrieves the product with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/products#retrieve-a-product - * - * @param {String} options.id - The ID of the product to retrieve - * - * @returns A product object - */ - public async retrieveProduct(options: RetrieveProductOptions) { - return retrieveProduct({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all products - * - * @description Returns a paginated list of products - * - * @docs https://docs.lemonsqueezy.com/api/products#list-all-products - * - * @param {Object} [options] - * - * @returns Returns a paginated list of product objects ordered by `name` - */ - public async listAllProducts(options: ListAllProductsOptions = {}) { - return listAllProducts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve variant - * - * @description Retrieves the variant with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/variants#retrieve-a-variant - * - * @param {String} options.id - The ID of the variant to retrieve - * - * @returns A variant object - */ - public async retrieveVariant(options: RetrieveVariantOptions) { - return retrieveVariant({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all variants - * - * @description Returns a paginated list of variants - * - * @docs https://docs.lemonsqueezy.com/api/variants#list-all-variants - * - * @param {Object} [options] - * - * @returns Returns a paginated list of variant objects ordered by sort - */ - public async listAllVariants(options: ListAllVariantsOptions = {}) { - return listAllVariants({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve file - * - * @description Retrieves the file with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/files#retrieve-a-file - * - * @param {String} options.id - The ID of the file to retrieve - * - * @returns A file object - */ - public async retrieveFile(options: RetrieveFileOptions) { - return retrieveFile({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all files - * - * @description Returns a paginated list of files - * - * @docs https://docs.lemonsqueezy.com/api/files#list-all-files - * - * @param {Object} [options] - * - * @returns Returns a paginated list of file objects ordered by `sort` - */ - public async listAllFiles(options: ListAllFilesOptions = {}) { - return listAllFiles({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve order - * - * @description Retrieves the order with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/orders#retrieve-an-order - * - * @param {String} options.id - The ID of the order to retrieve - * - * @returns A order object - */ - public async retrieveOrder(options: RetrieveOrderOptions) { - return retrieveOrder({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all orders - * - * @description Returns a paginated list of orders - * - * @docs https://docs.lemonsqueezy.com/api/orders#list-all-orders - * - * @param {Object} [options] - * - * @returns Returns a paginated list of file objects ordered by `sort` - */ - public async listAllOrders(options: ListAllOrdersOptions = {}) { - return listAllOrders({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve order item - * - * @description Retrieves the order item with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/order-items#retrieve-an-order-item - * - * @param {String} options.id - The ID of the order item to retrieve - * - * @returns A order item object - */ - public async retrieveOrderItem(options: RetrieveOrderItemOptions) { - return retrieveOrderItem({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all order items - * - * @description Returns a paginated list of order items - * - * @docs https://docs.lemonsqueezy.com/api/order-items#list-all-order-items - * - * @param {Object} [options] - * - * @returns Returns a paginated list of order item objects ordered by `id` - */ - public async listAllOrderItems(options: ListAllOrderItemsOptions = {}) { - return listAllOrderItems({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve subscription - * - * @description Retrieves the subscription with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#retrieve-a-subscription - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription object - */ - public async retrieveSubscription(options: RetrieveSubscriptionOptions) { - return retrieveSubscription({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all subscriptions - * - * @description Returns a paginated list of subscriptions - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#list-all-subscriptions - * - * @param {Object} [options] - * - * @returns Returns a paginated list of subscription objects ordered by `created_at` (descending) - */ - public async listAllSubscriptions(options: ListAllSubscriptionsOptions = {}) { - return listAllSubscriptions({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Update subscription - * - * @description Update an existing subscription to specific parameters. With this endpoint, you can: - * - * - Upgrade & Downgrade a subscripion to a different Product or Variant - * - Change payment pause collection behaviour - * - Update the billing date for when payments are collected - * - * When changing the plan of a subscription, we prorate the charge for the next billing cycle. - * For example, if a customer buys your product on April 1st for $50, they'll be charged $50 immediately. - * If on April 15th they upgrade to your $100 product, on May 1st they'll be charged $125. - * This is made up of $100 for renewing, $50 of used time on your upgraded $100 plan from April 15th - May 1st, and then a credited -$25 for unused time on your $50 plan. - * - * If downgrading a subscription, we'll issue a credit which is then applied on the next invoice. - * - * Changing a subscription plan may change the billing date or charge immediately if: - * - * - The variant has a different billing cycle (from monthly to yearly, etc) - * - The subscription is no longer free, or is now free - * - A trial starts or ends - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#update-a-subscription - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription object - */ - public async updateSubscription(options: UpdateSubscriptionOptions) { - return updateSubscription({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve discount - * - * @description Retrieves the discount with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/discounts#retrieve-a-discount - * - * @param {String} options.id - The ID of the discount to retrieve - * - * @returns A discount object - */ - public async retrieveDiscount(options: RetrieveDiscountOptions) { - return retrieveDiscount({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all discounts - * - * @description Returns a paginated list of discounts - * - * @docs https://docs.lemonsqueezy.com/api/discounts#list-all-discounts - * - * @param {Object} [options] - * - * @returns Returns a paginated list of discount objects ordered by `created_at` - */ - public async listAllDiscounts(options: ListAllDiscountsOptions = {}) { - return listAllDiscounts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve license key - * - * @description Retrieves the license key with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/license-keys#retrieve-a-license-key - * - * @param {String} options.id - The ID of the license key to retrieve - * - * @returns A license key object - */ - public async retrieveLicenseKey(options: RetrieveLicenseKeyOptions) { - return retrieveLicenseKey({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all license keys - * - * @description Returns a paginated list of license keys - * - * @docs https://docs.lemonsqueezy.com/api/license-keys#list-all-license-keys - * - * @param {Object} [options] - * - * @returns Returns a paginated list of license key objects ordered by `id` - */ - public async listAllLicenseKeys(options: ListAllLicenseKeysOptions = {}) { - return listAllLicenseKeys({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve license key instance - * - * @description Retrieves the license key instance with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/license-key-instances#retrieve-a-license-key-instance - * - * @param {String} options.id - The ID of the license key instance to retrieve - * - * @returns A license key instance object - */ - public async retrieveLicenseKeyInstance( - options: RetrieveLicenseKeyInstanceOptions - ) { - return retrieveLicenseKeyInstance({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all license key instances - * - * @description Returns a paginated list of license key instances - * - * @docs https://docs.lemonsqueezy.com/api/license-key-instances#list-all-license-key-instances - * - * @param {Object} [options] - * - * @returns Returns a paginated list of license key instance objects ordered by `id` - */ - public async listAllLicenseKeyInstances( - options: ListAllLicenseKeyInstancesOptions = {} - ) { - return listAllLicenseKeyInstances({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve checkout - * - * @description Retrieves the checkout with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#retrieve-a-checkout - * - * @param {String} options.id - The ID of the checkout to retrieve - * - * @returns A checkout object - */ - public async retrieveCheckout(options: RetrieveCheckoutOptions) { - return retrieveCheckout({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all checkouts - * - * @description Returns a paginated list of checkouts - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#list-all-checkouts - * - * @param {Object} [options] - * - * @returns Returns a paginated list of checkout objects ordered by `created_at` (descending) - */ - public async listAllCheckouts(options: ListAllCheckoutsOptions = {}) { - return listAllCheckouts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Create checkout - * - * @description Create a custom checkout. Use this endpoint to create a unique checkout URL for a specific variant - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout - * - * @returns A checkout object - */ - public async createCheckout(options: CreateCheckoutOptions) { - return createCheckout({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve subscription invoice - * - * @description Retrieves a subscription invoice with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#retrieve-a-subscription-invoice - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription invoice object - */ - public async retrieveSubscriptionInvoice( - options: RetrieveSubscriptionInvoiceOptions - ) { - return retrieveSubscriptionInvoice({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all subscription invoices - * - * @description Returns a paginated list of subscriptions - * - * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#list-all-subscription-invoices - * - * @param {Object} [options] - * - * @returns Returns a paginated list of subscription invoice objects. - */ - public async listAllSubscriptionInvoices( - options: ListAllSubscriptionInvoicesOptions = {} - ) { - return listAllSubscriptionInvoices({ - apiKey: this._apiKey, - ...options, - }); - } + private _apiKey: string; + + constructor(apiKey: string) { + this._apiKey = apiKey; + } + + /** + * Get User + * + * @description Retrieves the currently authenticated user + * + * @docs https://docs.lemonsqueezy.com/api/users#retrieve-the-authenticated-user + * + * @param {Object} [options] + * + * @returns A user object + */ + public async getUser(options: GetUserOptions = {}) { + return getUser({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve store + * + * @description Retrieves the store with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/stores#retrieve-a-store + * + * @param {String} options.id - The ID of the store to retrieve + * + * @returns A store object + */ + public async retrieveStore(options: RetrieveStoreOptions) { + return retrieveStore({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all stores + * + * @description Returns a paginated list of stores + * + * @docs https://docs.lemonsqueezy.com/api/stores#list-all-stores + * + * @param {Object} [options] + * + * @returns Returns a paginated list of `store` objects ordered by name + */ + public async listAllStores(options: ListAllStoresOptions = {}) { + return listAllStores({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve product + * + * @description Retrieves the product with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/products#retrieve-a-product + * + * @param {String} options.id - The ID of the product to retrieve + * + * @returns A product object + */ + public async retrieveProduct(options: RetrieveProductOptions) { + return retrieveProduct({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all products + * + * @description Returns a paginated list of products + * + * @docs https://docs.lemonsqueezy.com/api/products#list-all-products + * + * @param {Object} [options] + * + * @returns Returns a paginated list of product objects ordered by `name` + */ + public async listAllProducts(options: ListAllProductsOptions = {}) { + return listAllProducts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve variant + * + * @description Retrieves the variant with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/variants#retrieve-a-variant + * + * @param {String} options.id - The ID of the variant to retrieve + * + * @returns A variant object + */ + public async retrieveVariant(options: RetrieveVariantOptions) { + return retrieveVariant({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all variants + * + * @description Returns a paginated list of variants + * + * @docs https://docs.lemonsqueezy.com/api/variants#list-all-variants + * + * @param {Object} [options] + * + * @returns Returns a paginated list of variant objects ordered by sort + */ + public async listAllVariants(options: ListAllVariantsOptions = {}) { + return listAllVariants({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve file + * + * @description Retrieves the file with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/files#retrieve-a-file + * + * @param {String} options.id - The ID of the file to retrieve + * + * @returns A file object + */ + public async retrieveFile(options: RetrieveFileOptions) { + return retrieveFile({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all files + * + * @description Returns a paginated list of files + * + * @docs https://docs.lemonsqueezy.com/api/files#list-all-files + * + * @param {Object} [options] + * + * @returns Returns a paginated list of file objects ordered by `sort` + */ + public async listAllFiles(options: ListAllFilesOptions = {}) { + return listAllFiles({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve order + * + * @description Retrieves the order with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/orders#retrieve-an-order + * + * @param {String} options.id - The ID of the order to retrieve + * + * @returns A order object + */ + public async retrieveOrder(options: RetrieveOrderOptions) { + return retrieveOrder({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all orders + * + * @description Returns a paginated list of orders + * + * @docs https://docs.lemonsqueezy.com/api/orders#list-all-orders + * + * @param {Object} [options] + * + * @returns Returns a paginated list of file objects ordered by `sort` + */ + public async listAllOrders(options: ListAllOrdersOptions = {}) { + return listAllOrders({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve order item + * + * @description Retrieves the order item with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/order-items#retrieve-an-order-item + * + * @param {String} options.id - The ID of the order item to retrieve + * + * @returns A order item object + */ + public async retrieveOrderItem(options: RetrieveOrderItemOptions) { + return retrieveOrderItem({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all order items + * + * @description Returns a paginated list of order items + * + * @docs https://docs.lemonsqueezy.com/api/order-items#list-all-order-items + * + * @param {Object} [options] + * + * @returns Returns a paginated list of order item objects ordered by `id` + */ + public async listAllOrderItems(options: ListAllOrderItemsOptions = {}) { + return listAllOrderItems({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve subscription + * + * @description Retrieves the subscription with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#retrieve-a-subscription + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription object + */ + public async retrieveSubscription(options: RetrieveSubscriptionOptions) { + return retrieveSubscription({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all subscriptions + * + * @description Returns a paginated list of subscriptions + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#list-all-subscriptions + * + * @param {Object} [options] + * + * @returns Returns a paginated list of subscription objects ordered by `created_at` (descending) + */ + public async listAllSubscriptions(options: ListAllSubscriptionsOptions = {}) { + return listAllSubscriptions({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Update subscription + * + * @description Update an existing subscription to specific parameters. With this endpoint, you can: + * + * - Upgrade & Downgrade a subscripion to a different Product or Variant + * - Change payment pause collection behaviour + * - Update the billing date for when payments are collected + * + * When changing the plan of a subscription, we prorate the charge for the next billing cycle. + * For example, if a customer buys your product on April 1st for $50, they'll be charged $50 immediately. + * If on April 15th they upgrade to your $100 product, on May 1st they'll be charged $125. + * This is made up of $100 for renewing, $50 of used time on your upgraded $100 plan from April 15th - May 1st, and then a credited -$25 for unused time on your $50 plan. + * + * If downgrading a subscription, we'll issue a credit which is then applied on the next invoice. + * + * Changing a subscription plan may change the billing date or charge immediately if: + * + * - The variant has a different billing cycle (from monthly to yearly, etc) + * - The subscription is no longer free, or is now free + * - A trial starts or ends + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#update-a-subscription + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription object + */ + public async updateSubscription(options: UpdateSubscriptionOptions) { + return updateSubscription({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve discount + * + * @description Retrieves the discount with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/discounts#retrieve-a-discount + * + * @param {String} options.id - The ID of the discount to retrieve + * + * @returns A discount object + */ + public async retrieveDiscount(options: RetrieveDiscountOptions) { + return retrieveDiscount({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all discounts + * + * @description Returns a paginated list of discounts + * + * @docs https://docs.lemonsqueezy.com/api/discounts#list-all-discounts + * + * @param {Object} [options] + * + * @returns Returns a paginated list of discount objects ordered by `created_at` + */ + public async listAllDiscounts(options: ListAllDiscountsOptions = {}) { + return listAllDiscounts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve license key + * + * @description Retrieves the license key with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/license-keys#retrieve-a-license-key + * + * @param {String} options.id - The ID of the license key to retrieve + * + * @returns A license key object + */ + public async retrieveLicenseKey(options: RetrieveLicenseKeyOptions) { + return retrieveLicenseKey({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all license keys + * + * @description Returns a paginated list of license keys + * + * @docs https://docs.lemonsqueezy.com/api/license-keys#list-all-license-keys + * + * @param {Object} [options] + * + * @returns Returns a paginated list of license key objects ordered by `id` + */ + public async listAllLicenseKeys(options: ListAllLicenseKeysOptions = {}) { + return listAllLicenseKeys({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve license key instance + * + * @description Retrieves the license key instance with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/license-key-instances#retrieve-a-license-key-instance + * + * @param {String} options.id - The ID of the license key instance to retrieve + * + * @returns A license key instance object + */ + public async retrieveLicenseKeyInstance( + options: RetrieveLicenseKeyInstanceOptions + ) { + return retrieveLicenseKeyInstance({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all license key instances + * + * @description Returns a paginated list of license key instances + * + * @docs https://docs.lemonsqueezy.com/api/license-key-instances#list-all-license-key-instances + * + * @param {Object} [options] + * + * @returns Returns a paginated list of license key instance objects ordered by `id` + */ + public async listAllLicenseKeyInstances( + options: ListAllLicenseKeyInstancesOptions = {} + ) { + return listAllLicenseKeyInstances({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve checkout + * + * @description Retrieves the checkout with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#retrieve-a-checkout + * + * @param {String} options.id - The ID of the checkout to retrieve + * + * @returns A checkout object + */ + public async retrieveCheckout(options: RetrieveCheckoutOptions) { + return retrieveCheckout({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all checkouts + * + * @description Returns a paginated list of checkouts + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#list-all-checkouts + * + * @param {Object} [options] + * + * @returns Returns a paginated list of checkout objects ordered by `created_at` (descending) + */ + public async listAllCheckouts(options: ListAllCheckoutsOptions = {}) { + return listAllCheckouts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Create checkout + * + * @description Create a custom checkout. Use this endpoint to create a unique checkout URL for a specific variant + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout + * + * @returns A checkout object + */ + public async createCheckout(options: CreateCheckoutOptions) { + return createCheckout({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve subscription invoice + * + * @description Retrieves a subscription invoice with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#retrieve-a-subscription-invoice + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription invoice object + */ + public async retrieveSubscriptionInvoice( + options: RetrieveSubscriptionInvoiceOptions + ) { + return retrieveSubscriptionInvoice({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all subscription invoices + * + * @description Returns a paginated list of subscriptions + * + * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#list-all-subscription-invoices + * + * @param {Object} [options] + * + * @returns Returns a paginated list of subscription invoice objects. + */ + public async listAllSubscriptionInvoices( + options: ListAllSubscriptionInvoicesOptions = {} + ) { + return listAllSubscriptionInvoices({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve customer + * + * @description Retrieves the customer with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer + * + * @param {String} options.id - The ID of the customer to retrieve + * + * @returns A customer object + */ + public async retrieveCustomer(options: RetrieveCustomerOptions) { + return retrieveCustomer({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all customers + * + * @description Returns a paginated list of customers + * + * @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers + * + * @param {Object} [options] + * + * @returns Returns a paginated list of customer objects ordered by `created_at` (descending) + */ + public async listAllCustomers(options: ListAllCustomersOptions = {}) { + return listAllCustomers({ + apiKey: this._apiKey, + ...options, + }); + } } diff --git a/src/index.ts b/src/index.ts index 8efe05c..a101238 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,28 +1,30 @@ -export { LemonsqueezyClient } from "./client"; +export { LemonsqueezyClient } from './client'; export { - createCheckout, - getUser, - listAllCheckouts, - listAllDiscounts, - listAllFiles, - listAllLicenseKeyInstances, - listAllLicenseKeys, - listAllOrderItems, - listAllOrders, - listAllProducts, - listAllStores, - listAllSubscriptions, - listAllVariants, - retrieveCheckout, - retrieveDiscount, - retrieveFile, - retrieveLicenseKey, - retrieveLicenseKeyInstance, - retrieveOrder, - retrieveOrderItem, - retrieveProduct, - retrieveStore, - retrieveSubscription, - retrieveVariant, - updateSubscription, -} from "./modules"; + createCheckout, + getUser, + listAllCheckouts, + listAllCustomers, + listAllDiscounts, + listAllFiles, + listAllLicenseKeyInstances, + listAllLicenseKeys, + listAllOrderItems, + listAllOrders, + listAllProducts, + listAllStores, + listAllSubscriptions, + listAllVariants, + retrieveCheckout, + retrieveCustomer, + retrieveDiscount, + retrieveFile, + retrieveLicenseKey, + retrieveLicenseKeyInstance, + retrieveOrder, + retrieveOrderItem, + retrieveProduct, + retrieveStore, + retrieveSubscription, + retrieveVariant, + updateSubscription, +} from './modules'; diff --git a/src/modules/customer/README.md b/src/modules/customer/README.md new file mode 100644 index 0000000..6b76298 --- /dev/null +++ b/src/modules/customer/README.md @@ -0,0 +1,28 @@ +## 👤 Customer + +[![Docs](https://img.shields.io/badge/-Docs-blue.svg?style=for-the-badge)](https://docs.lemonsqueezy.com/api/customers) + +```typescript +import { LemonsqueezyClient } from 'lemonsqueezy.ts'; + +const client = new LemonsqueezyClient('YOUR_API_KEY'); + +const customer = await client.retrieveCustomer({ + id: '...', +}); + +const customers = await client.listAllCustomers(); +``` + +```typescript +import { retrieveCustomer, listAllCustomers } from 'lemonsqueezy.ts/customer'; + +const customer = await retrieveCustomer({ + apiKey: 'YOUR_API_KEY', + id: '...', +}); + +const customers = await listAllCustomers({ + apiKey: 'YOUR_API_KEY', +}); +``` diff --git a/src/modules/customer/customer.action.ts b/src/modules/customer/customer.action.ts new file mode 100644 index 0000000..1d20950 --- /dev/null +++ b/src/modules/customer/customer.action.ts @@ -0,0 +1,56 @@ +import { + ListAllCustomersOptions, + ListAllCustomersResult, + RetrieveCustomerOptions, + RetrieveCustomerResult, +} from './customer.types'; +import type { SharedModuleOptions } from '~/shared'; +import { requestLemonSqueeze } from '~/shared'; + +/** + * List all customers + * + * @description Returns a paginated list of customers + * + * @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers + * + * @param {Object} [options] + * + * @returns Returns a paginated list of customer objects ordered by `created_at` (descending) + */ +export async function listAllCustomers( + options: ListAllCustomersOptions & SharedModuleOptions +): Promise { + const { storeId, email, ...rest } = options; + + return requestLemonSqueeze({ + params: { + ...(storeId ? { store_id: storeId } : {}), + ...(email ? { email: email } : {}), + }, + path: '/customers', + ...rest, + }); +} + +/** + * Retrieve customer + * + * @description Retrieves the customer with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer + * + * @param {String} options.id - The ID of the customer to retrieve + * + * @returns A customer object + */ +export async function retrieveCustomer( + options: RetrieveCustomerOptions & SharedModuleOptions +): Promise { + const { id, ...rest } = options; + + return requestLemonSqueeze({ + path: `/customers/${id}`, + ...rest, + }); +} diff --git a/src/modules/customer/customer.test.ts b/src/modules/customer/customer.test.ts new file mode 100644 index 0000000..7638374 --- /dev/null +++ b/src/modules/customer/customer.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect, beforeAll } from 'vitest'; + +import { listAllCustomers, retrieveCustomer } from '.'; + +describe.concurrent('Customer', () => { + const apiKey = process.env.LEMON_SQUEEZY_API_KEY as string; + + beforeAll(() => { + if (!apiKey) throw 'No LEMON_SQUEEZY_API_KEY environment variable found'; + }); + + it('Retrieve customer', async () => { + const customers = await listAllCustomers({ + apiKey, + }); + if (!customers.data.length) throw new Error('No customers found'); + + const customer = await retrieveCustomer({ + apiKey, + id: customers.data.at(0)!.id, + }); + + expect(customer).toBeDefined(); + expect(customer.data).toBeDefined(); + expect(customer.data).not.toBeNull(); + expect(customer.errors).toBeUndefined(); + }); + + it('List all customers', async () => { + const customers = await listAllCustomers({ + apiKey, + }); + + expect(customers).toBeDefined(); + expect(Array.isArray(customers.data)).toBe(true); + expect(customers.errors).toBeUndefined(); + }); +}); diff --git a/src/modules/customer/customer.types.ts b/src/modules/customer/customer.types.ts new file mode 100644 index 0000000..44b2949 --- /dev/null +++ b/src/modules/customer/customer.types.ts @@ -0,0 +1,103 @@ +import type { + BaseLemonsqueezyResponse, + LemonsqueezyDataType, + PaginatedBaseLemonsqueezyResponse, + SharedLemonsqueezyOptions, +} from '~/shared'; + +export interface LemonsqueezyCustomer { + attributes: { + /** + * The ID of the store this customer belongs to + */ + store_id: number; + /** + * The full name of the customer + */ + name: string; + /** + * The email address of the customer + */ + email: string; + /** + * The email marketing status of the customer. + */ + status: + | 'subscribed' + | 'unsubscribed' + | 'archived' + | 'requires_verification' + | 'invalid_email' + | 'bounced'; + /** + * The city of the customer + */ + city: string; + /** + * The region of the customer + */ + region: string; + /** + * The country of the customer + */ + country: string; + /** + * A positive integer in cents representing the total revenue from the customer (USD). + */ + total_revenue_currency: number; + /** + * A positive integer in cents representing the monthly recurring revenue from the customer (USD). + */ + mrr: number; + /** + * The formatted status of the customer. + */ + status_formatted: string; + /** + * The formatted country of the customer. + */ + country_formatted: string; + /** + * A human-readable string representing the total revenue from the customer (e.g. $9.99). + */ + total_revenue_currency_formatted: string; + /** + * A human-readable string representing the monthly recurring revenue from the customer (e.g. $9.99). + */ + mrr_formatted: string; + /** + * An ISO-8601 formatted date-time string indicating when the object was created + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + created_at: Date; + /** + * An ISO-8601 formatted date-time string indicating when the object was last updated + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + updated_at: Date; + }; + type: LemonsqueezyDataType.customers; + id: string; +} + +export interface ListAllCustomersOptions extends SharedLemonsqueezyOptions { + /** + * Only return checkouts belonging to the store with this ID + */ + storeId?: string; + /** + * Only return customers where the email field is equal to this email address. + */ + email?: string; +} + +export type ListAllCustomersResult = PaginatedBaseLemonsqueezyResponse< + Array +>; + +export interface RetrieveCustomerOptions extends SharedLemonsqueezyOptions { + id: string; +} + +export type RetrieveCustomerResult = + BaseLemonsqueezyResponse; diff --git a/src/modules/customer/index.ts b/src/modules/customer/index.ts new file mode 100644 index 0000000..044b204 --- /dev/null +++ b/src/modules/customer/index.ts @@ -0,0 +1,16 @@ +import { listAllCustomers, retrieveCustomer } from './customer.action'; + +export { listAllCustomers, retrieveCustomer }; + +export type { + LemonsqueezyCustomer, + ListAllCustomersOptions, + ListAllCustomersResult, + RetrieveCustomerOptions, + RetrieveCustomerResult, +} from './customer.types'; + +export default { + listAllCustomers, + retrieveCustomer, +} as const; diff --git a/src/modules/index.ts b/src/modules/index.ts index 38664d1..b9bd935 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,13 +1,14 @@ -export * from "./checkout"; -export * from "./discount"; -export * from "./file"; -export * from "./licenseKey"; -export * from "./licenseKeyInstance"; -export * from "./order"; -export * from "./orderItem"; -export * from "./product"; -export * from "./store"; -export * from "./subscription"; -export * from "./subscriptionInvoice"; -export * from "./user"; -export * from "./variant"; +export * from './checkout'; +export * from './customer'; +export * from './discount'; +export * from './file'; +export * from './licenseKey'; +export * from './licenseKeyInstance'; +export * from './order'; +export * from './orderItem'; +export * from './product'; +export * from './store'; +export * from './subscription'; +export * from './subscriptionInvoice'; +export * from './user'; +export * from './variant'; diff --git a/src/shared/shared.types.ts b/src/shared/shared.types.ts index dcf4ef6..6bbf2a2 100644 --- a/src/shared/shared.types.ts +++ b/src/shared/shared.types.ts @@ -1,85 +1,86 @@ -import type { RequestInit } from "undici"; +import type { RequestInit } from 'undici'; export interface SharedModuleOptions { - apiKey: string; - page?: number; - include?: Array; + apiKey: string; + page?: number; + include?: Array; } export interface SharedLemonsqueezyOptions { - apiVersion?: "v1"; - baseUrl?: string; + apiVersion?: 'v1'; + baseUrl?: string; } export interface LemonsqueezyOptions< - TData extends Record = Record -> extends Omit, - SharedLemonsqueezyOptions, - SharedModuleOptions { - data?: TData; - params?: Record; - method?: - | "CONNECT" - | "DELETE" - | "GET" - | "HEAD" - | "OPTIONS" - | "PATCH" - | "POST" - | "PUT" - | "TRACE"; - path: string; + TData extends Record = Record +> extends Omit, + SharedLemonsqueezyOptions, + SharedModuleOptions { + data?: TData; + params?: Record; + method?: + | 'CONNECT' + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT' + | 'TRACE'; + path: string; } export enum LemonsqueezyDataType { - checkouts = "checkouts", - discounts = "discounts", - files = "files", - license_key_instances = "license-key-instances", - license_keys = "license-keys", - order_items = "order-items", - orders = "orders", - products = "products", - stores = "stores", - subscriptions = "subscriptions", - subscription_invoices = "subscription-invoices", - users = "users", - variants = "variants", + checkouts = 'checkouts', + customers = 'customers', + discounts = 'discounts', + files = 'files', + license_key_instances = 'license-key-instances', + license_keys = 'license-keys', + order_items = 'order-items', + orders = 'orders', + products = 'products', + stores = 'stores', + subscriptions = 'subscriptions', + subscription_invoices = 'subscription-invoices', + users = 'users', + variants = 'variants', } export interface BaseLemonsqueezyResponse< - TData, - TLinks = { - self: string; - } + TData, + TLinks = { + self: string; + } > { - data: TData; - errors?: Array<{ - detail: string; - status: string | number; - title: string; - }>; - jsonapi: { - version: string; - }; - links: TLinks; + data: TData; + errors?: Array<{ + detail: string; + status: string | number; + title: string; + }>; + jsonapi: { + version: string; + }; + links: TLinks; } export interface PaginatedBaseLemonsqueezyResponse< - TData, - TLinks = { - first: string; - last: string; - } + TData, + TLinks = { + first: string; + last: string; + } > extends BaseLemonsqueezyResponse { - meta: { - page: { - currentPage: number; - from: number; - lastPage: number; - perPage: number; - to: number; - total: number; - }; - }; + meta: { + page: { + currentPage: number; + from: number; + lastPage: number; + perPage: number; + to: number; + total: number; + }; + }; } From b69868e3cbddf062bf1caf1e7bffc04a4ddacd5a Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Fri, 30 Jun 2023 13:50:14 +0200 Subject: [PATCH 4/7] type: custom_price optional --- src/modules/checkout/checkout.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/checkout/checkout.types.ts b/src/modules/checkout/checkout.types.ts index 1f9b11c..25ac72c 100644 --- a/src/modules/checkout/checkout.types.ts +++ b/src/modules/checkout/checkout.types.ts @@ -221,7 +221,7 @@ export interface CreateCheckoutOptions extends SharedLemonsqueezyOptions { * future (i.e. the customer is moved to a different subscription "tier") the * new variant's price will be used from that moment forward. */ - custom_price: number; + custom_price?: number | null; /** * An ISO-8601 formatted date-time string indicating when the checkout expires * From d5cfe738ba951088b06e6be8188731593871c00d Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Tue, 4 Jul 2023 16:41:40 +0200 Subject: [PATCH 5/7] format: run prettier --- src/client/client.class.ts | 1220 +++++++++++------------ src/index.ts | 58 +- src/modules/checkout/checkout.types.ts | 522 +++++----- src/modules/customer/customer.action.ts | 46 +- src/modules/customer/customer.test.ts | 74 +- src/modules/customer/customer.types.ts | 176 ++-- src/modules/customer/index.ts | 18 +- src/modules/index.ts | 28 +- src/shared/shared.types.ts | 132 +-- 9 files changed, 1137 insertions(+), 1137 deletions(-) diff --git a/src/client/client.class.ts b/src/client/client.class.ts index d3e3a03..9640c9d 100644 --- a/src/client/client.class.ts +++ b/src/client/client.class.ts @@ -1,616 +1,616 @@ import { - createCheckout, - getUser, - listAllCheckouts, - listAllCustomers, - listAllDiscounts, - listAllFiles, - listAllLicenseKeyInstances, - listAllLicenseKeys, - listAllOrderItems, - listAllOrders, - listAllProducts, - listAllStores, - listAllSubscriptionInvoices, - listAllSubscriptions, - listAllVariants, - retrieveCheckout, - retrieveCustomer, - retrieveDiscount, - retrieveFile, - retrieveLicenseKey, - retrieveLicenseKeyInstance, - retrieveOrder, - retrieveOrderItem, - retrieveProduct, - retrieveStore, - retrieveSubscription, - retrieveSubscriptionInvoice, - retrieveVariant, - updateSubscription, -} from '~/modules'; + createCheckout, + getUser, + listAllCheckouts, + listAllCustomers, + listAllDiscounts, + listAllFiles, + listAllLicenseKeyInstances, + listAllLicenseKeys, + listAllOrderItems, + listAllOrders, + listAllProducts, + listAllStores, + listAllSubscriptionInvoices, + listAllSubscriptions, + listAllVariants, + retrieveCheckout, + retrieveCustomer, + retrieveDiscount, + retrieveFile, + retrieveLicenseKey, + retrieveLicenseKeyInstance, + retrieveOrder, + retrieveOrderItem, + retrieveProduct, + retrieveStore, + retrieveSubscription, + retrieveSubscriptionInvoice, + retrieveVariant, + updateSubscription, +} from "~/modules"; import type { - CreateCheckoutOptions, - GetUserOptions, - ListAllCheckoutsOptions, - ListAllCustomersOptions, - ListAllDiscountsOptions, - ListAllFilesOptions, - ListAllLicenseKeyInstancesOptions, - ListAllLicenseKeysOptions, - ListAllOrderItemsOptions, - ListAllOrdersOptions, - ListAllProductsOptions, - ListAllStoresOptions, - ListAllSubscriptionInvoicesOptions, - ListAllSubscriptionsOptions, - ListAllVariantsOptions, - RetrieveCheckoutOptions, - RetrieveCustomerOptions, - RetrieveDiscountOptions, - RetrieveFileOptions, - RetrieveLicenseKeyInstanceOptions, - RetrieveLicenseKeyOptions, - RetrieveOrderItemOptions, - RetrieveOrderOptions, - RetrieveProductOptions, - RetrieveStoreOptions, - RetrieveSubscriptionInvoiceOptions, - RetrieveSubscriptionOptions, - RetrieveVariantOptions, - UpdateSubscriptionOptions, -} from '~/modules'; + CreateCheckoutOptions, + GetUserOptions, + ListAllCheckoutsOptions, + ListAllCustomersOptions, + ListAllDiscountsOptions, + ListAllFilesOptions, + ListAllLicenseKeyInstancesOptions, + ListAllLicenseKeysOptions, + ListAllOrderItemsOptions, + ListAllOrdersOptions, + ListAllProductsOptions, + ListAllStoresOptions, + ListAllSubscriptionInvoicesOptions, + ListAllSubscriptionsOptions, + ListAllVariantsOptions, + RetrieveCheckoutOptions, + RetrieveCustomerOptions, + RetrieveDiscountOptions, + RetrieveFileOptions, + RetrieveLicenseKeyInstanceOptions, + RetrieveLicenseKeyOptions, + RetrieveOrderItemOptions, + RetrieveOrderOptions, + RetrieveProductOptions, + RetrieveStoreOptions, + RetrieveSubscriptionInvoiceOptions, + RetrieveSubscriptionOptions, + RetrieveVariantOptions, + UpdateSubscriptionOptions, +} from "~/modules"; export class LemonsqueezyClient { - private _apiKey: string; - - constructor(apiKey: string) { - this._apiKey = apiKey; - } - - /** - * Get User - * - * @description Retrieves the currently authenticated user - * - * @docs https://docs.lemonsqueezy.com/api/users#retrieve-the-authenticated-user - * - * @param {Object} [options] - * - * @returns A user object - */ - public async getUser(options: GetUserOptions = {}) { - return getUser({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve store - * - * @description Retrieves the store with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/stores#retrieve-a-store - * - * @param {String} options.id - The ID of the store to retrieve - * - * @returns A store object - */ - public async retrieveStore(options: RetrieveStoreOptions) { - return retrieveStore({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all stores - * - * @description Returns a paginated list of stores - * - * @docs https://docs.lemonsqueezy.com/api/stores#list-all-stores - * - * @param {Object} [options] - * - * @returns Returns a paginated list of `store` objects ordered by name - */ - public async listAllStores(options: ListAllStoresOptions = {}) { - return listAllStores({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve product - * - * @description Retrieves the product with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/products#retrieve-a-product - * - * @param {String} options.id - The ID of the product to retrieve - * - * @returns A product object - */ - public async retrieveProduct(options: RetrieveProductOptions) { - return retrieveProduct({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all products - * - * @description Returns a paginated list of products - * - * @docs https://docs.lemonsqueezy.com/api/products#list-all-products - * - * @param {Object} [options] - * - * @returns Returns a paginated list of product objects ordered by `name` - */ - public async listAllProducts(options: ListAllProductsOptions = {}) { - return listAllProducts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve variant - * - * @description Retrieves the variant with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/variants#retrieve-a-variant - * - * @param {String} options.id - The ID of the variant to retrieve - * - * @returns A variant object - */ - public async retrieveVariant(options: RetrieveVariantOptions) { - return retrieveVariant({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all variants - * - * @description Returns a paginated list of variants - * - * @docs https://docs.lemonsqueezy.com/api/variants#list-all-variants - * - * @param {Object} [options] - * - * @returns Returns a paginated list of variant objects ordered by sort - */ - public async listAllVariants(options: ListAllVariantsOptions = {}) { - return listAllVariants({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve file - * - * @description Retrieves the file with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/files#retrieve-a-file - * - * @param {String} options.id - The ID of the file to retrieve - * - * @returns A file object - */ - public async retrieveFile(options: RetrieveFileOptions) { - return retrieveFile({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all files - * - * @description Returns a paginated list of files - * - * @docs https://docs.lemonsqueezy.com/api/files#list-all-files - * - * @param {Object} [options] - * - * @returns Returns a paginated list of file objects ordered by `sort` - */ - public async listAllFiles(options: ListAllFilesOptions = {}) { - return listAllFiles({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve order - * - * @description Retrieves the order with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/orders#retrieve-an-order - * - * @param {String} options.id - The ID of the order to retrieve - * - * @returns A order object - */ - public async retrieveOrder(options: RetrieveOrderOptions) { - return retrieveOrder({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all orders - * - * @description Returns a paginated list of orders - * - * @docs https://docs.lemonsqueezy.com/api/orders#list-all-orders - * - * @param {Object} [options] - * - * @returns Returns a paginated list of file objects ordered by `sort` - */ - public async listAllOrders(options: ListAllOrdersOptions = {}) { - return listAllOrders({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve order item - * - * @description Retrieves the order item with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/order-items#retrieve-an-order-item - * - * @param {String} options.id - The ID of the order item to retrieve - * - * @returns A order item object - */ - public async retrieveOrderItem(options: RetrieveOrderItemOptions) { - return retrieveOrderItem({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all order items - * - * @description Returns a paginated list of order items - * - * @docs https://docs.lemonsqueezy.com/api/order-items#list-all-order-items - * - * @param {Object} [options] - * - * @returns Returns a paginated list of order item objects ordered by `id` - */ - public async listAllOrderItems(options: ListAllOrderItemsOptions = {}) { - return listAllOrderItems({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve subscription - * - * @description Retrieves the subscription with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#retrieve-a-subscription - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription object - */ - public async retrieveSubscription(options: RetrieveSubscriptionOptions) { - return retrieveSubscription({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all subscriptions - * - * @description Returns a paginated list of subscriptions - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#list-all-subscriptions - * - * @param {Object} [options] - * - * @returns Returns a paginated list of subscription objects ordered by `created_at` (descending) - */ - public async listAllSubscriptions(options: ListAllSubscriptionsOptions = {}) { - return listAllSubscriptions({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Update subscription - * - * @description Update an existing subscription to specific parameters. With this endpoint, you can: - * - * - Upgrade & Downgrade a subscripion to a different Product or Variant - * - Change payment pause collection behaviour - * - Update the billing date for when payments are collected - * - * When changing the plan of a subscription, we prorate the charge for the next billing cycle. - * For example, if a customer buys your product on April 1st for $50, they'll be charged $50 immediately. - * If on April 15th they upgrade to your $100 product, on May 1st they'll be charged $125. - * This is made up of $100 for renewing, $50 of used time on your upgraded $100 plan from April 15th - May 1st, and then a credited -$25 for unused time on your $50 plan. - * - * If downgrading a subscription, we'll issue a credit which is then applied on the next invoice. - * - * Changing a subscription plan may change the billing date or charge immediately if: - * - * - The variant has a different billing cycle (from monthly to yearly, etc) - * - The subscription is no longer free, or is now free - * - A trial starts or ends - * - * @docs https://docs.lemonsqueezy.com/api/subscriptions#update-a-subscription - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription object - */ - public async updateSubscription(options: UpdateSubscriptionOptions) { - return updateSubscription({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve discount - * - * @description Retrieves the discount with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/discounts#retrieve-a-discount - * - * @param {String} options.id - The ID of the discount to retrieve - * - * @returns A discount object - */ - public async retrieveDiscount(options: RetrieveDiscountOptions) { - return retrieveDiscount({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all discounts - * - * @description Returns a paginated list of discounts - * - * @docs https://docs.lemonsqueezy.com/api/discounts#list-all-discounts - * - * @param {Object} [options] - * - * @returns Returns a paginated list of discount objects ordered by `created_at` - */ - public async listAllDiscounts(options: ListAllDiscountsOptions = {}) { - return listAllDiscounts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve license key - * - * @description Retrieves the license key with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/license-keys#retrieve-a-license-key - * - * @param {String} options.id - The ID of the license key to retrieve - * - * @returns A license key object - */ - public async retrieveLicenseKey(options: RetrieveLicenseKeyOptions) { - return retrieveLicenseKey({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all license keys - * - * @description Returns a paginated list of license keys - * - * @docs https://docs.lemonsqueezy.com/api/license-keys#list-all-license-keys - * - * @param {Object} [options] - * - * @returns Returns a paginated list of license key objects ordered by `id` - */ - public async listAllLicenseKeys(options: ListAllLicenseKeysOptions = {}) { - return listAllLicenseKeys({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve license key instance - * - * @description Retrieves the license key instance with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/license-key-instances#retrieve-a-license-key-instance - * - * @param {String} options.id - The ID of the license key instance to retrieve - * - * @returns A license key instance object - */ - public async retrieveLicenseKeyInstance( - options: RetrieveLicenseKeyInstanceOptions - ) { - return retrieveLicenseKeyInstance({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all license key instances - * - * @description Returns a paginated list of license key instances - * - * @docs https://docs.lemonsqueezy.com/api/license-key-instances#list-all-license-key-instances - * - * @param {Object} [options] - * - * @returns Returns a paginated list of license key instance objects ordered by `id` - */ - public async listAllLicenseKeyInstances( - options: ListAllLicenseKeyInstancesOptions = {} - ) { - return listAllLicenseKeyInstances({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve checkout - * - * @description Retrieves the checkout with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#retrieve-a-checkout - * - * @param {String} options.id - The ID of the checkout to retrieve - * - * @returns A checkout object - */ - public async retrieveCheckout(options: RetrieveCheckoutOptions) { - return retrieveCheckout({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all checkouts - * - * @description Returns a paginated list of checkouts - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#list-all-checkouts - * - * @param {Object} [options] - * - * @returns Returns a paginated list of checkout objects ordered by `created_at` (descending) - */ - public async listAllCheckouts(options: ListAllCheckoutsOptions = {}) { - return listAllCheckouts({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Create checkout - * - * @description Create a custom checkout. Use this endpoint to create a unique checkout URL for a specific variant - * - * @docs https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout - * - * @returns A checkout object - */ - public async createCheckout(options: CreateCheckoutOptions) { - return createCheckout({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve subscription invoice - * - * @description Retrieves a subscription invoice with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#retrieve-a-subscription-invoice - * - * @param {String} options.id - The ID of the subscription to retrieve - * - * @returns A subscription invoice object - */ - public async retrieveSubscriptionInvoice( - options: RetrieveSubscriptionInvoiceOptions - ) { - return retrieveSubscriptionInvoice({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all subscription invoices - * - * @description Returns a paginated list of subscriptions - * - * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#list-all-subscription-invoices - * - * @param {Object} [options] - * - * @returns Returns a paginated list of subscription invoice objects. - */ - public async listAllSubscriptionInvoices( - options: ListAllSubscriptionInvoicesOptions = {} - ) { - return listAllSubscriptionInvoices({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * Retrieve customer - * - * @description Retrieves the customer with the given ID - * - * @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer - * - * @param {String} options.id - The ID of the customer to retrieve - * - * @returns A customer object - */ - public async retrieveCustomer(options: RetrieveCustomerOptions) { - return retrieveCustomer({ - apiKey: this._apiKey, - ...options, - }); - } - - /** - * List all customers - * - * @description Returns a paginated list of customers - * - * @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers - * - * @param {Object} [options] - * - * @returns Returns a paginated list of customer objects ordered by `created_at` (descending) - */ - public async listAllCustomers(options: ListAllCustomersOptions = {}) { - return listAllCustomers({ - apiKey: this._apiKey, - ...options, - }); - } + private _apiKey: string; + + constructor(apiKey: string) { + this._apiKey = apiKey; + } + + /** + * Get User + * + * @description Retrieves the currently authenticated user + * + * @docs https://docs.lemonsqueezy.com/api/users#retrieve-the-authenticated-user + * + * @param {Object} [options] + * + * @returns A user object + */ + public async getUser(options: GetUserOptions = {}) { + return getUser({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve store + * + * @description Retrieves the store with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/stores#retrieve-a-store + * + * @param {String} options.id - The ID of the store to retrieve + * + * @returns A store object + */ + public async retrieveStore(options: RetrieveStoreOptions) { + return retrieveStore({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all stores + * + * @description Returns a paginated list of stores + * + * @docs https://docs.lemonsqueezy.com/api/stores#list-all-stores + * + * @param {Object} [options] + * + * @returns Returns a paginated list of `store` objects ordered by name + */ + public async listAllStores(options: ListAllStoresOptions = {}) { + return listAllStores({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve product + * + * @description Retrieves the product with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/products#retrieve-a-product + * + * @param {String} options.id - The ID of the product to retrieve + * + * @returns A product object + */ + public async retrieveProduct(options: RetrieveProductOptions) { + return retrieveProduct({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all products + * + * @description Returns a paginated list of products + * + * @docs https://docs.lemonsqueezy.com/api/products#list-all-products + * + * @param {Object} [options] + * + * @returns Returns a paginated list of product objects ordered by `name` + */ + public async listAllProducts(options: ListAllProductsOptions = {}) { + return listAllProducts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve variant + * + * @description Retrieves the variant with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/variants#retrieve-a-variant + * + * @param {String} options.id - The ID of the variant to retrieve + * + * @returns A variant object + */ + public async retrieveVariant(options: RetrieveVariantOptions) { + return retrieveVariant({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all variants + * + * @description Returns a paginated list of variants + * + * @docs https://docs.lemonsqueezy.com/api/variants#list-all-variants + * + * @param {Object} [options] + * + * @returns Returns a paginated list of variant objects ordered by sort + */ + public async listAllVariants(options: ListAllVariantsOptions = {}) { + return listAllVariants({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve file + * + * @description Retrieves the file with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/files#retrieve-a-file + * + * @param {String} options.id - The ID of the file to retrieve + * + * @returns A file object + */ + public async retrieveFile(options: RetrieveFileOptions) { + return retrieveFile({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all files + * + * @description Returns a paginated list of files + * + * @docs https://docs.lemonsqueezy.com/api/files#list-all-files + * + * @param {Object} [options] + * + * @returns Returns a paginated list of file objects ordered by `sort` + */ + public async listAllFiles(options: ListAllFilesOptions = {}) { + return listAllFiles({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve order + * + * @description Retrieves the order with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/orders#retrieve-an-order + * + * @param {String} options.id - The ID of the order to retrieve + * + * @returns A order object + */ + public async retrieveOrder(options: RetrieveOrderOptions) { + return retrieveOrder({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all orders + * + * @description Returns a paginated list of orders + * + * @docs https://docs.lemonsqueezy.com/api/orders#list-all-orders + * + * @param {Object} [options] + * + * @returns Returns a paginated list of file objects ordered by `sort` + */ + public async listAllOrders(options: ListAllOrdersOptions = {}) { + return listAllOrders({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve order item + * + * @description Retrieves the order item with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/order-items#retrieve-an-order-item + * + * @param {String} options.id - The ID of the order item to retrieve + * + * @returns A order item object + */ + public async retrieveOrderItem(options: RetrieveOrderItemOptions) { + return retrieveOrderItem({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all order items + * + * @description Returns a paginated list of order items + * + * @docs https://docs.lemonsqueezy.com/api/order-items#list-all-order-items + * + * @param {Object} [options] + * + * @returns Returns a paginated list of order item objects ordered by `id` + */ + public async listAllOrderItems(options: ListAllOrderItemsOptions = {}) { + return listAllOrderItems({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve subscription + * + * @description Retrieves the subscription with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#retrieve-a-subscription + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription object + */ + public async retrieveSubscription(options: RetrieveSubscriptionOptions) { + return retrieveSubscription({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all subscriptions + * + * @description Returns a paginated list of subscriptions + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#list-all-subscriptions + * + * @param {Object} [options] + * + * @returns Returns a paginated list of subscription objects ordered by `created_at` (descending) + */ + public async listAllSubscriptions(options: ListAllSubscriptionsOptions = {}) { + return listAllSubscriptions({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Update subscription + * + * @description Update an existing subscription to specific parameters. With this endpoint, you can: + * + * - Upgrade & Downgrade a subscripion to a different Product or Variant + * - Change payment pause collection behaviour + * - Update the billing date for when payments are collected + * + * When changing the plan of a subscription, we prorate the charge for the next billing cycle. + * For example, if a customer buys your product on April 1st for $50, they'll be charged $50 immediately. + * If on April 15th they upgrade to your $100 product, on May 1st they'll be charged $125. + * This is made up of $100 for renewing, $50 of used time on your upgraded $100 plan from April 15th - May 1st, and then a credited -$25 for unused time on your $50 plan. + * + * If downgrading a subscription, we'll issue a credit which is then applied on the next invoice. + * + * Changing a subscription plan may change the billing date or charge immediately if: + * + * - The variant has a different billing cycle (from monthly to yearly, etc) + * - The subscription is no longer free, or is now free + * - A trial starts or ends + * + * @docs https://docs.lemonsqueezy.com/api/subscriptions#update-a-subscription + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription object + */ + public async updateSubscription(options: UpdateSubscriptionOptions) { + return updateSubscription({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve discount + * + * @description Retrieves the discount with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/discounts#retrieve-a-discount + * + * @param {String} options.id - The ID of the discount to retrieve + * + * @returns A discount object + */ + public async retrieveDiscount(options: RetrieveDiscountOptions) { + return retrieveDiscount({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all discounts + * + * @description Returns a paginated list of discounts + * + * @docs https://docs.lemonsqueezy.com/api/discounts#list-all-discounts + * + * @param {Object} [options] + * + * @returns Returns a paginated list of discount objects ordered by `created_at` + */ + public async listAllDiscounts(options: ListAllDiscountsOptions = {}) { + return listAllDiscounts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve license key + * + * @description Retrieves the license key with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/license-keys#retrieve-a-license-key + * + * @param {String} options.id - The ID of the license key to retrieve + * + * @returns A license key object + */ + public async retrieveLicenseKey(options: RetrieveLicenseKeyOptions) { + return retrieveLicenseKey({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all license keys + * + * @description Returns a paginated list of license keys + * + * @docs https://docs.lemonsqueezy.com/api/license-keys#list-all-license-keys + * + * @param {Object} [options] + * + * @returns Returns a paginated list of license key objects ordered by `id` + */ + public async listAllLicenseKeys(options: ListAllLicenseKeysOptions = {}) { + return listAllLicenseKeys({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve license key instance + * + * @description Retrieves the license key instance with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/license-key-instances#retrieve-a-license-key-instance + * + * @param {String} options.id - The ID of the license key instance to retrieve + * + * @returns A license key instance object + */ + public async retrieveLicenseKeyInstance( + options: RetrieveLicenseKeyInstanceOptions + ) { + return retrieveLicenseKeyInstance({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all license key instances + * + * @description Returns a paginated list of license key instances + * + * @docs https://docs.lemonsqueezy.com/api/license-key-instances#list-all-license-key-instances + * + * @param {Object} [options] + * + * @returns Returns a paginated list of license key instance objects ordered by `id` + */ + public async listAllLicenseKeyInstances( + options: ListAllLicenseKeyInstancesOptions = {} + ) { + return listAllLicenseKeyInstances({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve checkout + * + * @description Retrieves the checkout with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#retrieve-a-checkout + * + * @param {String} options.id - The ID of the checkout to retrieve + * + * @returns A checkout object + */ + public async retrieveCheckout(options: RetrieveCheckoutOptions) { + return retrieveCheckout({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all checkouts + * + * @description Returns a paginated list of checkouts + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#list-all-checkouts + * + * @param {Object} [options] + * + * @returns Returns a paginated list of checkout objects ordered by `created_at` (descending) + */ + public async listAllCheckouts(options: ListAllCheckoutsOptions = {}) { + return listAllCheckouts({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Create checkout + * + * @description Create a custom checkout. Use this endpoint to create a unique checkout URL for a specific variant + * + * @docs https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout + * + * @returns A checkout object + */ + public async createCheckout(options: CreateCheckoutOptions) { + return createCheckout({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve subscription invoice + * + * @description Retrieves a subscription invoice with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#retrieve-a-subscription-invoice + * + * @param {String} options.id - The ID of the subscription to retrieve + * + * @returns A subscription invoice object + */ + public async retrieveSubscriptionInvoice( + options: RetrieveSubscriptionInvoiceOptions + ) { + return retrieveSubscriptionInvoice({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all subscription invoices + * + * @description Returns a paginated list of subscriptions + * + * @docs https://docs.lemonsqueezy.com/api/subscription-invoices#list-all-subscription-invoices + * + * @param {Object} [options] + * + * @returns Returns a paginated list of subscription invoice objects. + */ + public async listAllSubscriptionInvoices( + options: ListAllSubscriptionInvoicesOptions = {} + ) { + return listAllSubscriptionInvoices({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * Retrieve customer + * + * @description Retrieves the customer with the given ID + * + * @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer + * + * @param {String} options.id - The ID of the customer to retrieve + * + * @returns A customer object + */ + public async retrieveCustomer(options: RetrieveCustomerOptions) { + return retrieveCustomer({ + apiKey: this._apiKey, + ...options, + }); + } + + /** + * List all customers + * + * @description Returns a paginated list of customers + * + * @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers + * + * @param {Object} [options] + * + * @returns Returns a paginated list of customer objects ordered by `created_at` (descending) + */ + public async listAllCustomers(options: ListAllCustomersOptions = {}) { + return listAllCustomers({ + apiKey: this._apiKey, + ...options, + }); + } } diff --git a/src/index.ts b/src/index.ts index a101238..aba85bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,30 +1,30 @@ -export { LemonsqueezyClient } from './client'; +export { LemonsqueezyClient } from "./client"; export { - createCheckout, - getUser, - listAllCheckouts, - listAllCustomers, - listAllDiscounts, - listAllFiles, - listAllLicenseKeyInstances, - listAllLicenseKeys, - listAllOrderItems, - listAllOrders, - listAllProducts, - listAllStores, - listAllSubscriptions, - listAllVariants, - retrieveCheckout, - retrieveCustomer, - retrieveDiscount, - retrieveFile, - retrieveLicenseKey, - retrieveLicenseKeyInstance, - retrieveOrder, - retrieveOrderItem, - retrieveProduct, - retrieveStore, - retrieveSubscription, - retrieveVariant, - updateSubscription, -} from './modules'; + createCheckout, + getUser, + listAllCheckouts, + listAllCustomers, + listAllDiscounts, + listAllFiles, + listAllLicenseKeyInstances, + listAllLicenseKeys, + listAllOrderItems, + listAllOrders, + listAllProducts, + listAllStores, + listAllSubscriptions, + listAllVariants, + retrieveCheckout, + retrieveCustomer, + retrieveDiscount, + retrieveFile, + retrieveLicenseKey, + retrieveLicenseKeyInstance, + retrieveOrder, + retrieveOrderItem, + retrieveProduct, + retrieveStore, + retrieveSubscription, + retrieveVariant, + updateSubscription, +} from "./modules"; diff --git a/src/modules/checkout/checkout.types.ts b/src/modules/checkout/checkout.types.ts index 25ac72c..9e38ff6 100644 --- a/src/modules/checkout/checkout.types.ts +++ b/src/modules/checkout/checkout.types.ts @@ -1,302 +1,302 @@ import type { - BaseLemonsqueezyResponse, - LemonsqueezyDataType, - PaginatedBaseLemonsqueezyResponse, - SharedLemonsqueezyOptions, -} from '~/shared'; + BaseLemonsqueezyResponse, + LemonsqueezyDataType, + PaginatedBaseLemonsqueezyResponse, + SharedLemonsqueezyOptions, +} from "~/shared"; export interface LemonsqueezyBillingAddress { - /** - * A pre-filled billing address country in a ISO 3166-1 alpha-2 format - * - * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - */ - country?: string; - /** - * A pre-filled billing address zip/postal code - */ - zip?: string; + /** + * A pre-filled billing address country in a ISO 3166-1 alpha-2 format + * + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + country?: string; + /** + * A pre-filled billing address zip/postal code + */ + zip?: string; } export interface LemonsqueezyCheckoutData { - billing_address?: LemonsqueezyBillingAddress; - /** - * An object containing any custom data to be passed to the checkout - */ - custom?: Record; - /** - * A pre-filled discount code - */ - discount_code?: string; - /** - * A pre-filled email address - */ - email?: string; - /** - * A pre-filled name - */ - name?: string; - /** - * A pre-filled tax number - */ - tax_number?: string; + billing_address?: LemonsqueezyBillingAddress; + /** + * An object containing any custom data to be passed to the checkout + */ + custom?: Record; + /** + * A pre-filled discount code + */ + discount_code?: string; + /** + * A pre-filled email address + */ + email?: string; + /** + * A pre-filled name + */ + name?: string; + /** + * A pre-filled tax number + */ + tax_number?: string; } export interface LemonsqueezyCheckoutOptions { - /** - * A custom hex color to use for the checkout button - */ - button_color?: `#${string}`; - /** - * If `true`, use the dark theme - */ - dark?: boolean; - /** - * If `false`, hide the product description - */ - desc?: boolean; - /** - * If `false`, hide the discount code field - */ - discount?: boolean; - /** - * If `true`, show the checkout overlay - * - * @docs https://docs.lemonsqueezy.com/help/checkout/checkout-overlay - */ - embed?: boolean; - /** - * If `false`, hide the store logo - */ - logo?: boolean; - /** - * If `false`, hide the product media - */ - media?: boolean; - /** - * If false, hide the "You will be charged..." subscription preview text - */ - subscription_preview?: boolean; + /** + * A custom hex color to use for the checkout button + */ + button_color?: `#${string}`; + /** + * If `true`, use the dark theme + */ + dark?: boolean; + /** + * If `false`, hide the product description + */ + desc?: boolean; + /** + * If `false`, hide the discount code field + */ + discount?: boolean; + /** + * If `true`, show the checkout overlay + * + * @docs https://docs.lemonsqueezy.com/help/checkout/checkout-overlay + */ + embed?: boolean; + /** + * If `false`, hide the store logo + */ + logo?: boolean; + /** + * If `false`, hide the product media + */ + media?: boolean; + /** + * If false, hide the "You will be charged..." subscription preview text + */ + subscription_preview?: boolean; } export interface LemonsqueezyCheckoutPreview { - currency_rate: number; - currency: string; - discount_total_formatted: string; - discount_total_usd: number; - discount_total: number; - subtotal_formatted: string; - subtotal_usd: number; - subtotal: number; - tax_formatted: string; - tax_usd: number; - tax: number; - total_formatted: string; - total_usd: number; - total: number; + currency_rate: number; + currency: string; + discount_total_formatted: string; + discount_total_usd: number; + discount_total: number; + subtotal_formatted: string; + subtotal_usd: number; + subtotal: number; + tax_formatted: string; + tax_usd: number; + tax: number; + total_formatted: string; + total_usd: number; + total: number; } export interface LemonsqueezyProductOptions { - /** - * A custom description for the product - */ - description?: string; - /** - * An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled - */ - enabled_variants?: Array; - /** - * An array of image URLs to use as the product's media - */ - media?: Array; - /** - * A custom name for the product - */ - name?: string; - /** - * A custom text to use for the order receipt email button - */ - receipt_button_text?: string; - /** - * A custom URL to use for the order receipt email button - */ - receipt_link_url?: string; - /** - * A custom thank you note to use for the order receipt email - */ - receipt_thank_you_note?: string; - /** - * A custom URL to redirect to after a successful purchase - */ - redirect_url?: string; + /** + * A custom description for the product + */ + description?: string; + /** + * An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled + */ + enabled_variants?: Array; + /** + * An array of image URLs to use as the product's media + */ + media?: Array; + /** + * A custom name for the product + */ + name?: string; + /** + * A custom text to use for the order receipt email button + */ + receipt_button_text?: string; + /** + * A custom URL to use for the order receipt email button + */ + receipt_link_url?: string; + /** + * A custom thank you note to use for the order receipt email + */ + receipt_thank_you_note?: string; + /** + * A custom URL to redirect to after a successful purchase + */ + redirect_url?: string; } /** * @docs https://docs.lemonsqueezy.com/api/checkouts#the-checkout-object */ export interface LemonsqueezyCheckout { - attributes: { - /** - * An object containing any prefill or custom data to be used in the checkout - * - * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields - * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data - */ - checkout_data: LemonsqueezyCheckoutData; - /** - * An object containing checkout options for this checkout - */ - checkout_options: LemonsqueezyCheckoutOptions; - /** - * An ISO-8601 formatted date-time string indicating when the object was created - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - created_at: Date; - /** - * If the value is not `null`, this represents a positive integer in cents representing the custom price of the variant - */ - custom_price: number | null; - /** - * An ISO-8601 formatted date-time string indicating when the checkout expires - * - * Can be `null` if the checkout is perpetual - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - expires_at: Date | null; - preview: LemonsqueezyCheckoutPreview; - /** - * An object containing any overridden product options for this checkout - */ - product_options: LemonsqueezyProductOptions; - /** - * The ID of the store this checkout belongs to - */ - store_id: number; - /** - * A boolean indicating if the returned checkout object was created within test mode - */ - test_mode: boolean; - /** - * An ISO-8601 formatted date-time string indicating when the object was last updated - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - updated_at: Date; - /** - * The unique URL to access the checkout - * - * Note: for security reasons, download URLs are signed - * - * If the checkout `expires_at` is set, the URL will expire after the specified time - */ - url: string; - /** - * The ID of the variant associated with this checkout - */ - variant_id: number; - }; - type: LemonsqueezyDataType.checkouts; - id: string; + attributes: { + /** + * An object containing any prefill or custom data to be used in the checkout + * + * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields + * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data + */ + checkout_data: LemonsqueezyCheckoutData; + /** + * An object containing checkout options for this checkout + */ + checkout_options: LemonsqueezyCheckoutOptions; + /** + * An ISO-8601 formatted date-time string indicating when the object was created + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + created_at: Date; + /** + * If the value is not `null`, this represents a positive integer in cents representing the custom price of the variant + */ + custom_price: number | null; + /** + * An ISO-8601 formatted date-time string indicating when the checkout expires + * + * Can be `null` if the checkout is perpetual + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + expires_at: Date | null; + preview: LemonsqueezyCheckoutPreview; + /** + * An object containing any overridden product options for this checkout + */ + product_options: LemonsqueezyProductOptions; + /** + * The ID of the store this checkout belongs to + */ + store_id: number; + /** + * A boolean indicating if the returned checkout object was created within test mode + */ + test_mode: boolean; + /** + * An ISO-8601 formatted date-time string indicating when the object was last updated + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + updated_at: Date; + /** + * The unique URL to access the checkout + * + * Note: for security reasons, download URLs are signed + * + * If the checkout `expires_at` is set, the URL will expire after the specified time + */ + url: string; + /** + * The ID of the variant associated with this checkout + */ + variant_id: number; + }; + type: LemonsqueezyDataType.checkouts; + id: string; } export interface CreateCheckoutOptions extends SharedLemonsqueezyOptions { - /** - * An object containing any prefill or custom data to be used in the checkout - * - * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields - * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data - */ - checkout_data?: LemonsqueezyCheckoutData; - /** - * An object containing checkout options for this checkout - */ - checkout_options?: LemonsqueezyCheckoutOptions; - /** - * A positive integer in cents representing the custom price of the variant. - * - * Note: If the product purchased is a subscription, this custom price is used - * for all renewal payments. If the subscription's variant changes in the - * future (i.e. the customer is moved to a different subscription "tier") the - * new variant's price will be used from that moment forward. - */ - custom_price?: number | null; - /** - * An ISO-8601 formatted date-time string indicating when the checkout expires - * - * Can be `null` if the checkout is perpetual - * - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - expires_at?: Date | null; - /** - * A boolean indicating whether to return a preview of the checkout. - * - * If `true`, the checkout will include a `preview` object with the checkout preview data. - */ - preview?: boolean; - /** - * An object containing any overridden product options for this checkout. - */ - product_options?: LemonsqueezyProductOptions; - /** - * The ID of the store this checkout belongs to. - */ - store: string; - /** - * The ID of the variant associated with this checkout. - * - * Note: by default, all variants of the related product will be shown in the checkout, with - * your selected variant highlighted. If you want hide to other variants, you can utilise - * the `product_options.enabled_variants` option to determine which variant(s) are - * displayed in the checkout. - */ - variant: string; + /** + * An object containing any prefill or custom data to be used in the checkout + * + * @docs https://docs.lemonsqueezy.com/help/checkout/prefilling-checkout-fields + * @docs https://docs.lemonsqueezy.com/help/checkout/passing-custom-data + */ + checkout_data?: LemonsqueezyCheckoutData; + /** + * An object containing checkout options for this checkout + */ + checkout_options?: LemonsqueezyCheckoutOptions; + /** + * A positive integer in cents representing the custom price of the variant. + * + * Note: If the product purchased is a subscription, this custom price is used + * for all renewal payments. If the subscription's variant changes in the + * future (i.e. the customer is moved to a different subscription "tier") the + * new variant's price will be used from that moment forward. + */ + custom_price?: number | null; + /** + * An ISO-8601 formatted date-time string indicating when the checkout expires + * + * Can be `null` if the checkout is perpetual + * + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + expires_at?: Date | null; + /** + * A boolean indicating whether to return a preview of the checkout. + * + * If `true`, the checkout will include a `preview` object with the checkout preview data. + */ + preview?: boolean; + /** + * An object containing any overridden product options for this checkout. + */ + product_options?: LemonsqueezyProductOptions; + /** + * The ID of the store this checkout belongs to. + */ + store: string; + /** + * The ID of the variant associated with this checkout. + * + * Note: by default, all variants of the related product will be shown in the checkout, with + * your selected variant highlighted. If you want hide to other variants, you can utilise + * the `product_options.enabled_variants` option to determine which variant(s) are + * displayed in the checkout. + */ + variant: string; } export interface CreateCheckoutBody { - data: { - type: LemonsqueezyDataType.checkouts; - attributes: Omit; - relationships: { - store: { - data: { - id: string; - type: LemonsqueezyDataType.stores; - }; - }; - variant: { - data: { - id: string; - type: LemonsqueezyDataType.variants; - }; - }; - }; - }; + data: { + type: LemonsqueezyDataType.checkouts; + attributes: Omit; + relationships: { + store: { + data: { + id: string; + type: LemonsqueezyDataType.stores; + }; + }; + variant: { + data: { + id: string; + type: LemonsqueezyDataType.variants; + }; + }; + }; + }; } export type CreateCheckoutResult = - BaseLemonsqueezyResponse; + BaseLemonsqueezyResponse; export interface ListAllCheckoutsOptions extends SharedLemonsqueezyOptions { - /** - * Only return checkouts belonging to the store with this ID - */ - storeId?: string; - /** - * Only return checkouts belonging to the variant with this ID - */ - variantId?: string; + /** + * Only return checkouts belonging to the store with this ID + */ + storeId?: string; + /** + * Only return checkouts belonging to the variant with this ID + */ + variantId?: string; } export type ListAllCheckoutsResult = PaginatedBaseLemonsqueezyResponse< - Array + Array >; export interface RetrieveCheckoutOptions extends SharedLemonsqueezyOptions { - id: string; + id: string; } export type RetrieveCheckoutResult = - BaseLemonsqueezyResponse; + BaseLemonsqueezyResponse; diff --git a/src/modules/customer/customer.action.ts b/src/modules/customer/customer.action.ts index 1d20950..c3f1016 100644 --- a/src/modules/customer/customer.action.ts +++ b/src/modules/customer/customer.action.ts @@ -1,11 +1,11 @@ import { - ListAllCustomersOptions, - ListAllCustomersResult, - RetrieveCustomerOptions, - RetrieveCustomerResult, -} from './customer.types'; -import type { SharedModuleOptions } from '~/shared'; -import { requestLemonSqueeze } from '~/shared'; + ListAllCustomersOptions, + ListAllCustomersResult, + RetrieveCustomerOptions, + RetrieveCustomerResult, +} from "./customer.types"; +import type { SharedModuleOptions } from "~/shared"; +import { requestLemonSqueeze } from "~/shared"; /** * List all customers @@ -19,18 +19,18 @@ import { requestLemonSqueeze } from '~/shared'; * @returns Returns a paginated list of customer objects ordered by `created_at` (descending) */ export async function listAllCustomers( - options: ListAllCustomersOptions & SharedModuleOptions + options: ListAllCustomersOptions & SharedModuleOptions ): Promise { - const { storeId, email, ...rest } = options; + const { storeId, email, ...rest } = options; - return requestLemonSqueeze({ - params: { - ...(storeId ? { store_id: storeId } : {}), - ...(email ? { email: email } : {}), - }, - path: '/customers', - ...rest, - }); + return requestLemonSqueeze({ + params: { + ...(storeId ? { store_id: storeId } : {}), + ...(email ? { email: email } : {}), + }, + path: "/customers", + ...rest, + }); } /** @@ -45,12 +45,12 @@ export async function listAllCustomers( * @returns A customer object */ export async function retrieveCustomer( - options: RetrieveCustomerOptions & SharedModuleOptions + options: RetrieveCustomerOptions & SharedModuleOptions ): Promise { - const { id, ...rest } = options; + const { id, ...rest } = options; - return requestLemonSqueeze({ - path: `/customers/${id}`, - ...rest, - }); + return requestLemonSqueeze({ + path: `/customers/${id}`, + ...rest, + }); } diff --git a/src/modules/customer/customer.test.ts b/src/modules/customer/customer.test.ts index 7638374..25fe2ac 100644 --- a/src/modules/customer/customer.test.ts +++ b/src/modules/customer/customer.test.ts @@ -1,38 +1,38 @@ -import { describe, it, expect, beforeAll } from 'vitest'; - -import { listAllCustomers, retrieveCustomer } from '.'; - -describe.concurrent('Customer', () => { - const apiKey = process.env.LEMON_SQUEEZY_API_KEY as string; - - beforeAll(() => { - if (!apiKey) throw 'No LEMON_SQUEEZY_API_KEY environment variable found'; - }); - - it('Retrieve customer', async () => { - const customers = await listAllCustomers({ - apiKey, - }); - if (!customers.data.length) throw new Error('No customers found'); - - const customer = await retrieveCustomer({ - apiKey, - id: customers.data.at(0)!.id, - }); - - expect(customer).toBeDefined(); - expect(customer.data).toBeDefined(); - expect(customer.data).not.toBeNull(); - expect(customer.errors).toBeUndefined(); - }); - - it('List all customers', async () => { - const customers = await listAllCustomers({ - apiKey, - }); - - expect(customers).toBeDefined(); - expect(Array.isArray(customers.data)).toBe(true); - expect(customers.errors).toBeUndefined(); - }); +import { describe, it, expect, beforeAll } from "vitest"; + +import { listAllCustomers, retrieveCustomer } from "."; + +describe.concurrent("Customer", () => { + const apiKey = process.env.LEMON_SQUEEZY_API_KEY as string; + + beforeAll(() => { + if (!apiKey) throw "No LEMON_SQUEEZY_API_KEY environment variable found"; + }); + + it("Retrieve customer", async () => { + const customers = await listAllCustomers({ + apiKey, + }); + if (!customers.data.length) throw new Error("No customers found"); + + const customer = await retrieveCustomer({ + apiKey, + id: customers.data.at(0)!.id, + }); + + expect(customer).toBeDefined(); + expect(customer.data).toBeDefined(); + expect(customer.data).not.toBeNull(); + expect(customer.errors).toBeUndefined(); + }); + + it("List all customers", async () => { + const customers = await listAllCustomers({ + apiKey, + }); + + expect(customers).toBeDefined(); + expect(Array.isArray(customers.data)).toBe(true); + expect(customers.errors).toBeUndefined(); + }); }); diff --git a/src/modules/customer/customer.types.ts b/src/modules/customer/customer.types.ts index 44b2949..f689136 100644 --- a/src/modules/customer/customer.types.ts +++ b/src/modules/customer/customer.types.ts @@ -1,103 +1,103 @@ import type { - BaseLemonsqueezyResponse, - LemonsqueezyDataType, - PaginatedBaseLemonsqueezyResponse, - SharedLemonsqueezyOptions, -} from '~/shared'; + BaseLemonsqueezyResponse, + LemonsqueezyDataType, + PaginatedBaseLemonsqueezyResponse, + SharedLemonsqueezyOptions, +} from "~/shared"; export interface LemonsqueezyCustomer { - attributes: { - /** - * The ID of the store this customer belongs to - */ - store_id: number; - /** - * The full name of the customer - */ - name: string; - /** - * The email address of the customer - */ - email: string; - /** - * The email marketing status of the customer. - */ - status: - | 'subscribed' - | 'unsubscribed' - | 'archived' - | 'requires_verification' - | 'invalid_email' - | 'bounced'; - /** - * The city of the customer - */ - city: string; - /** - * The region of the customer - */ - region: string; - /** - * The country of the customer - */ - country: string; - /** - * A positive integer in cents representing the total revenue from the customer (USD). - */ - total_revenue_currency: number; - /** - * A positive integer in cents representing the monthly recurring revenue from the customer (USD). - */ - mrr: number; - /** - * The formatted status of the customer. - */ - status_formatted: string; - /** - * The formatted country of the customer. - */ - country_formatted: string; - /** - * A human-readable string representing the total revenue from the customer (e.g. $9.99). - */ - total_revenue_currency_formatted: string; - /** - * A human-readable string representing the monthly recurring revenue from the customer (e.g. $9.99). - */ - mrr_formatted: string; - /** - * An ISO-8601 formatted date-time string indicating when the object was created - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - created_at: Date; - /** - * An ISO-8601 formatted date-time string indicating when the object was last updated - * @see https://en.wikipedia.org/wiki/ISO_8601 - */ - updated_at: Date; - }; - type: LemonsqueezyDataType.customers; - id: string; + attributes: { + /** + * The ID of the store this customer belongs to + */ + store_id: number; + /** + * The full name of the customer + */ + name: string; + /** + * The email address of the customer + */ + email: string; + /** + * The email marketing status of the customer. + */ + status: + | "subscribed" + | "unsubscribed" + | "archived" + | "requires_verification" + | "invalid_email" + | "bounced"; + /** + * The city of the customer + */ + city: string; + /** + * The region of the customer + */ + region: string; + /** + * The country of the customer + */ + country: string; + /** + * A positive integer in cents representing the total revenue from the customer (USD). + */ + total_revenue_currency: number; + /** + * A positive integer in cents representing the monthly recurring revenue from the customer (USD). + */ + mrr: number; + /** + * The formatted status of the customer. + */ + status_formatted: string; + /** + * The formatted country of the customer. + */ + country_formatted: string; + /** + * A human-readable string representing the total revenue from the customer (e.g. $9.99). + */ + total_revenue_currency_formatted: string; + /** + * A human-readable string representing the monthly recurring revenue from the customer (e.g. $9.99). + */ + mrr_formatted: string; + /** + * An ISO-8601 formatted date-time string indicating when the object was created + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + created_at: Date; + /** + * An ISO-8601 formatted date-time string indicating when the object was last updated + * @see https://en.wikipedia.org/wiki/ISO_8601 + */ + updated_at: Date; + }; + type: LemonsqueezyDataType.customers; + id: string; } export interface ListAllCustomersOptions extends SharedLemonsqueezyOptions { - /** - * Only return checkouts belonging to the store with this ID - */ - storeId?: string; - /** - * Only return customers where the email field is equal to this email address. - */ - email?: string; + /** + * Only return checkouts belonging to the store with this ID + */ + storeId?: string; + /** + * Only return customers where the email field is equal to this email address. + */ + email?: string; } export type ListAllCustomersResult = PaginatedBaseLemonsqueezyResponse< - Array + Array >; export interface RetrieveCustomerOptions extends SharedLemonsqueezyOptions { - id: string; + id: string; } export type RetrieveCustomerResult = - BaseLemonsqueezyResponse; + BaseLemonsqueezyResponse; diff --git a/src/modules/customer/index.ts b/src/modules/customer/index.ts index 044b204..9974472 100644 --- a/src/modules/customer/index.ts +++ b/src/modules/customer/index.ts @@ -1,16 +1,16 @@ -import { listAllCustomers, retrieveCustomer } from './customer.action'; +import { listAllCustomers, retrieveCustomer } from "./customer.action"; export { listAllCustomers, retrieveCustomer }; export type { - LemonsqueezyCustomer, - ListAllCustomersOptions, - ListAllCustomersResult, - RetrieveCustomerOptions, - RetrieveCustomerResult, -} from './customer.types'; + LemonsqueezyCustomer, + ListAllCustomersOptions, + ListAllCustomersResult, + RetrieveCustomerOptions, + RetrieveCustomerResult, +} from "./customer.types"; export default { - listAllCustomers, - retrieveCustomer, + listAllCustomers, + retrieveCustomer, } as const; diff --git a/src/modules/index.ts b/src/modules/index.ts index b9bd935..d98ebbb 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,14 +1,14 @@ -export * from './checkout'; -export * from './customer'; -export * from './discount'; -export * from './file'; -export * from './licenseKey'; -export * from './licenseKeyInstance'; -export * from './order'; -export * from './orderItem'; -export * from './product'; -export * from './store'; -export * from './subscription'; -export * from './subscriptionInvoice'; -export * from './user'; -export * from './variant'; +export * from "./checkout"; +export * from "./customer"; +export * from "./discount"; +export * from "./file"; +export * from "./licenseKey"; +export * from "./licenseKeyInstance"; +export * from "./order"; +export * from "./orderItem"; +export * from "./product"; +export * from "./store"; +export * from "./subscription"; +export * from "./subscriptionInvoice"; +export * from "./user"; +export * from "./variant"; diff --git a/src/shared/shared.types.ts b/src/shared/shared.types.ts index 6bbf2a2..71c5b8d 100644 --- a/src/shared/shared.types.ts +++ b/src/shared/shared.types.ts @@ -1,86 +1,86 @@ -import type { RequestInit } from 'undici'; +import type { RequestInit } from "undici"; export interface SharedModuleOptions { - apiKey: string; - page?: number; - include?: Array; + apiKey: string; + page?: number; + include?: Array; } export interface SharedLemonsqueezyOptions { - apiVersion?: 'v1'; - baseUrl?: string; + apiVersion?: "v1"; + baseUrl?: string; } export interface LemonsqueezyOptions< - TData extends Record = Record -> extends Omit, - SharedLemonsqueezyOptions, - SharedModuleOptions { - data?: TData; - params?: Record; - method?: - | 'CONNECT' - | 'DELETE' - | 'GET' - | 'HEAD' - | 'OPTIONS' - | 'PATCH' - | 'POST' - | 'PUT' - | 'TRACE'; - path: string; + TData extends Record = Record +> extends Omit, + SharedLemonsqueezyOptions, + SharedModuleOptions { + data?: TData; + params?: Record; + method?: + | "CONNECT" + | "DELETE" + | "GET" + | "HEAD" + | "OPTIONS" + | "PATCH" + | "POST" + | "PUT" + | "TRACE"; + path: string; } export enum LemonsqueezyDataType { - checkouts = 'checkouts', - customers = 'customers', - discounts = 'discounts', - files = 'files', - license_key_instances = 'license-key-instances', - license_keys = 'license-keys', - order_items = 'order-items', - orders = 'orders', - products = 'products', - stores = 'stores', - subscriptions = 'subscriptions', - subscription_invoices = 'subscription-invoices', - users = 'users', - variants = 'variants', + checkouts = "checkouts", + customers = "customers", + discounts = "discounts", + files = "files", + license_key_instances = "license-key-instances", + license_keys = "license-keys", + order_items = "order-items", + orders = "orders", + products = "products", + stores = "stores", + subscriptions = "subscriptions", + subscription_invoices = "subscription-invoices", + users = "users", + variants = "variants", } export interface BaseLemonsqueezyResponse< - TData, - TLinks = { - self: string; - } + TData, + TLinks = { + self: string; + } > { - data: TData; - errors?: Array<{ - detail: string; - status: string | number; - title: string; - }>; - jsonapi: { - version: string; - }; - links: TLinks; + data: TData; + errors?: Array<{ + detail: string; + status: string | number; + title: string; + }>; + jsonapi: { + version: string; + }; + links: TLinks; } export interface PaginatedBaseLemonsqueezyResponse< - TData, - TLinks = { - first: string; - last: string; - } + TData, + TLinks = { + first: string; + last: string; + } > extends BaseLemonsqueezyResponse { - meta: { - page: { - currentPage: number; - from: number; - lastPage: number; - perPage: number; - to: number; - total: number; - }; - }; + meta: { + page: { + currentPage: number; + from: number; + lastPage: number; + perPage: number; + to: number; + total: number; + }; + }; } From 98d24cdf18a46d7e134e59db11f049f81c9f76a4 Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Tue, 4 Jul 2023 23:03:56 +0200 Subject: [PATCH 6/7] initial --- src/modules/webhook/index.ts | 0 src/modules/webhook/webhook.action.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/modules/webhook/index.ts create mode 100644 src/modules/webhook/webhook.action.ts diff --git a/src/modules/webhook/index.ts b/src/modules/webhook/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/webhook/webhook.action.ts b/src/modules/webhook/webhook.action.ts new file mode 100644 index 0000000..f8efa36 --- /dev/null +++ b/src/modules/webhook/webhook.action.ts @@ -0,0 +1,26 @@ +import crypto from 'crypto'; + +/** + * Construct event + * + * @description Constructs an event object + * + * @param {String} payload - Raw text body received from Lemonsqueezy + * @param {String} header - Value of the `X-Signature` header received from Lemonsqueezy + * @param {String} secret - Your Lemonsqueezy webhook signing secret + * + * @returns An event object + */ +export function constructEvent( + payload: string, + header: string, + secret: string, +) { + const hmac = crypto.createHmac('sha256', secret); + const digest = Buffer.from(hmac.update(payload).digest('hex'), 'utf8'); + const signature = Buffer.from(header, 'utf8'); + + if (!crypto.timingSafeEqual(digest, signature)) { + throw new Error('Invalid signature.'); + } +} \ No newline at end of file From 94b177d05c6066c6549ac480a77e5b168462b41a Mon Sep 17 00:00:00 2001 From: carstenlebek Date: Wed, 5 Jul 2023 14:13:00 +0200 Subject: [PATCH 7/7] feat: add webhook event constructor --- src/client/client.class.ts | 21 +++++ src/index.ts | 1 + src/modules/index.ts | 1 + src/modules/webhook/README.md | 53 +++++++++++ src/modules/webhook/index.ts | 9 ++ src/modules/webhook/webhook.action.ts | 58 +++++++++--- src/modules/webhook/webhook.test.ts | 1 + src/modules/webhook/webhook.types.ts | 131 ++++++++++++++++++++++++++ src/types.ts | 2 + 9 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 src/modules/webhook/README.md create mode 100644 src/modules/webhook/webhook.test.ts create mode 100644 src/modules/webhook/webhook.types.ts diff --git a/src/client/client.class.ts b/src/client/client.class.ts index 9640c9d..8ea3f86 100644 --- a/src/client/client.class.ts +++ b/src/client/client.class.ts @@ -1,4 +1,5 @@ import { + constructEvent, createCheckout, getUser, listAllCheckouts, @@ -61,6 +62,7 @@ import type { RetrieveVariantOptions, UpdateSubscriptionOptions, } from "~/modules"; +import { LemonsqueezyWebhookPayload } from "~/modules/webhook/webhook.types"; export class LemonsqueezyClient { private _apiKey: string; @@ -613,4 +615,23 @@ export class LemonsqueezyClient { ...options, }); } + + /** + * Construct webhook event + * + * @description Constructs an event object + * + * @param {String | Uint8Array} payload - Raw text body received from Lemonsqueezy + * @param {String} header - Value of the `X-Signature` header received from Lemonsqueezy + * @param {String} secret - Your Lemonsqueezy webhook signing secret + * + * @returns An event object + */ + public constructEvent( + payload: LemonsqueezyWebhookPayload, + header: string, + secret: string + ) { + return constructEvent(payload, header, secret); + } } diff --git a/src/index.ts b/src/index.ts index aba85bb..b3fb13e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { LemonsqueezyClient } from "./client"; export { + constructEvent, createCheckout, getUser, listAllCheckouts, diff --git a/src/modules/index.ts b/src/modules/index.ts index d98ebbb..1d72bb1 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -12,3 +12,4 @@ export * from "./subscription"; export * from "./subscriptionInvoice"; export * from "./user"; export * from "./variant"; +export * from "./webhook"; diff --git a/src/modules/webhook/README.md b/src/modules/webhook/README.md new file mode 100644 index 0000000..810f0f1 --- /dev/null +++ b/src/modules/webhook/README.md @@ -0,0 +1,53 @@ +## 🪝 Webhook + +[![Docs](https://img.shields.io/badge/-Docs-blue.svg?style=for-the-badge)](https://docs.lemonsqueezy.com/help/webhooks) + +```typescript +import { LemonsqueezyClient } from "lemonsqueezy.ts"; + +const client = new LemonsqueezyClient("YOUR_API_KEY"); + +const event = client.constructEvent( + "RAW_REQUEST_BODY", + "X-Signature HEADER_VALUE", + "YOUR_WEBHOOK_SECRET" +) + +switch (event.type) { + case "order_created": + const order = event.data; + break; + case "order_refunded": + const order = event.data; + break; + case "subscription_created": + const subscription = event.data; + break; + default: + break; +} +``` + +```typescript +import { constructEvent } from "lemonsqueezy.ts"; + +const event = constructEvent( + "RAW_REQUEST_BODY", + "X-Signature HEADER_VALUE", + "YOUR_WEBHOOK_SECRET" +) + +switch (event.type) { + case "order_created": + const order = event.data; + break; + case "order_refunded": + const order = event.data; + break; + case "subscription_created": + const subscription = event.data; + break; + default: + break; +} +``` \ No newline at end of file diff --git a/src/modules/webhook/index.ts b/src/modules/webhook/index.ts index e69de29..3496f7a 100644 --- a/src/modules/webhook/index.ts +++ b/src/modules/webhook/index.ts @@ -0,0 +1,9 @@ +import { constructEvent } from "./webhook.action"; + +export { constructEvent }; + +export type { LemonsqueezyWebhookEvent } from "./webhook.types"; + +export default { + constructEvent, +} as const; diff --git a/src/modules/webhook/webhook.action.ts b/src/modules/webhook/webhook.action.ts index f8efa36..dd63802 100644 --- a/src/modules/webhook/webhook.action.ts +++ b/src/modules/webhook/webhook.action.ts @@ -1,26 +1,54 @@ -import crypto from 'crypto'; +import crypto from "crypto"; +import { + LemonsqueezyWebhookEvent, + LemonsqueezyWebhookPayload, + LemonsqueezyWebhookPayloadJson, +} from "./webhook.types"; + +function verifySignature( + payload: LemonsqueezyWebhookPayload, + header: string, + secret: string +) { + const hmac = crypto.createHmac("sha256", secret); + const digest = Buffer.from(hmac.update(payload).digest("hex"), "utf8"); + const signature = Buffer.from(header, "utf8"); + + return crypto.timingSafeEqual(digest, signature); +} /** * Construct event - * + * * @description Constructs an event object - * + * * @param {String} payload - Raw text body received from Lemonsqueezy * @param {String} header - Value of the `X-Signature` header received from Lemonsqueezy * @param {String} secret - Your Lemonsqueezy webhook signing secret - * + * * @returns An event object */ export function constructEvent( - payload: string, - header: string, - secret: string, -) { - const hmac = crypto.createHmac('sha256', secret); - const digest = Buffer.from(hmac.update(payload).digest('hex'), 'utf8'); - const signature = Buffer.from(header, 'utf8'); + payload: LemonsqueezyWebhookPayload, + header: string, + secret: string +): LemonsqueezyWebhookEvent { + if (!verifySignature(payload, header, secret)) { + throw new Error("Invalid signature."); + } + + const jsonPayload: LemonsqueezyWebhookPayloadJson = + payload instanceof Uint8Array + ? JSON.parse(new TextDecoder("utf8").decode(payload)) + : JSON.parse(payload); + + const { meta, data } = jsonPayload; + + const event: LemonsqueezyWebhookEvent = { + type: meta.event_name, + custom_data: meta.custom_data, + data, + }; - if (!crypto.timingSafeEqual(digest, signature)) { - throw new Error('Invalid signature.'); - } -} \ No newline at end of file + return event; +} diff --git a/src/modules/webhook/webhook.test.ts b/src/modules/webhook/webhook.test.ts new file mode 100644 index 0000000..e46b0a0 --- /dev/null +++ b/src/modules/webhook/webhook.test.ts @@ -0,0 +1 @@ +// TODO: Create tests for webhook module diff --git a/src/modules/webhook/webhook.types.ts b/src/modules/webhook/webhook.types.ts new file mode 100644 index 0000000..2448502 --- /dev/null +++ b/src/modules/webhook/webhook.types.ts @@ -0,0 +1,131 @@ +import { LemonsqueezyLicenseKey } from "../licenseKey"; +import { LemonsqueezyOrder } from "../order"; +import { LemonsqueezySubscription } from "../subscription"; +import { LemonsqueezySubscriptionInvoice } from "../subscriptionInvoice"; + +export type LemonsqueezyWebhookPayload = string | Uint8Array; + +enum LemonsqueezyEventNames { + OrderCreated = "order_created", + OrderRefunded = "order_refunded", + SubscriptionCreated = "subscription_created", + SubscriptionUpdated = "subscription_updated", + SubscriptionCancelled = "subscription_cancelled", + SubscriptionResumed = "subscription_resumed", + SubscriptionExpired = "subscription_expired", + SubscriptionPaused = "subscription_paused", + SubscriptionUnpaused = "subscription_unpaused", + SubscriptionPaymentSuccess = "subscription_payment_success", + SubscriptionPaymentFailed = "subscription_payment_failed", + SubscriptionPaymentRecovered = "subscription_payment_recovered", + LicenseKeyCreated = "license_key_created", + LicenseKeyUpdated = "license_key_updated", +} + +export type LemonsqueezyWebhookPayloadJson = { + meta: { + event_name: LemonsqueezyEventNames; + custom_data?: Record; + }; + data: any; +}; + +type OrderCreatedEvent = { + type: LemonsqueezyEventNames.OrderCreated; + custom_data?: Record; + data: LemonsqueezyOrder; +}; + +type OrderRefundedEvent = { + type: LemonsqueezyEventNames.OrderRefunded; + custom_data?: Record; + data: LemonsqueezyOrder; +}; + +type SubscriptionCreatedEvent = { + type: LemonsqueezyEventNames.SubscriptionCreated; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionUpdatedEvent = { + type: LemonsqueezyEventNames.SubscriptionUpdated; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionCancelledEvent = { + type: LemonsqueezyEventNames.SubscriptionCancelled; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionResumedEvent = { + type: LemonsqueezyEventNames.SubscriptionResumed; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionExpiredEvent = { + type: LemonsqueezyEventNames.SubscriptionExpired; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionPausedEvent = { + type: LemonsqueezyEventNames.SubscriptionPaused; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionUnpausedEvent = { + type: LemonsqueezyEventNames.SubscriptionUnpaused; + custom_data?: Record; + data: LemonsqueezySubscription; +}; + +type SubscriptionPaymentSuccessEvent = { + type: LemonsqueezyEventNames.SubscriptionPaymentSuccess; + custom_data?: Record; + data: LemonsqueezySubscriptionInvoice; +}; + +type SubscriptionPaymentFailedEvent = { + type: LemonsqueezyEventNames.SubscriptionPaymentFailed; + custom_data?: Record; + data: LemonsqueezySubscriptionInvoice; +}; + +type SubscriptionPaymentRecoveredEvent = { + type: LemonsqueezyEventNames.SubscriptionPaymentRecovered; + custom_data?: Record; + data: LemonsqueezySubscriptionInvoice; +}; + +type LicenseKeyCreatedEvent = { + type: LemonsqueezyEventNames.LicenseKeyCreated; + custom_data?: Record; + data: LemonsqueezyLicenseKey; +}; + +type LicenseKeyUpdatedEvent = { + type: LemonsqueezyEventNames.LicenseKeyUpdated; + custom_data?: Record; + data: LemonsqueezyLicenseKey; +}; + +export type LemonsqueezyWebhookEvent = + | OrderCreatedEvent + | OrderRefundedEvent + | SubscriptionCreatedEvent + | SubscriptionUpdatedEvent + | SubscriptionCancelledEvent + | SubscriptionResumedEvent + | SubscriptionExpiredEvent + | SubscriptionPausedEvent + | SubscriptionUnpausedEvent + | SubscriptionPaymentSuccessEvent + | SubscriptionPaymentFailedEvent + | SubscriptionPaymentRecoveredEvent + | LicenseKeyCreatedEvent + | LicenseKeyUpdatedEvent; diff --git a/src/types.ts b/src/types.ts index 6559860..f7a2099 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,3 +100,5 @@ export type { RetrieveVariantOptions, RetrieveVariantResult, } from "~/modules/variant/variant.types"; + +export type { LemonsqueezyWebhookEvent } from "~/modules/webhook/webhook.types";