diff --git a/README.md b/README.md index 5c35a87..cf5c900 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,10 @@ const subscription = await ls.cancelSubscription({ id: 123 }); You can use `include` in every "read" method to pull in [related resources](https://docs.lemonsqueezy.com/api#including-related-resources) (works for both individual and list methods). +Note: In v1.0.3 and lower, `include` was a string of object names. Now it should be an array of strings. + ```javascript -const product = await ls.getProduct({ id: 123, include: "variants" }); +const product = await ls.getProduct({ id: 123, include: ["store", "variants"] }); ``` ### Pagination @@ -1279,26 +1281,4 @@ Delete a webhook. ```javascript await ls.deleteWebhook({ id: 123 }) -``` - -## Development - -To get started developing this project locally, clone the repository & install the dependencies: - -``` -git clone https://github.com/lmsqueezy/lemonsqueezy.js.git -cd lemonsqueezy-js -npm install -``` - -To create a new build: - -```bash -npm run build -``` - -To start the local development server: - -```bash -npm run dev -``` +``` \ No newline at end of file diff --git a/package.json b/package.json index 94e3950..4d915b1 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,11 @@ { "name": "@lemonsqueezy/lemonsqueezy.js", "description": "The official Lemon Squeezy JavaScript SDK.", - "version": "1.0.3", + "version": "1.1.0", "author": "Lemon Squeezy", "license": "MIT", "main": "dist/index.js", "module": "dist/index.mjs", - "types": "dist/index.d.ts", "homepage": "https://github.com/lmsqueezy/lemonsqueezy.js", "repository": { "type": "git", @@ -18,7 +17,8 @@ "keywords": [ "api", "lemonsqueezy", - "javascript" + "javascript", + "typescript" ], "files": [ "./dist/*", @@ -33,5 +33,6 @@ "@types/node": "^20.4.5", "tsup": "^7.1.0", "typescript": "^5.1.6" - } + }, + "type": "module" } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 343a883..0000000 --- a/src/index.js +++ /dev/null @@ -1,821 +0,0 @@ - -export default class LemonSqueezy { - - apiKey; - apiUrl = 'https://api.lemonsqueezy.com/'; - - constructor(apiKey) { - this.apiKey = apiKey; - } - - - /** - * Builds a params object for the API query based on provided and allowed filters. - * Also converts pagination parameters `page` to `page[number]` and `perPage` to `page[size]` - * @params {Object} [args] Arguments to the API method - * @params {string[]} [allowedFilters] List of filters the API query permits (camelCase) - */ - buildParams(args, allowedFilters = []) { - let params = {} - for (let filter in args) { - if (allowedFilters.includes(filter)) { - let queryFilter = filter.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); - params['filter['+queryFilter+']'] = args[filter] - } else { - if (filter == 'include') { - params['include'] = args[filter] - } - if (filter == 'page') { - params['page[number]'] = args[filter] - } - if (filter == 'perPage') { - params['page[size]'] = args[filter] - } - } - } - return params - } - - /** - * Base API query - * @param {string} path - * @param {string} [method] POST, GET, PATCH, DELETE - * @param {Object} [params] URL query parameters - * @param {Object} [payload] Object/JSON payload - * @returns {Object} JSON - */ - async queryApi({ - path, - method = 'GET', - params, - payload - }) { - - try { - - // Prepare URL - const url = new URL(path, this.apiUrl); - if (params && method === "GET") { - Object.entries(params).forEach(([key, value]) => - url.searchParams.append(key, value) - ); - } - - // fetch options - const options = { - headers: { - Accept: "application/vnd.api+json", - Authorization: `Bearer ${this.apiKey}`, - "Content-Type": "application/vnd.api+json" - }, - method - } - - if (payload) { - options['body'] = JSON.stringify(payload) - } - - const response = await fetch(url.href, options); - - if (!response.ok) { - let errorsJson = await response.json() - throw { - status: response.status, - message: response.statusText, - errors: errorsJson.errors, - }; - } - - if (method !== 'DELETE') { - return await response.json(); - } - - } catch (error) { - throw error; - } - } - - /** - * Get current user - * @returns {Object} JSON - */ - async getUser() { - return this.queryApi({ path: 'v1/users/me' }); - } - - /** - * Get stores - * @param {Object} [params] - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"products,discounts,license-keys,subscriptions,webhooks"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getStores(params = {}) { - params = this.buildParams(params) - return this.queryApi({ path: 'v1/stores', params }); - } - - /** - * Get a store - * @param {Object} params - * @param {number} params.id - * @param {"products,discounts,license-keys,subscriptions,webhooks"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getStore({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getStore().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/stores/'+id, params }); - } - - /** - * Get products - * @param {Object} [params] - * @param {number} [params.storeId] Filter products by store - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,variants"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getProducts(params = {}) { - params = this.buildParams(params, ['storeId']) - return this.queryApi({ path: 'v1/products', params }); - } - - /** - * Get a product - * @param {Object} params - * @param {number} params.id - * @param {"store,variants"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getProduct({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getProduct().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/products/'+id, params }); - } - - /** - * Get variants - * @param {Object} [params] - * @param {number} [params.productId] Filter variants by product - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"product,files"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getVariants(params = {}) { - params = this.buildParams(params, ['productId']) - return this.queryApi({ path: 'v1/variants', params }); - } - - /** - * Get a variant - * @param {Object} params - * @param {number} params.id - * @param {product,files} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getVariant({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getVariant().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/variants/'+id, params }); - } - - /** - * Get checkouts - * @param {Object} [params] - * @param {number} [params.storeId] Filter variants by store - * @param {number} [params.variantId] Filter checkouts by variant - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getCheckouts(params = {}) { - params = this.buildParams(params, ['storeId', 'variantId']) - return this.queryApi({ path: 'v1/checkouts', params }); - } - - /** - * Get a checkout - * @param {Object} params - * @param {string} params.id - * @param {"store,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getCheckout({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getCheckout().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/checkouts/'+id, params }); - } - - /** - * Create a checkout - * @param {Object} params - * @param {number} params.storeId - * @param {number} params.variantId - * @param {Object} [params.attributes] An object of values used to configure the checkout - * https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout - * @returns {Object} JSON - */ - async createCheckout({ storeId, variantId, attributes = {} } = {}) { - if (!storeId) throw 'You must provide a store ID in createCheckout().' - if (!variantId) throw 'You must provide a variant ID in createCheckout().' - let payload = { - 'data': { - 'type': 'checkouts', - 'attributes': attributes, - 'relationships': { - 'store': { - 'data': { - 'type': 'stores', - 'id': '' + storeId // convert to string - } - }, - 'variant': { - 'data': { - 'type': 'variants', - 'id': '' + variantId // convert to string - } - } - } - } - } - return this.queryApi({ path: 'v1/checkouts', method: 'POST', payload }); - } - - /** - * Get customers - * @param {Object} [params] - * @param {number} [params.storeId] Filter customers by store - * @param {number} [params.email] Filter customers by email address - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"license-keys,orders,store,subscriptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getCustomers(params = {}) { - params = this.buildParams(params, ['storeId', 'email']) - return this.queryApi({ path: 'v1/customers', params }); - } - - /** - * Get a customer - * @param {Object} params - * @param {number} params.id - * @param {"license-keys,orders,store,subscriptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getCustomer({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getCustomer().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/customers/'+id, params }); - } - - /** - * Get orders - * @param {Object} [params] - * @param {number} [params.storeId] Filter orders by store - * @param {number} [params.userEmail] Filter orders by email address - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"customer,discount-redemptions,license-keys,order-items,store,subscriptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getOrders(params = {}) { - params = this.buildParams(params, ['storeId', 'userEmail']) - return this.queryApi({ path: 'v1/orders', params }); - } - - /** - * Get an order - * @param {Object} params - * @param {number} params.id - * @param {"customer,discount-redemptions,license-keys,order-items,store,subscriptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getOrder({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getOrder().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/orders/'+id, params }); - } - - /** - * Get files - * @param {Object} [params] - * @param {number} [params.variantId] Filter orders by variant - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getFiles(params = {}) { - params = this.buildParams(params, ['variantId']) - return this.queryApi({ path: 'v1/files', params }); - } - - /** - * Get a file - * @param {Object} params - * @param {number} params.id - * @param {"variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getFile({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getFile().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/files/'+id, params }); - } - - /** - * Get order items - * @param {Object} [params] - * @param {number} [params.orderId] Filter order items by order - * @param {number} [params.productId] Filter order items by product - * @param {number} [params.variantId] Filter order items by variant - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"order,product,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getOrderItems(params = {}) { - params = this.buildParams(params, ['orderId', 'productId', 'variantId']) - return this.queryApi({ path: 'v1/order-items', params }); - } - - /** - * Get an order item - * @param {Object} params - * @param {number} params.id - * @param {"order,product,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getOrderItem({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getOrderItem().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/order-items/'+id, params }); - } - - /** - * Get subscriptions - * @param {Object} [params] - * @param {number} [params.storeId] Filter subscriptions by store - * @param {number} [params.orderId] Filter subscriptions by order - * @param {number} [params.orderItemId] Filter subscriptions by order item - * @param {number} [params.productId] Filter subscriptions by product - * @param {number} [params.variantId] Filter subscriptions by variant - * @param {"on_trial"|"active"|"paused"|"past_due"|"unpaid"|"cancelled"|"expired"} [params.status] Filter subscriptions by status - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,customer,order,order-item,product,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getSubscriptions(params = {}) { - params = this.buildParams(params, ['storeId', 'orderId', 'orderItemId', 'productId', 'variantId', 'status']) - return this.queryApi({ path: 'v1/subscriptions', params }); - } - - - /** - * Get a subscription - * @param {Object} params - * @param {number} params.id - * @param {"store,customer,order,order-item,product,variant"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getSubscription({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getSubscription().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/subscriptions/'+id, params }); - } - - /** - * Update a subscription's plan - * @param {Object} params - * @param {number} params.id - * @param {number} [params.variantId] ID of variant (required if changing plans) - * @param {number} [params.productId] ID of product (required if changing plans) - * @param {number} [params.billingAnchor] Set the billing day (1–31) used for renewal charges - * @param {"immediate"|"disable"} [params.proration] If not included, proration will occur at the next renewal date. - * Use 'immediate' to charge a prorated amount immediately. - * Use 'disable' to charge a full ammount immediately. - * @returns {Object} JSON - */ - async updateSubscription({ id, variantId, productId, billingAnchor, proration } = {}) { - if (!id) throw 'You must provide an ID in updateSubscription().' - let attributes = { - variant_id: variantId, - product_id: productId, - billing_anchor: billingAnchor - } - if (proration == 'disable') attributes.disable_prorations = true - if (proration == 'immediate') attributes.invoice_immediately = true - let payload = { - data: { - type: 'subscriptions', - id: '' + id, - attributes - } - } - return this.queryApi({ path: 'v1/subscriptions/'+id, method: 'PATCH', payload }); - } - - /** - * Cancel a subscription - * @param {Object} params - * @param {number} params.id - * @returns {Object} JSON - */ - async cancelSubscription({ id }) { - if (!id) throw 'You must provide an ID in cancelSubscription().' - return this.queryApi({ path: 'v1/subscriptions/'+id, method: 'DELETE' }); - } - - /** - * Resume (un-cancel) a subscription - * @param {Object} params - * @param {number} params.id - * @returns {Object} JSON - */ - async resumeSubscription({ id }) { - if (!id) throw 'You must provide an ID in resumeSubscription().' - let payload = { - data: { - type: 'subscriptions', - id: '' + id, - attributes: { - cancelled: false, - } - } - } - return this.queryApi({ path: 'v1/subscriptions/'+id, method: 'PATCH', payload }); - } - - /** - * Pause a subscription - * @param {Object} params - * @param {number} params.id - * @param {"void"|"free"} [params.mode] Pause mode: "void" (default) or "free" - * @param {string} [params.resumesAt] Date to automatically resume the subscription (ISO 8601 format) - * @returns {Object} JSON - */ - async pauseSubscription({ id, mode, resumesAt } = {}) { - if (!id) throw 'You must provide an ID in pauseSubscription().' - let pause = { mode: 'void' } - if (mode) pause.mode = mode - if (resumesAt) pause.resumes_at = resumesAt - let payload = { - data: { - type: 'subscriptions', - id: '' + id, - attributes: { pause } - } - } - return this.queryApi({ path: 'v1/subscriptions/'+id, method: 'PATCH', payload }); - } - - /** - * Unpause a subscription - * @param {Object} params - * @param {number} params.id - * @returns {Object} JSON - */ - async unpauseSubscription({ id }) { - if (!id) throw 'You must provide an ID in unpauseSubscription().' - let payload = { - data: { - type: 'subscriptions', - id: '' + id, - attributes: { pause: null } - } - } - return this.queryApi({ path: 'v1/subscriptions/'+id, method: 'PATCH', payload }); - } - - /** - * Get subscription invoices - * @param {Object} [params] - * @param {number} [params.storeId] Filter subscription invoices by store - * @param {"paid"|"pending"|"void"|"refunded"} [params.status] Filter subscription invoices by status - * @param {boolean} [params.refunded] Filter subscription invoices by refunded - * @param {number} [params.subscriptionId] Filter subscription invoices by subscription - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,subscription"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getSubscriptionInvoices(params = {}) { - params = this.buildParams(params, ['storeId', 'status', 'refunded', 'subscriptionId']) - return this.queryApi({ path: 'v1/subscription-invoices', params }); - } - - /** - * Get a subscription invoice - * @param {Object} params - * @param {number} params.id - * @param {"store,subscription"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getSubscriptionInvoice({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getSubscriptionInvoice().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/subscription-invoices/'+id, params }); - } - - /** - * Get discounts - * @param {Object} [params] - * @param {number} [params.storeId] Filter discounts by store - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,variants,discount-redemptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getDiscounts(params = {}) { - params = this.buildParams(params, ['storeId']) - return this.queryApi({ path: 'v1/discounts', params }); - } - - /** - * Get a discount - * @param {Object} params - * @param {number} params.id - * @param {"store,variants,discount-redemptions"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getDiscount({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getDiscount().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/discounts/'+id, params }); - } - - /** - * Create a discount - * @param {Object} params - * @param {number} params.storeId Store to create a discount in - * @param {string} params.name Name of discount - * @param {string} params.code Discount code (uppercase letters and numbers, between 3 and 256 characters) - * @param {number} params.amount Amount discount code is for - * @param {"percent"|"fixed"} [params.amountType] Type of discount - * @param {"once"|"repeating"|"forever"} [params.duration] Duration of discount - * @param {number} [params.durationInMonths] Number of months to repeat the discount for - * @param {number[]} [params.variantIds] Limit the discount to certain variants - * @param {number} [params.maxRedemptions] Limit the total number of redemptions allowed - * @param {number} [params.startsAt] Date the discount code starts on (ISO 8601 format) - * @param {number} [params.expiresAt] Date the discount code expires on (ISO 8601 format) - * @returns {Object} JSON - */ - async createDiscount({ - storeId, - name, - code, - amount, - amountType='percent', - duration='once', - durationInMonths, - variantIds, - maxRedemptions, - startsAt, - expiresAt - }) { - if (!storeId) throw 'You must include a `storeId` in createDiscount().' - if (!name) throw 'You must include a `name` in createDiscount().' - if (!code) throw 'You must include a `code` in createDiscount().' - if (!amount) throw 'You must include an `amount` in createDiscount().' - let attributes = { - name, - code, - amount, - amount_type: amountType, - duration, - starts_at: startsAt, - expires_at: expiresAt - } - if (durationInMonths && duration != 'once') { - attributes.duration_in_months = durationInMonths - } - if (maxRedemptions) { - attributes.is_limited_redemptions = true - attributes.max_redemptions = maxRedemptions - } - let payload = { - data: { - type: 'discounts', - attributes, - relationships: { - store: { - data: { - type: 'stores', - id: '' + storeId - } - } - } - } - } - if (variantIds) { - let variantData = [] - for (var i = 0; i < variantIds.length; i++) { - variantData.push({ type: 'variants', id: '' + variantIds[i] }) - } - payload.data.attributes.is_limited_to_products = true - payload.data.relationships.variants = { - data: variantData - } - } - return this.queryApi({ path: 'v1/discounts', method: 'POST', payload }); - } - - /** - * Delete a discount - * @param {Object} params - * @param {number} params.id - */ - async deleteDiscount({ id }) { - if (!id) throw 'You must provide an ID in deleteDiscount().' - this.queryApi({ path: 'v1/discounts/'+id, method: 'DELETE' }); - } - - /** - * Get discount redemptions - * @param {Object} [params] - * @param {number} [params.discountId] Filter discount redemptions by discount - * @param {number} [params.orderId] Filter discount redemptions by order - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"discount,order"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getDiscountRedemptions(params = {}) { - params = this.buildParams(params, ['discountId', 'orderId']) - return this.queryApi({ path: 'v1/discount-redemptions', params }); - } - - /** - * Get a discount redemption - * @param {Object} params - * @param {number} params.id - * @param {"discount,order"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getDiscountRedemption({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getDiscountRedemption().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/discount-redemptions/'+id, params }); - } - - /** - * Get license keys - * @param {Object} [params] - * @param {number} [params.storeId] Filter license keys by store - * @param {number} [params.orderId] Filter license keys by order - * @param {number} [params.orderItemId] Filter license keys by order item - * @param {number} [params.productId] Filter license keys by product - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store,customer,order,order-item,product,license-key-instances"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getLicenseKeys(params = {}) { - params = this.buildParams(params, ['storeId', 'orderId', 'orderItemId', 'productId']) - return this.queryApi({ path: 'v1/license-keys', params }); - } - - /** - * Get a license key - * @param {Object} params - * @param {number} params.id - * @param {"store,customer,order,order-item,product,license-key-instances"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getLicenseKey({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getLicenseKey().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/license-keys/'+id, params }); - } - - /** - * Get license key instances - * @param {Object} [params] - * @param {number} [params.licenseKeyId] Filter license keys instances by license key - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"license-key"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getLicenseKeyInstances(params = {}) { - params = this.buildParams(params, ['licenseKeyId']) - return this.queryApi({ path: 'v1/license-key-instances', params }); - } - - /** - * Get a license key instance - * @param {Object} params - * @param {number} params.id - * @param {"license-key"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getLicenseKeyInstance({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getLicenseKeyInstance().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/license-key-instances/'+id, params }); - } - - /** - * Get webhooks - * @param {Object} [params] - * @param {number} [params.storeId] Filter webhooks by store - * @param {number} [params.perPage] Number of records to return (between 1 and 100) - * @param {number} [params.page] Page of records to return - * @param {"store"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getWebhooks(params = {}) { - params = this.buildParams(params, ['storeId']) - return this.queryApi({ path: 'v1/webhooks', params }); - } - - /** - * Get a webhook - * @param {Object} params - * @param {number} params.id - * @param {"store"} [params.include] Comma-separated list of record types to include - * @returns {Object} JSON - */ - async getWebhook({ id, ...params } = {}) { - if (!id) throw 'You must provide an ID in getWebhook().' - params = this.buildParams(params) - return this.queryApi({ path: 'v1/webhooks/'+id, params }); - } - - /** - * Create a webhook - * @param {Object} params - * @param {string} params.storeId The ID of the store the webhook is for - * @param {string} params.url Endpoint URL that the webhooks should be sent to - * @param {string[]} params.events List of webhook events to receive - * @param {string} params.secret Signing secret (between 6 and 40 characters) - * @returns {Object} JSON - */ - async createWebhook({ storeId, url, events, secret } = {}) { - if (!storeId) throw 'You must provide a store ID in createWebhook().' - if (!url) throw 'You must provide a URL in createWebhook().' - if (!events || events?.length < 1) throw 'You must provide a list of events in createWebhook().' - if (!secret) throw 'You must provide a signing secret in createWebhook().' - let payload = { - data: { - type: 'webhooks', - attributes: { - url, - events, - secret - }, - relationships: { - store: { - data: { - type: 'stores', - id: '' + storeId - } - } - } - } - } - return this.queryApi({ path: 'v1/webhooks', method: 'POST', payload }); - } - - /** - * Update a webhook - * @param {Object} params - * @param {number} params.id - * @param {string} [params.url] Endpoint URL that the webhooks should be sent to - * @param {string[]} [params.events] List of webhook events to receive - * @param {string} [params.secret] Signing secret (between 6 and 40 characters) - * @returns {Object} JSON - */ - async updateWebhook({ id, url, events, secret } = {}) { - if (!id) throw 'You must provide an ID in updateWebhook().' - let attributes = {} - if (url) attributes.url = url - if (events) attributes.events = events - if (secret) attributes.secret = secret - let payload = { - data: { - type: 'webhooks', - id: '' + id, - attributes - } - } - return this.queryApi({ path: 'v1/webhooks/'+id, method: 'PATCH', payload }); - } - - /** - * Delete a webhook - * @param {Object} params - * @param {number} params.id - */ - async deleteWebhook({ id }) { - if (!id) throw 'You must provide an ID in deleteWebhook().' - this.queryApi({ path: 'v1/webhooks/'+id, method: 'DELETE' }); - } -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..cf7120b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,1122 @@ +import { + BaseUpdateSubscriptionOptions, + CreateCheckoutOptions, + CreateDiscountAttributes, + CreateDiscountOptions, + CreateWebhookOptions, + DeleteDiscountOptions, + DeleteWebhookOptions, + GetCheckoutOptions, + GetCheckoutsOptions, + GetCustomerOptions, + GetCustomersOptions, + GetDiscountOptions, + GetDiscountsOptions, + GetDiscountRedemptionOptions, + GetDiscountRedemptionsOptions, + GetFileOptions, + GetFilesOptions, + GetLicenseKeyOptions, + GetLicenseKeysOptions, + GetLicenseKeyInstanceOptions, + GetLicenseKeyInstancesOptions, + GetOrderItemOptions, + GetOrderItemsOptions, + GetOrderOptions, + GetOrdersOptions, + GetProductOptions, + GetProductsOptions, + GetStoreOptions, + GetStoresOptions, + GetSubscriptionOptions, + GetSubscriptionsOptions, + GetSubscriptionInvoiceOptions, + GetSubscriptionInvoicesOptions, + GetVariantOptions, + GetVariantsOptions, + GetWebhookOptions, + GetWebhooksOptions, + PauseSubscriptionAttributes, + PauseSubscriptionOptions, + QueryApiOptions, + UpdateSubscriptionAttributes, + UpdateSubscriptionOptions, + UpdateWebhookOptions +} from "./types/methods"; +import { + CheckoutsResponse, + CheckoutResponse, + CustomersResponse, + CustomerResponse, + DiscountsResponse, + DiscountResponse, + DiscountRedemptionsResponse, + DiscountRedemptionResponse, + FilesResponse, + FileResponse, + LicenseKeysResponse, + LicenseKeyResponse, + LicenseKeyInstancesResponse, + LicenseKeyInstanceResponse, + OrdersResponse, + OrderResponse, + ProductsResponse, + ProductResponse, + StoresResponse, + StoreResponse, + SubscriptionInvoicesResponse, + SubscriptionInvoiceResponse, + SubscriptionsResponse, + SubscriptionResponse, + UserResponse, + VariantsResponse, + VariantResponse, + WebhooksResponse, + WebhookResponse +} from "./types/api"; + +export class LemonSqueezy { + public apiKey: string; + + public apiUrl = "https://api.lemonsqueezy.com/"; + + /** + * LemonSqueezy API client + * + * @param {String} apiKey - Your LemonSqueezy API key + */ + constructor(apiKey: string) { + this.apiKey = apiKey; + } + + /** + * Builds a params object for the API query based on provided and allowed filters. + * + * Also converts pagination parameters `page` to `page[number]` and `perPage` to `page[size]` + * + * @params {Object} [args] Arguments to the API method + * @params {string[]} [allowedFilters] List of filters the API query permits (camelCase) + */ + private _buildParams>( + args: TArgs, + allowedFilters: Array = [] + ): Record { + let params: Record = {}; + + for (let filter in args) { + if (allowedFilters.includes(filter)) { + const queryFilter = filter.replace( + /[A-Z]/g, + (letter) => `_${letter.toLowerCase()}` + ); + + params["filter[" + queryFilter + "]"] = args[filter]; + } else { + // In v1.0.3 and lower we supported passing in a string of comma separated values + // for the `include` filter. This is now deprecated in favour of an array. + if (filter === "include") { + params["include"] = Array.isArray(args[filter]) + ? args[filter].join(",") + : args[filter]; + } + + if (filter === "page") params["page[number]"] = args[filter]; + if (filter === "perPage") params["page[size]"] = args[filter]; + } + } + + return params; + } + + /** + * Send an API query to the LemonSqueezy API + * + * @param {string} path + * @param {string} [method] POST, GET, PATCH, DELETE + * @param {Object} [params] URL query parameters + * @param {Object} [payload] Object/JSON payload + * + * @returns {Object} JSON + */ + private async _query({ + path, + method = "GET", + params, + payload, + }: QueryApiOptions) { + try { + const url = new URL(path, this.apiUrl); + if (params && method === "GET") + Object.entries(params).forEach(([key, value]) => + url.searchParams.append(key, value) + ); + + const headers = new Headers(); + headers.set("Accept", "application/vnd.api+json"); + headers.set("Authorization", `Bearer ${this.apiKey}`); + headers.set("Content-Type", "application/vnd.api+json"); + + const response = await fetch(url.href, { + headers, + method, + body: payload ? JSON.stringify(payload) : undefined, + }); + + if (!response.ok) { + let errorsJson = await response.json(); + throw { + status: response.status, + message: response.statusText, + errors: errorsJson.errors, + }; + } + + if (method !== "DELETE") return await response.json(); + } catch (error) { + throw error; + } + } + + /** + * Get current user + * + * @returns {Object} JSON + */ + async getUser(): Promise { + return this._query({ path: "v1/users/me" }); + } + + /** + * Get stores + * + * @param {Object} [params] + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"products" | "discounts" | "license-keys" | "subscriptions" | "webhooks">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getStores(params: GetStoresOptions = {}): Promise { + return this._query({ + path: "v1/stores", + params: this._buildParams(params), + }); + } + + /** + * Get a store + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"products" | "discounts" | "license-keys" | "subscriptions" | "webhooks">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getStore({ id, ...params }: GetStoreOptions): Promise { + if (!id) throw "You must provide an `id` in getStore()."; + + return this._query({ + path: `v1/stores/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get products + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter products by store + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "variants">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getProducts(params: GetProductsOptions = {}): Promise { + return this._query({ + path: "v1/products", + params: this._buildParams(params, ["storeId"]), + }); + } + + /** + * Get a product + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store" | "variants">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getProduct({ id, ...params }: GetProductOptions): Promise { + if (!id) throw "You must provide an `id` in getProduct()."; + return this._query({ + path: `v1/products/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get variants + * + * @param {Object} [params] + * @param {number} [params.productId] Filter variants by product + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"product" | "files">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getVariants(params: GetVariantsOptions = {}): Promise { + return this._query({ + path: "v1/variants", + params: this._buildParams(params, ["productId"]), + }); + } + + /** + * Get a variant + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"product" | "files">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getVariant({ id, ...params }: GetVariantOptions): Promise { + if (!id) throw "You must provide an `id` in getVariant()."; + return this._query({ + path: `v1/variants/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get checkouts + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter variants by store + * @param {number} [params.variantId] Filter checkouts by variant + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getCheckouts(params: GetCheckoutsOptions = {}): Promise { + return this._query({ + path: "v1/checkouts", + params: this._buildParams(params, ["storeId", "variantId"]) + }); + } + + /** + * Get a checkout + * + * @param {Object} params + * @param {string} params.id + * @param {Array<"store" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getCheckout({ id, ...params }: GetCheckoutOptions): Promise { + if (!id) throw "You must provide an `id` in getCheckout()."; + return this._query({ + path: `v1/checkouts/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Create a checkout + * + * @param {Object} params + * @param {number} params.storeId + * @param {number} params.variantId + * @param {Object} [params.attributes] An object of values used to configure the checkout + * + * @see https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout + * + * @returns {Object} JSON + */ + async createCheckout({ + storeId, + variantId, + attributes = {}, + }: CreateCheckoutOptions): Promise { + if (!storeId) throw "You must provide a `storeId` in createCheckout()."; + if (!variantId) throw "You must provide a `variantId` in createCheckout()."; + return this._query({ + path: "v1/checkouts", + method: "POST", + payload: { + data: { + type: "checkouts", + attributes: attributes, + relationships: { + store: { + data: { + type: "stores", + id: "" + storeId, // convert to string + }, + }, + variant: { + data: { + type: "variants", + id: "" + variantId, // convert to string + }, + }, + }, + }, + }, + }); + } + + /** + * Get customers + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter customers by store + * @param {number} [params.email] Filter customers by email address + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"license-keys" | "orders" | "store" | "subscriptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getCustomers(params: GetCustomersOptions = {}): Promise { + return this._query({ + path: "v1/customers", + params: this._buildParams(params, ["storeId", "email"]), + }); + } + + /** + * Get a customer + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"license-keys" | "orders" | "store" | "subscriptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getCustomer({ id, ...params }: GetCustomerOptions): Promise { + if (!id) throw "You must provide an `id` in getCustomer()."; + return this._query({ + path: `v1/customers/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get orders + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter orders by store + * @param {number} [params.userEmail] Filter orders by email address + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"customer" | "discount-redemptions" | "license-keys" | "order-items" | "store" | "subscriptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getOrders(params: GetOrdersOptions = {}): Promise { + return this._query({ + path: "v1/orders", + params: this._buildParams(params, ["storeId", "userEmail"]), + }); + } + + /** + * Get an order + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"customer" | "discount-redemptions" | "license-keys" | "order-items" | "store" | "subscriptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getOrder({ id, ...params }: GetOrderOptions): Promise { + if (!id) throw "You must provide an `id` in getOrder()."; + return this._query({ + path: `v1/orders/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get files + * + * @param {Object} [params] + * @param {number} [params.variantId] Filter orders by variant + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getFiles(params: GetFilesOptions = {}): Promise { + return this._query({ + path: "v1/files", + params: this._buildParams(params, ["variantId"]), + }); + } + + /** + * Get a file + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getFile({ id, ...params }: GetFileOptions): Promise { + if (!id) throw "You must provide an `id` in getFile()."; + return this._query({ + path: `v1/files/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get order items + * + * @param {Object} [params] + * @param {number} [params.orderId] Filter order items by order + * @param {number} [params.productId] Filter order items by product + * @param {number} [params.variantId] Filter order items by variant + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"order" | "product" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getOrderItems(params: GetOrderItemsOptions = {}) { + return this._query({ + path: "v1/order-items", + params: this._buildParams(params, ["orderId", "productId", "variantId"]), + }); + } + + /** + * Get an order item + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"order" | "product" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getOrderItem({ id, ...params }: GetOrderItemOptions) { + if (!id) throw "You must provide an `id` in getOrderItem()."; + return this._query({ + path: `v1/order-items/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Get subscriptions + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter subscriptions by store + * @param {number} [params.orderId] Filter subscriptions by order + * @param {number} [params.orderItemId] Filter subscriptions by order item + * @param {number} [params.productId] Filter subscriptions by product + * @param {number} [params.variantId] Filter subscriptions by variant + * @param {"on_trial" | "active" | "paused" | "past_due" | "unpaid" | "cancelled" | "expired"} [params.status] Filter subscriptions by status + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "customer" | "order" | "order-item" | "product" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getSubscriptions(params: GetSubscriptionsOptions = {}): Promise { + return this._query({ + path: "v1/subscriptions", + params: this._buildParams(params, [ + "storeId", + "orderId", + "orderItemId", + "productId", + "variantId", + "status", + ]) + }) + } + + /** + * Get a subscription + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store" | "customer" | "order" | "order-item" | "product" | "variant">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getSubscription({ id, ...params }: GetSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in getSubscription()."; + return this._query({ + path: `v1/subscriptions/${id}`, + params: this._buildParams(params), + }); + } + + /** + * Update a subscription's plan + * + * @param {Object} params + * @param {number} params.id + * @param {number} [params.variantId] ID of variant (required if changing plans) + * @param {number} [params.productId] ID of product (required if changing plans) + * @param {number} [params.billingAnchor] Set the billing day (1–31) used for renewal charges + * @param {"immediate" | "disable"} [params.proration] If not included, proration will occur at the next renewal date. + * Use 'immediate' to charge a prorated amount immediately. + * Use 'disable' to charge a full ammount immediately. + * + * @returns {Object} JSON + */ + async updateSubscription({ + id, + variantId, + productId, + billingAnchor, + proration, + }: UpdateSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in updateSubscription()."; + let attributes: UpdateSubscriptionAttributes = { + variant_id: variantId, + product_id: productId, + billing_anchor: billingAnchor, + }; + if (proration == "disable") attributes.disable_prorations = true; + if (proration == "immediate") attributes.invoice_immediately = true; + return this._query({ + path: `v1/subscriptions/${id}`, + method: "PATCH", + payload: { + data: { + type: "subscriptions", + id: "" + id, + attributes + } + } + }); + } + + /** + * Cancel a subscription + * + * @param {Object} params + * @param {number} params.id + * + * @returns {Object} JSON + */ + async cancelSubscription({ id }: BaseUpdateSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in cancelSubscription()."; + return this._query({ + path: `v1/subscriptions/${id}`, + method: "PATCH", + payload: { + data: { + type: "subscriptions", + id: "" + id, + attributes: { + cancelled: true, + } + } + } + }); + } + + /** + * Resume (un-cancel) a subscription + * + * @param {Object} params + * @param {number} params.id + * + * @returns {Object} JSON + */ + async resumeSubscription({ id }: BaseUpdateSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in resumeSubscription()."; + return this._query({ + path: `v1/subscriptions/${id}`, + method: "PATCH", + payload: { + data: { + type: "subscriptions", + id: "" + id, + attributes: { + cancelled: false, + } + } + } + }); + } + + /** + * Pause a subscription + * + * @param {Object} params + * @param {number} params.id + * @param {"void" | "free"} [params.mode] Pause mode: "void" (default) or "free" + * @param {string} [params.resumesAt] Date to automatically resume the subscription (ISO 8601 format) + * + * @returns {Object} JSON + */ + async pauseSubscription({ + id, + mode, + resumesAt + }: PauseSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in pauseSubscription()."; + let pause: PauseSubscriptionAttributes = { mode: "void" }; + if (mode) pause.mode = mode; + if (resumesAt) pause.resumes_at = resumesAt; + return this._query({ + path: `v1/subscriptions/${id}`, + method: "PATCH", + payload: { + data: { + type: "subscriptions", + id: "" + id, + attributes: { pause }, + }, + } + }); + } + + /** + * Unpause a subscription + * + * @param {Object} params + * @param {number} params.id + * + * @returns {Object} JSON + */ + async unpauseSubscription({ id }: BaseUpdateSubscriptionOptions): Promise { + if (!id) throw "You must provide an `id` in unpauseSubscription()."; + return this._query({ + path: `v1/subscriptions/${id}`, + method: "PATCH", + payload: { + data: { + type: "subscriptions", + id: "" + id, + attributes: { + pause: null + }, + }, + } + }); + } + + /** + * Get subscription invoices + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter subscription invoices by store + * @param {"paid" | "pending" | "void" | "refunded"} [params.status] Filter subscription invoices by status + * @param {boolean} [params.refunded] Filter subscription invoices by refunded + * @param {number} [params.subscriptionId] Filter subscription invoices by subscription + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "subscription">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getSubscriptionInvoices(params: GetSubscriptionInvoicesOptions = {}): Promise { + return this._query({ + path: "v1/subscription-invoices", + params: this._buildParams(params, [ + "storeId", + "status", + "refunded", + "subscriptionId", + ]) + }); + } + + /** + * Get a subscription invoice + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store" | "subscription">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getSubscriptionInvoice({ id, ...params }: GetSubscriptionInvoiceOptions): Promise { + if (!id) throw "You must provide an `id` in getSubscriptionInvoice()."; + return this._query({ + path: `v1/subscription-invoices/${id}`, + params: this._buildParams(params) + }); + } + + /** + * Get discounts + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter discounts by store + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "variants" | "discount-redemptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getDiscounts(params: GetDiscountsOptions = {}): Promise { + return this._query({ + path: "v1/discounts", + params: this._buildParams(params, ["storeId"]) + }); + } + + /** + * Get a discount + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store" | "variants" | "discount-redemptions">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getDiscount({ id, ...params }: GetDiscountOptions): Promise { + if (!id) throw "You must provide an `id` in getDiscount()."; + return this._query({ + path: `v1/discounts/${id}`, + params: this._buildParams(params) + }); + } + + /** + * Create a discount + * + * @param {Object} params + * @param {number} params.storeId Store to create a discount in + * @param {string} params.name Name of discount + * @param {string} params.code Discount code (uppercase letters and numbers, between 3 and 256 characters) + * @param {number} params.amount Amount the discount is for + * @param {"percent" | "fixed"} [params.amountType] Type of discount + * @param {"once" | "repeating" | "forever"} [params.duration] Duration of discount + * @param {number} [params.durationInMonths] Number of months to repeat the discount for + * @param {number[]} [params.variantIds] Limit the discount to certain variants + * @param {number} [params.maxRedemptions] The total number of redemptions allowed + * @param {number} [params.startsAt] Date the discount code starts on (ISO 8601 format) + * @param {number} [params.expiresAt] Date the discount code expires on (ISO 8601 format) + * + * @returns {Object} JSON + */ + async createDiscount({ + storeId, + name, + code, + amount, + amountType = "percent", + duration = "once", + durationInMonths, + variantIds, + maxRedemptions, + startsAt, + expiresAt + }: CreateDiscountOptions): Promise { + if (!storeId) throw "You must provide a `storeId` in createDiscount()."; + if (!name) throw "You must provide a `name` in createDiscount()."; + if (!code) throw "You must provide a `code` in createDiscount()."; + if (!amount) throw "You must provide an `amount` in createDiscount()."; + let attributes: CreateDiscountAttributes = { + name, + code, + amount, + amount_type: amountType, + duration, + starts_at: startsAt, + expires_at: expiresAt, + }; + if (durationInMonths && duration != "once") { + attributes.duration_in_months = durationInMonths; + } + if (maxRedemptions) { + attributes.is_limited_redemptions = true; + attributes.max_redemptions = maxRedemptions; + } + + let relationships: {store: Object; variants?: Object} = { + store: { + data: { + type: "stores", + id: "" + storeId, + } + } + } + + if (variantIds) { + let variantData: Array<{ type: string; id: string }> = []; + for (var i = 0; i < variantIds.length; i++) { + variantData.push({ type: "variants", id: "" + variantIds[i] }); + } + attributes.is_limited_to_products = true; + relationships.variants = { + data: variantData, + }; + } + + return this._query({ + path: "v1/discounts", + method: "POST", + payload: { + data: { + type: "discounts", + attributes, + relationships + } + } + }); + } + + /** + * Delete a discount + * + * @param {Object} params + * @param {number} params.id + */ + async deleteDiscount({ id }: DeleteDiscountOptions): Promise { + if (!id) throw "You must provide a `id` in deleteDiscount()."; + this._query({ path: `v1/discounts/${id}`, method: "DELETE" }); + } + + /** + * Get discount redemptions + * + * @param {Object} [params] + * @param {number} [params.discountId] Filter discount redemptions by discount + * @param {number} [params.orderId] Filter discount redemptions by order + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"discount" | "order">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getDiscountRedemptions(params: GetDiscountRedemptionsOptions = {}): Promise { + return this._query({ + path: "v1/discount-redemptions", + params: this._buildParams(params, ["discountId", "orderId"]) + }); + } + + + /** + * Get a discount redemption + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"discount" | "order">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getDiscountRedemption({ id, ...params }: GetDiscountRedemptionOptions): Promise { + if (!id) throw "You must provide a `id` in getDiscountRedemption()."; + return this._query({ + path: `v1/discount-redemptions/${id}`, + params: this._buildParams(params) + }); + } + + /** + * Get license keys + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter license keys by store + * @param {number} [params.orderId] Filter license keys by order + * @param {number} [params.orderItemId] Filter license keys by order item + * @param {number} [params.productId] Filter license keys by product + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store" | "customer" | "order" | "order-item" | "product" | "license-key-instances">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getLicenseKeys(params: GetLicenseKeysOptions = {}): Promise { + return this._query({ + path: "v1/license-keys", + params: this._buildParams(params, [ + "storeId", + "orderId", + "orderItemId", + "productId", + ]) + }); + } + + /** + * Get a license key + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store" | "customer" | "order" | "order-item" | "product" | "license-key-instances">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getLicenseKey({ id, ...params }: GetLicenseKeyOptions): Promise { + if (!id) throw "You must provide an `id` in getLicenseKey()."; + return this._query({ + path: `v1/license-keys/${id}`, + params: this._buildParams(params) + }); + } + + /** + * Get license key instances + * + * @param {Object} [params] + * @param {number} [params.licenseKeyId] Filter license keys instances by license key + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"license-key">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getLicenseKeyInstances(params: GetLicenseKeyInstancesOptions = {}): Promise { + return this._query({ + path: "v1/license-key-instances", + params: this._buildParams(params, ["licenseKeyId"]) + }); + } + + /** + * Get a license key instance + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"license-key">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getLicenseKeyInstance({ id, ...params }: GetLicenseKeyInstanceOptions): Promise { + if (!id) throw "You must provide an `id` in getLicenseKeyInstance()."; + return this._query({ + path: `v1/license-key-instances/${id}}`, + params: this._buildParams(params) + }); + } + + /** + * Get webhooks + * + * @param {Object} [params] + * @param {number} [params.storeId] Filter webhooks by store + * @param {number} [params.perPage] Number of records to return (between 1 and 100) + * @param {number} [params.page] Page of records to return + * @param {Array<"store">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getWebhooks(params: GetWebhooksOptions = {}): Promise { + return this._query({ + path: "v1/webhooks", + params: this._buildParams(params, ["storeId"]) + }); + } + + /** + * Get a webhook + * + * @param {Object} params + * @param {number} params.id + * @param {Array<"store">} [params.include] Comma-separated list of record types to include + * + * @returns {Object} JSON + */ + async getWebhook({ id, ...params }: GetWebhookOptions): Promise { + if (!id) throw "You must provide an `id` in getWebhook()."; + return this._query({ + path: `v1/webhooks/${id}`, + params: this._buildParams(params) + }); + } + + /** + * Create a webhook + * + * @param {Object} params + * @param {string} params.storeId ID of the store the webhook is for + * @param {string} params.url Endpoint URL that the webhooks should be sent to + * @param {string[]} params.events List of webhook events to receive + * @param {string} params.secret Signing secret (between 6 and 40 characters) + * + * @returns {Object} JSON + */ + async createWebhook({ storeId, url, events, secret }: CreateWebhookOptions): Promise { + if (!storeId) throw "You must provide a `storeId` in createWebhook()."; + if (!url) throw "You must provide a `url` in createWebhook()."; + if (!events || events?.length < 1) + throw "You must provide a list of events in createWebhook()."; + if (!secret) throw "You must provide a `secret` in createWebhook()."; + return this._query({ + path: "v1/webhooks", + method: "POST", + payload: { + data: { + type: "webhooks", + attributes: { + url, + events, + secret, + }, + relationships: { + store: { + data: { + type: "stores", + id: "" + storeId, + }, + }, + }, + }, + } + }); + } + + /** + * Update a webhook + * + * @param {Object} params + * @param {number} params.id + * @param {string} [params.url] Endpoint URL that the webhooks should be sent to + * @param {string[]} [params.events] List of webhook events to receive + * @param {string} [params.secret] Signing secret (between 6 and 40 characters) + * + * @returns {Object} JSON + */ + async updateWebhook({ id, url, events, secret }: UpdateWebhookOptions): Promise { + if (!id) throw "You must provide an `id` in updateWebhook()."; + let attributes: { url?: string; events?: string[]; secret?: string } = {}; + if (url) attributes.url = url; + if (events) attributes.events = events; + if (secret) attributes.secret = secret; + return this._query({ + path: `v1/webhooks/${id}`, + method: "PATCH", + payload: { + data: { + type: "webhooks", + id: "" + id, + attributes, + }, + } + }); + } + + /** + * Delete a webhook + * + * @param {Object} params + * @param {number} params.id + */ + async deleteWebhook({ id }: DeleteWebhookOptions): Promise { + if (!id) throw "You must provide an `id` in deleteWebhook()."; + this._query({ path: `v1/webhooks/${id}`, method: "DELETE" }); + } +} + +export default LemonSqueezy; diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 0000000..44d8c11 --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,1509 @@ +interface BaseListResponse { + meta: object; + jsonapi: object; + links: object; + included?: Record; +} + +interface BaseIndividualResponse { + jsonapi: object; + links: object; + included?: Record; +} + +interface BaseApiObject { + type: string; + id: string; + relationships: object; + links: object; +} + + +interface SubscriptionAttributes { + /** + * The ID of the store this subscription belongs to. + */ + store_id: number; + /** + * The ID of the customer this subscription belongs to. + */ + customer_id: number; + /** + * The ID of the order associated with this subscription. + */ + order_id: number; + /** + * The ID of the order item associated with this subscription. + */ + order_item_id: number; + /** + * The ID of the product associated with this subscription. + */ + product_id: number; + /** + * The ID of the variant associated with this subscription. + */ + variant_id: number; + /** + * The name of the product. + */ + product_name: string; + /** + * The name of the variant. + */ + variant_name: string; + /** + * The full name of the customer. + */ + user_name: string; + /** + * The email address of the customer. + */ + user_email: string; + /** + * The status of the subscription. + */ + status: "on_trial" | "active" | "paused" | "unpaid" | "cancelled" | "expired"; + /** + * The title-case formatted status of the subscription. + */ + status_formatted: "On Trial" | "Active" | "Paused" | "Unpaid" | "Cancelled" | "Expired"; + /** + * Lowercase brand of the card used to pay for the latest subscription payment. + */ + card_brand: "visa" | "mastercard" | "amex" | "discover" | "jcb" | "diners" | "unionpay" | null; + /** + * The last 4 digits of the card used to pay for the latest subscription payment. + */ + card_last_four: string | null; + /** + * An object containing the payment collection pause behaviour options for the subscription, if set. + */ + pause: object | null; + /** + * A boolean indicating if the subscription has been cancelled. + */ + cancelled: boolean; + /** + * If the subscription has a free trial, an ISO-8601 formatted date-time indicating when the trial period ends. + */ + trial_ends_at: string | null; + /** + * An integer representing the day of the month on which subscription invoice payments are collected. + */ + billing_anchor: number; + /** + * An object representing the first subscription item belonging to this subscription. + */ + first_subscription_item: { + /** + * ID of the subscription item. + */ + id: number; + /** + * ID of the related subscription. + */ + subscription_id: number; + /** + * ID of the subscription item's price. + */ + price_id: number; + /** + * Quantity of the subscription item. + */ + quantity: number; + /** + * Date the subscription item was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the subscription item was updated (ISO 8601 format). + */ + updated_at: string; + }; + /** + * URLs for the customer to manage the subscription. + */ + urls: { + /** + * A signed URL for managing payment and billing infanaginormation for the subscription, valid for 24 hours. + */ + update_payment_method: string; + }; + /** + * Date indicating the end of the current billing cycle, and when the next invoice will be issued (ISO 8601 format). + */ + renews_at: string | null; + /** + * Date indicating when the subscription will expire or has expired (ISO 8601 format). + */ + ends_at: string | null; + /** + * Date the subscription was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the subscription was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the customer was created within test mode. + */ + test_mode: boolean; +} + + +interface SubscriptionObject extends BaseApiObject { + attributes: SubscriptionAttributes; +} + +export interface SubscriptionsResponse extends BaseListResponse { + data: SubscriptionObject[]; +} + +export interface SubscriptionResponse extends BaseIndividualResponse { + data: SubscriptionObject; +} + + +interface StoreAttributes { + /** + * The name of the store. + */ + name: string; + /** + * The slug used to identify the store. + */ + slug: string; + /** + * The domain of the store in the format {slug}.lemonsqueezy.com. + */ + domain: string; + /** + * The fully-qualified URL for the store (e.g. https://{slug}.lemonsqueezy.com). + */ + url: string; + /** + * The URL for the store avatar. + */ + avatar_url: string; + /** + * The current billing plan for the store. + */ + plan: string; + /** + * The ISO 3166-1 two-letter country code for the store. + */ + country: string; + /** + * The full country name for the store. + */ + country_nicename: string; + /** + * The ISO 4217 currency code for the store. + */ + currency: string; + /** + * A count of the all-time total sales made by this store. + */ + total_sales: number; + /** + * A positive integer in cents representing the total all-time revenue of the store in USD. + */ + total_revenue: number; + /** + * A count of the sales made by this store in the last 30 days. + */ + thirty_day_sales: number; + /** + * A positive integer in cents representing the total revenue of the store in USD in the last 30 days. + */ + thirty_day_revenue: number; + /** + * Date the store was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the store was updated (ISO 8601 format). + */ + updated_at: string; +} + +interface StoreObject extends BaseApiObject { + attributes: StoreAttributes; +} + +export interface StoresResponse extends BaseListResponse { + data: StoreObject[]; +} + +export interface StoreResponse extends BaseIndividualResponse { + data: StoreObject; +} + + +interface CustomerAttributes { + /** + * 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 | null; + /** + * The region of the customer. + */ + region: string | null; + /** + * 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: "Subscribed" | "Unsubscribed" | "Archived" | "Requires Verification" | "Invalid Email" | "Bounced"; + /** + * 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; + /** + * Date the customer was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the customer was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the customer was created within test mode. + */ + test_mode: boolean; +} + +interface CustomerObject extends BaseApiObject { + attributes: CustomerAttributes; +} + +export interface CustomersResponse extends BaseListResponse { + data: CustomerObject[]; +} + +export interface CustomerResponse extends BaseIndividualResponse { + data: CustomerObject; +} + + +interface UserAttributes { + /** + * The name of the user. + */ + name: string; + /** + * The email address of the user. + */ + email: string; + /** + * A randomly generated hex color code for the user. + */ + color: string; + /** + * A URL to the avatar image for this user. + */ + avatar_url: string; + /** + * Has the value true if the user has uploaded a custom avatar image. + */ + has_custom_avatar: string; + /** + * Date the user was created (ISO 8601 format). + */ + createdAt: string; + /** + * Date the user was updated (ISO 8601 format). + */ + updatedAt: string; +} + +interface UserObject extends BaseApiObject { + attributes: UserAttributes; +} + +export interface UserResponse extends BaseIndividualResponse { + data: UserObject; +} + + +interface ProductAttributes { + /** + * The ID of the store this product belongs to. + */ + store_id: number; + /** + * The name of the product. + */ + name: string; + /** + * The slug used to identify the product. + */ + slug: string; + /** + * The description of the product in HTML. + */ + description: string; + /** + * The status of the product. + */ + status: "draft" | "published"; + /** + * The formatted status of the product. + */ + status_formatted: "Draft" | "Published"; + /** + * A URL to the thumbnail image for this product (if one exists). The image will be 100x100px in size. + */ + thumb_url: string | null; + /** + * A URL to the large thumbnail image for this product (if one exists). The image will be 1000x1000px in size. + */ + large_thumb_url: string | null; + /** + * A positive integer in cents representing the price of the product. + */ + price: number; + /** + * A human-readable string representing the price of the product (e.g. $9.99). + */ + price_formatted: string; + /** + * If this product has multiple variants, this will be a positive integer in cents representing the price of the cheapest variant. Otherwise, it will be null. + */ + from_price: number | null; + /** + * If this product has multiple variants, this will be a positive integer in cents representing the price of the most expensive variant. Otherwise, it will be null. + */ + to_price: number | null; + /** + * Has the value true if this is a “pay what you want” product where the price can be set by the customer at checkout. + */ + pay_what_you_want: boolean; + /** + * A URL to purchase this product using the Lemon Squeezy checkout. + */ + buy_now_url: string; + /** + * Date the product was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the product was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the product was created within test mode. + */ + test_mode: boolean; +} + +interface ProductObject extends BaseApiObject { + attributes: ProductAttributes; +} + +export interface ProductsResponse extends BaseListResponse { + data: ProductObject[]; +} + +export interface ProductResponse extends BaseIndividualResponse { + data: ProductObject; +} + + +interface VariantAttributes { + /** + * The ID of the product this variant belongs to. + */ + product_id: number; + /** + * The name of the variant. + */ + name: string; + /** + * The slug used to identify the variant. + */ + slug: string; + /** + * The description of the variant in HTML. + */ + description: string; + /** + * @deprecated Price information has been moved to Price objects. + */ + price: number; + /** + * @deprecated Price information has been moved to Price objects. + */ + is_subscription: boolean; + /** + * @deprecated Price information has been moved to Price objects. + */ + interval: "day" | "week" | "month" | "year" | null; + /** + * @deprecated Price information has been moved to Price objects. + */ + interval_count: number | null; + /** + * @deprecated Price information has been moved to Price objects. + */ + has_free_trial: boolean; + /** + * @deprecated Price information has been moved to Price objects. + */ + trial_interval: "day" | "week" | "month" | "year" | null; + /** + * @deprecated Price information has been moved to Price objects. + */ + trial_interval_count: number | null; + /** + * @deprecated Price information has been moved to Price objects. + */ + pay_what_you_want: boolean; + /** + * @deprecated Price information has been moved to Price objects. + */ + min_price: number; + /** + * @deprecated Price information has been moved to Price objects. + */ + suggested_price: number; + /** + * A boolean representing if this variant should generate license keys for the customer on purchase. + */ + has_license_keys: boolean; + /** + * The maximum number of times a license key can be activated for this variant + */ + license_activation_limit: number; + /** + * A boolean representing if license key activations are unlimited for this variant. + */ + is_license_limit_unlimited: boolean; + /** + * The number of units (specified in the `license_length_unit attribute`) until a license key expires. + */ + license_length_value: number | null; + /** + * The unit linked with the `license_length_value` attribute. + */ + license_length_unit: "days" | "months" | "years" | null; + /** + * A boolean representing if license keys should never expire. + */ + is_license_length_unlimited: boolean; + /** + * An integer representing the order of this variant when displayed on the checkout. + */ + sort: number; + /** + * The status of the variant. + */ + status: "pending" | "draft" | "published"; + /** + * The formatted status of the variant. + */ + status_formatted: "Pending" | "Draft" | "Published"; + /** + * Date the variant was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the variant was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the variant was created within test mode. + */ + test_mode: boolean; +} + +interface VariantObject extends BaseApiObject { + attributes: VariantAttributes; +} + +export interface VariantsResponse extends BaseListResponse { + data: VariantObject[]; +} + +export interface VariantResponse extends BaseIndividualResponse { + data: VariantObject; +} + + +interface CheckoutProductOptions { + /** + * A custom name for the product. + */ + name: string; + /** + * A custom description for the product. + */ + description: string; + /** + * An array of image URLs to use as the product's media. + */ + media: string[]; + /** + * A custom URL to redirect to after a successful purchase. + */ + redirect_url: 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; + /** + * An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled. + */ + enabled_variants: number[]; +} + +interface CheckoutCheckoutOptions { + /** + * A boolean indicating whether to show the checkout overlay. + */ + embed: boolean; + /** + * A boolean indicating whether to show product media. + */ + media: boolean; + /** + * A boolean indicating whether to show the store log. + */ + logo: boolean; + /** + * A boolean indicating whether to show the product description + * */ + desc: boolean; + /** + * A boolean indicating whether to show the discount code. + */ + discount: boolean; + /** + * A boolean indicating whether to use the dark theme. + */ + dark: boolean; + /** + * A boolean indicating whether to show the text "You will be charged..." + */ + subscription_preview: boolean; + /** + * A custom hex color to use for the checkout button. + */ + button_color: string; +} + +interface CheckoutCheckoutData { + /** + * A pre-filled email address. + */ + email: string; + /** + * A pre-filled name. + */ + name: string; + /** + * A pre-filled billing address. + */ + billing_address: { + /** + * A country in a ISO 3166-1 alpha-2 format + */ + country: string; + /** + * A zip/postal code. + */ + address_zip: string; + }; + /** + * A pre-filled tax number. + */ + tax_number: string; + /** + * A pre-filled discount code. + */ + discount_code: string; + /** + * An object containing any custom data to be passed to the checkout. + */ + custom: Record; + /** + * A list containing quantity data objects. + */ + variant_quantities: Array<{ + quantity: number; + variant_id: number; + }>; +} + +interface CheckoutPreview { + /** + * The ISO 4217 currency code of the store. + */ + currency: string; + /** + * The currency conversion rate used to determine the price of the checkout in USD. + */ + currency_rate: number; + /** + * A positive integer in cents representing the subtotal of the checkout in the store currency. + */ + subtotal: number; + /** + * A positive integer in cents representing the total discount value applied to the checkout in the store currency. + */ + discount_total: number; + /** + * A positive integer in cents representing the tax applied to the checkout in the store currency. + */ + tax: number; + /** + * A positive integer in cents representing the total price of the checkout in the store currency. + */ + total: number; + /** + * A positive integer in cents representing the subtotal of the checkout in USD. + */ + subtotal_usd: number; + /** + * A positive integer in cents representing the total discount value applied to the checkout in USD. + */ + discount_total_usd: number; + /** + * A positive integer in cents representing the tax applied to the checkout in USD. + */ + tax_usd: number; + /** + * A positive integer in cents representing the total price of the checkout in USD. + */ + total_usd: number; + /** + * A human-readable string representing the subtotal of the checkout in the store currency. + */ + subtotal_formatted: string; + /** + * A human-readable string representing the total discount value applied to the checkout in the store currency. + */ + discount_total_formatted: string; + /** + * A human-readable string representing the tax applied to the checkout in the store currency. + */ + tax_formatted: string; + /** + * A human-readable string representing the total price of the checkout in the store currency. + */ + total_formatted: string; +} + +interface CheckoutAttributes { + /** + * The ID of the store this checkout belongs to. + */ + store_id: number; + /** + * The ID of the variant associated with this checkout. + */ + variant_id: number; + /** + * A positive integer in cents representing the custom price of the variant. + */ + custom_price: number | null; + /** + * An object containing any overridden product options for this checkout. + */ + product_options: CheckoutProductOptions; + /** + * An object containing checkout options for this checkout. + */ + checkout_options: CheckoutCheckoutOptions; + /** + * An object containing any prefill or custom data to be used in the checkout. + */ + checkout_data: CheckoutCheckoutData; + /** + * An object containin pricing information for this checkout. Will be `false` if `preview` was not `true` in the request. + */ + preview: CheckoutPreview | false; + /** + * Date the checkout expires (ISO 8601 format). + */ + expires_at: string | null; + /** + * Date the checkout was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the checkout was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the checkout was created within test mode. + */ + test_mode: boolean; + /** + * The unique URL to access the checkout. + */ + url: string; +} + +interface CheckoutObject extends BaseApiObject { + attributes: CheckoutAttributes; +} + +export interface CheckoutsResponse extends BaseListResponse { + data: CheckoutObject[]; +} + +export interface CheckoutResponse extends BaseIndividualResponse { + data: CheckoutObject; +} + + +interface OrderAttributes { + /** + * The ID of the store this order belongs to. + */ + store_id: number; + /** + * The ID of the customer this order belongs to. + */ + customer_id: number; + /** + * The unique identifier (UUID) for this order. + */ + identifier: string; + /** + * An integer representing the sequential order number for this store. + */ + order_number: number; + /** + * The full name of the customer. + */ + user_name: string; + /** + * The email address of the customer. + */ + user_email: string; + /** + * The ISO 4217 currency code for the order. + */ + currency: string; + /** + * The currency conversion rate used to determine the price of the checkout in USD. + */ + currency_rate: string; + /** + * A positive integer in cents representing the subtotal of the order in the order currency. + */ + subtotal: number; + /** + * A positive integer in cents representing the total discount value applied to the order in the order currency. + */ + discount_total: number; + /** + * A positive integer in cents representing the tax applied to the order in the order currency. + */ + tax: number; + /** + * A positive integer in cents representing the total cost of the order in the order currency. + */ + total: number; + /** + * A positive integer in cents representing the subtotal of the order in USD. + */ + subtotal_usd: number; + /** + * A positive integer in cents representing the total discount value applied to the order in USD. + */ + discount_total_usd: number; + /** + * A positive integer in cents representing the tax applied to the order in USD. + */ + tax_usd: number; + /** + * A positive integer in cents representing the total cost of the order in USD. + */ + total_usd: number; + /** + * The name of the tax rate applied to the order. + */ + tax_name: string | null; + /** + * If tax is applied to the order, this will be the rate of tax displayed as a decimal percentage as a string. + */ + tax_rate: string; + /** + * The status of the order. + */ + status: "pending" | "failed" | "paid" | "refunded"; + /** + * The formatted status of the order. + */ + status_formatted: "Pending" | "Failed" | "Paid" | "Refunded"; + /** + * A boolean indicating if the order has been refunded. + */ + refunded: boolean; + /** + * Date the order was refuned (ISO 8601 format). + */ + refunded_at: string | null; + /** + * A human-readable string representing the subtotal of the order in the order currency. + */ + subtotal_formatted: string; + /** + * A human-readable string representing the total discount value applied to the order in the order currency. + */ + discount_total_formatted: string; + /** + * A human-readable string representing the tax applied to the order in the order currency. + */ + tax_formatted: string; + /** + * A human-readable string representing the total cost of the order in the order currency. + */ + total_formatted: string; + /** + * An object representing the first order item belonging to this order. + */ + first_order_item: { + /** + * The ID of the order item. + */ + id: number; + /** + * The ID of the order. + */ + order_id: number; + /** + * The ID of the product. + */ + product_id: number; + /** + * The ID of the product variant. + */ + variant_id: number; + /** + * The name of the product. + */ + product_name: string; + /** + * The name of the product variant. + */ + variant_name: string; + /** + * A positive integer in cents representing the price of the order item in the order currency. + */ + price: number; + /** + * Date the order item was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the order item was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the order item was created within test mode. + */ + test_mode: boolean; + }; + urls: { + /** + * A signed URL for viewing the order in the customer's My Orders page. + */ + receipt: string; + }; + /** + * Date the order was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the order was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the order was created within test mode. + */ + test_mode: boolean; +} + +interface OrderObject extends BaseApiObject { + attributes: OrderAttributes; +} + +export interface OrdersResponse extends BaseListResponse { + data: OrderObject[]; +} + +export interface OrderResponse extends BaseIndividualResponse { + data: OrderObject; +} + + +interface FileAttributes { + /** + * The ID of the variant this file belongs to. + */ + variant_id: number; + /** + * The unique identifier (UUID) for this file. + */ + identifier: string; + /** + * The name of the file. + */ + name: string; + /** + * The file extension of the file. + */ + extension: string; + /** + * The unique URL to download the file. + */ + download_url: string; + /** + * A positive integer in bytes representing the size of the file. + */ + size: number; + /** + * The human-readable size of the file. + */ + size_formatted: string; + /** + * The software version of this file. + */ + version: string | null; + /** + * An integer representing the order of this file when displayed. + */ + sort: number; + /** + * The status of the file + */ + status: "draft" | "published"; + /** + * Date the file was created (ISO 8601 format). + */ + createdAt: string; + /** + * Date the file was updated (ISO 8601 format). + */ + updatedAt: string; + /** + * A boolean indicating if the file was created within test mode. + */ + test_mode: boolean; +} + +interface FileObject extends BaseApiObject { + attributes: FileAttributes; +} + +export interface FilesResponse extends BaseListResponse { + data: FileObject[]; +} + +export interface FileResponse extends BaseIndividualResponse { + data: FileObject; +} + + +interface SubscriptionInvoiceAttributes { + /** + * The ID of the Store this subscription invoice belongs to. + */ + store_id: number; + /** + * The ID of the Subscription associated with this subscription invoice. + */ + subscription_id: number; + /** + * The ID of the customer this subscription invoice belongs to. + */ + customer_id: number; + /** + * The full name of the customer. + */ + user_name: string; + /** + * The email address of the customer. + */ + user_email: string; + /** + * The reason for the invoice being generated. + */ + billing_reason: "initial" | "renewal" | "updated"; + /** + * Lowercase brand of the card used to pay for the invoice. + */ + card_brand: "visa" | "mastercard" | "amex" | "discover" | "jcb" | "diners" | "unionpay" | null; + /** + * The last 4 digits of the card used to pay for the invoice. + */ + card_last_four: string | null; + /** + * The ISO 4217 currency code for the invoice + */ + currency: string; + /** + * The currency conversion rate used to determine the price of the checkout in USD. + */ + currency_rate: string; + /** + * The status of the invoice. + */ + status: "pending" | "paid" | "void" | "refunded"; + /** + * The formatted status of the invoice. + */ + status_formatted: "Pending" | "Paid" | "Void" | "Refunded"; + /** + * A boolean value indicating whether the invoice has been refunded. + */ + refunded: boolean; + /** + * Date the order was refuned (ISO 8601 format). + */ + refunded_at: string | null; + /** + * A positive integer in cents representing the subtotal of the invoice in the invoice currency. + */ + subtotal: number; + /** + * A positive integer in cents representing the total discount value applied to the invoice in the invoice currency. + */ + discount_total: number; + /** + * A positive integer in cents representing the tax applied to the invoice in the invoice currency. + */ + tax: number; + /** + * A positive integer in cents representing the total cost of the invoice in the invoice currency. + */ + total: number; + /** + * A positive integer in cents representing the subtotal of the invoice in USD. + */ + subtotal_usd: number; + /** + * A positive integer in cents representing the total discount value applied to the invoice in USD. + */ + discount_total_usd: number; + /** + * A positive integer in cents representing the tax applied to the invoice in USD. + */ + tax_usd: number; + /** + * A positive integer in cents representing the total cost of the invoice in USD. + */ + total_usd: number; + /** + * A human-readable string representing the subtotal of the invoice in the invoice currency (e.g. $9.99). + */ + subtotal_formatted: string; + /** + * A human-readable string representing the total discount value applied to the invoice in the invoice currency (e.g. $9.99). + */ + discount_total_formatted: string; + /** + * A human-readable string representing the tax applied to the invoice in the invoice currency (e.g. $9.99). + */ + tax_formatted: string; + /** + * A human-readable string representing the total cost of the invoice in the invoice currency (e.g. $9.99). + */ + total_formatted: string; + /** + * An object of customer-facing URLs for the invoice. + */ + urls: { + /** + * The unique URL to download a PDF of the invoice (will be `null` if status is `pending`). + */ + invoice_url: string | null; + }; + /** + * Date the subscription invoice was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the subscription invoice was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the subscription invoice was created within test mode. + */ + test_mode: boolean; +} + +interface SubscriptionInvoiceObject extends BaseApiObject { + attributes: SubscriptionInvoiceAttributes; +} + +export interface SubscriptionInvoicesResponse extends BaseListResponse { + data: SubscriptionInvoiceObject[]; +} + +export interface SubscriptionInvoiceResponse extends BaseIndividualResponse { + data: SubscriptionInvoiceObject; +} + + +interface DiscountAttributes { + /** + * The ID of the store this discount belongs to. + */ + store_id: number; + /** + * The name of the discount. + */ + name: string; + /** + * The discount code that can be used at checkout. + */ + code: string; + /** + * The amount of discount to apply to the order. + */ + amount: number; + /** + * The type of the amount. + */ + amount_type: "percent" | "fixed"; + /** + * A boolean indicating if the discount can only be applied to certain products/variants. + */ + is_limited_to_products: boolean; + /** + * A boolean indicating if the discount can only be redeemed a limited number of times. + */ + is_limited_redemptions: boolean; + /** + * If is_limited_redemptions is true, this is the maximum number of redemptions. + */ + max_redemptions: number; + /** + * Date the discount is valid from (ISO 8601 format). + */ + starts_at: string | null; + /** + * Date the discount expires (ISO 8601 format). + */ + expires_at: string | null; + /** + * When applied to a subscription, how often the discount should be applied. + */ + duration: "once" | "repeating" | "forever"; + /** + * If duration is repeating, this specifies how many months the discount should apply. + */ + duration_in_months: number; + /** + * The status of the discount. + */ + status: "draft" | "published"; + /** + * The formatted status of the discount. + */ + status_formatted: "Draft" | "Published"; + /** + * Date the discount was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the discount was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the subscription invoice was created within test mode. + */ + test_mode: boolean; +} + +interface DiscountObject extends BaseApiObject { + attributes: DiscountAttributes; +} + +export interface DiscountsResponse extends BaseListResponse { + data: DiscountObject[]; +} + +export interface DiscountResponse extends BaseIndividualResponse { + data: DiscountObject; +} + + +interface DiscountRedemptionAttributes { + /** + * The ID of the discount this redemption belongs to. + */ + discount_id: number; + /** + * The ID of the order this redemption belongs to. + */ + order_id: number; + /** + * The name of the discount. + */ + discount_name: string; + /** + * The discount code that was used at checkout. + */ + discount_code: string; + /** + * The amount of the discount. + */ + discount_amount: number; + /** + * The type of the discount_amount. + */ + discount_amount_type: "percent" | "fixed"; + /** + * A positive integer in cents representing the amount of the discount that was applied to the order (in the order currency). + */ + amount: number; + /** + * Date the discount redemption was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the discount redemption was updated (ISO 8601 format). + */ + updated_at: string; +} + +interface DiscountRedemptionObject extends BaseApiObject { + attributes: DiscountRedemptionAttributes; +} + +export interface DiscountRedemptionsResponse extends BaseListResponse { + data: DiscountRedemptionObject[]; +} + +export interface DiscountRedemptionResponse extends BaseIndividualResponse { + data: DiscountRedemptionObject; +} + + +interface LicenseKeyAttributes { + /** + * The ID of the store this license key belongs to. + */ + store_id: number; + /** + * The ID of the customer this license key belongs to. + */ + customer_id: number; + /** + * The ID of the order associated with this license key. + */ + order_id: number; + /** + * The ID of the order item associated with this license key. + */ + order_item_id: number; + /** + * The ID of the product associated with this license key. + */ + product_id: number; + /** + * The full name of the customer. + */ + user_name: string; + /** + * The email address of the customer. + */ + user_email: string; + /** + * The full license key. + */ + key: string; + /** + * A “short” representation of the license key, made up of the string “XXXX-” followed by the last 12 characters of the license key. + */ + key_short: string; + /** + * The activation limit of this license key. + */ + activation_limit: number; + /** + * A count of the number of instances this license key has been activated on. + */ + instances_count: number; + /** + * A boolean indicating if this license key has been disabled. + */ + disabled: boolean; + /** + * The status of the license key. + */ + status: "inactive" | "active" | "expired" | "disabled"; + /** + * The formatted status of the license key. + */ + status_formatted: "Inactive" | "Active" | "Expired" | "Disabled"; + /** + * Date the license key expires (ISO 8601 format). + */ + expires_at: string | null; + /** + * Date the license key was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the license key was updated (ISO 8601 format). + */ + updated_at: string; +} + +interface LicenseKeyObject extends BaseApiObject { + attributes: LicenseKeyAttributes; +} + +export interface LicenseKeysResponse extends BaseListResponse { + data: LicenseKeyObject[]; +} + +export interface LicenseKeyResponse extends BaseIndividualResponse { + data: LicenseKeyObject; +} + + +interface LicenseKeyInstanceAttributes { + /** + * The ID of the license key this instance belongs to. + */ + license_key_id: number; + /** + * The unique identifier (UUID) for this instance. + */ + identifier: string; + /** + * The name of the license key instance. + */ + name: string; + /** + * Date the license key instance was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the license key instance was updated (ISO 8601 format). + */ + updated_at: string; +} + +interface LicenseKeyInstanceObject extends BaseApiObject { + attributes: LicenseKeyInstanceAttributes; +} + +export interface LicenseKeyInstancesResponse extends BaseListResponse { + data: LicenseKeyInstanceObject[]; +} + +export interface LicenseKeyInstanceResponse extends BaseIndividualResponse { + data: LicenseKeyInstanceObject; +} + +export type WebhookEvent = + | "order_created" + | "order_refunded" + | "subscription_created" + | "subscription_updated" + | "subscription_cancelled" + | "subscription_resumed" + | "subscription_expired" + | "subscription_paused" + | "subscription_unpaused" + | "subscription_payment_success" + | "subscription_payment_failed" + | "subscription_payment_recovered" + | "license_key_created" + | "license_key_updated"; + +interface WebhookAttributes { + /** + * The ID of the store this webhook belongs to. + */ + store_id: number; + /** + * The URL that events will be sent to. + */ + url: string; + /** + * An array of events that will be sent. + */ + events: Array; + /** + * Date the webhook was last sent (ISO 8601 format). + */ + last_sent_at: string | null; + /** + * Date the webhook was created (ISO 8601 format). + */ + created_at: string; + /** + * Date the webhook was updated (ISO 8601 format). + */ + updated_at: string; + /** + * A boolean indicating if the webhook was created within test mode. + */ + test_mode: boolean; +} + +interface WebhookObject extends BaseApiObject { + attributes: WebhookAttributes; +} + +export interface WebhooksResponse extends BaseListResponse { + data: WebhookObject[]; +} + +export interface WebhookResponse extends BaseIndividualResponse { + data: WebhookObject; +} \ No newline at end of file diff --git a/src/types/methods.ts b/src/types/methods.ts new file mode 100644 index 0000000..ab8d3c9 --- /dev/null +++ b/src/types/methods.ts @@ -0,0 +1,708 @@ +import { WebhookEvent } from "./api"; + +/** + * A union of all possible API versions available. + */ +type ApiVersion = "v1"; + +export interface QueryApiOptions { + /** + * The path to the API endpoint. + */ + path: `${ApiVersion}/${string}`; + /** + * The HTTP method to use. + * + * @default "GET" + */ + method?: "POST" | "GET" | "PATCH" | "DELETE"; + /** + * Any query parameters to add to the request. + */ + params?: unknown; + /** + * Any data to send in the request body. + */ + payload?: object; +} + +interface PaginatedOptions { + /** + * Number of records to return (between 1 and 100) + */ + perPage?: number; + /** + * Page of records to return + */ + page?: number; +} + +export interface GetStoresOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array< + "products" | "discounts" | "license-keys" | "subscriptions" | "webhooks" + >; +} + +export interface GetStoreOptions { + /** + * The ID of the store to retrieve + */ + id: string; + /** + * List of record types to include + */ + include?: Array< + "products" | "discounts" | "license-keys" | "subscriptions" | "webhooks" + >; +} + +export interface GetProductsOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"store" | "variants">; + /** + * Filter products by store + */ + storeId?: number; +} + +export interface GetProductOptions { + /** + * The ID of the store to retrieve + */ + id: string; + /** + * List of record types to include + */ + include?: Array<"store" | "variants">; +} + +export interface GetVariantsOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"product" | "files">; + /** + * Filter variants by product + */ + productId?: number; +} + +export interface GetVariantOptions { + /** + * The ID of the variant to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"product" | "files">; +} + +export interface GetCheckoutsOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"store" | "variant">; + /** + * Filter variants by store + */ + storeId?: number; + /** + * Filter checkouts by variant + */ + variantId?: number; +} + +export interface GetCheckoutOptions { + /** + * The ID of the checkout to retrieve + */ + id: string; + /** + * List of record types to include + */ + include?: Array<"store" | "variant">; +} + +export interface CreateCheckoutOptions { + /** + * An object of values used to configure the checkout + * + * @see https://docs.lemonsqueezy.com/api/checkouts#create-a-checkout + */ + attributes?: object; + /** + * The ID of the store + */ + storeId: number; + /** + * The ID of the variant + */ + variantId: number; +} + +export interface GetCustomersOptions extends PaginatedOptions { + /** + * Filter customers by email address + */ + email?: number; + /** + * List of record types to include + */ + include?: Array<"license-keys" | "orders" | "store" | "subscriptions">; + /** + * Filter customers by store + */ + storeId?: number; +} + +export interface GetCustomerOptions { + /** + * The ID of the customer to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"license-keys" | "orders" | "store" | "subscriptions">; +} + +export interface GetOrdersOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array< + | "customer" + | "discount-redemptions" + | "license-keys" + | "order-items" + | "store" + | "subscriptions" + >; + /** + * Filter orders by store + */ + storeId?: number; + /** + * Filter orders by email address + */ + userEmail?: number; +} + +export interface GetOrderOptions { + /** + * The ID of the order to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array< + | "customer" + | "discount-redemptions" + | "license-keys" + | "order-items" + | "store" + | "subscriptions" + >; +} + +export interface GetFilesOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"variant">; + /** + * Filter files by variant + */ + variantId?: number; +} + +export interface GetFileOptions { + /** + * The ID of the file to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"variant">; +} + +export interface GetOrderItemsOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"order" | "product" | "variant">; + /** + * Filter order items by order + */ + orderId?: number; + /** + * Filter order items by product + */ + productId?: number; + /** + * Filter order items by variant + */ + variantId?: number; +} + +export interface GetOrderItemOptions { + /** + * The ID of the order item to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"order" | "product" | "variant">; +} + +export interface GetSubscriptionsOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"store" | "customer" | "order" | "order-item" | "product" | "variant">; + /** + * Filter subscriptions by store + */ + storeId?: number; + /** + * Filter subscriptions by order + */ + orderId?: number; + /** + * Filter subscriptions by order item + */ + orderItemId?: number; + /** + * Filter subscriptions by product + */ + productId?: number; + /** + * Filter subscriptions by variant + */ + variantId?: number; + /** + * Filter subscriptions by status + */ + status?: "on_trial" | "active" | "paused" | "past_due" | "unpaid" | "cancelled" | "expired"; +} + +export interface GetSubscriptionOptions { + /** + * The ID of the subscription to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"store" | "customer" | "order" | "order-item" | "product" | "variant">; +} + + +export interface BaseUpdateSubscriptionOptions { + /** + * The ID of the subscription to update + */ + id: number; +} + +export interface UpdateSubscriptionOptions extends BaseUpdateSubscriptionOptions { + /** + * The ID of the product (required when changing plans) + */ + productId?: number; + /** + * The ID of the variant (required when changing plans) + */ + variantId?: number; + /** + * Set the proration when changing plans. If ommitted, proration will occur at the next renewal date. + */ + proration?: "immediate" | "disable"; + /** + * Change the billing day used for renewal charges. Must be a number between 1 and 31 + */ + billingAnchor?: number; +} + +export interface UpdateSubscriptionAttributes { + /** + * The ID of the variant (required when changing plans) + */ + variant_id?: number; + /** + * The ID of the product (required when changing plans) + */ + product_id?: number; + /** + * Change the billing day used for renewal charges. Must be a number between 1 and 31 + */ + billing_anchor?: number; + /** + * Disable proration; charge customer the full amount immediately + */ + disable_prorations?: boolean; + /** + * Invoice the customer now for a prorated amount + */ + invoice_immediately?: boolean; +} + +export interface PauseSubscriptionOptions extends BaseUpdateSubscriptionOptions { + /** + * Type of pause + * + * @default "void" + */ + mode?: "void" | "free"; + /** + * Date to automatically resume the subscription (ISO 8601 format datetime) + */ + resumesAt?: string; +} + +export interface PauseSubscriptionAttributes { + /** + * Type of pause + * + * @default "void" + */ + mode?: "void" | "free"; + /** + * Date to automatically resume the subscription (ISO 8601 format datetime) + */ + resumes_at?: string; +} + +export interface GetSubscriptionInvoicesOptions extends PaginatedOptions { + /** + * List of record types to include + */ + include?: Array<"store" | "subscription">; + /** + * Filter subscription invoices by store + */ + storeId?: number; + /** + * Filter subscription invoices by status + */ + status?: "paid" | "pending" | "void" | "refunded"; + /** + * Filter subscription invoices by refunded + */ + refunded?: boolean; + /** + * Filter subscription invoices by subscription + */ + subscriptionId?: number; +} + +export interface GetSubscriptionInvoiceOptions { + /** + * The ID of the subscription invoice to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"store" | "subscription">; +} + +export interface GetDiscountsOptions extends PaginatedOptions { + /** + * Filter discounts by store + */ + storeId?: number; + /** + * List of record types to include + */ + include?: Array<"store" | "variants" | "discount-redemptions">; +} + +export interface GetDiscountOptions { + /** + * The ID of the discount to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"store" | "variants" | "discount-redemptions">; +} + +export interface CreateDiscountOptions { + /** + * Store to create a discount in + */ + storeId: number; + /** + * Name of discount + */ + name: string; + /** + * Discount code (uppercase letters and numbers, between 3 and 256 characters) + */ + code: string; + /** + * Amount the discount is for + */ + amount: number; + /** + * Type of discount + */ + amountType?: "percent" | "fixed"; + /** + * Duration of discount + */ + duration?: "once" | "repeating" | "forever"; + /** + * Number of months to repeat the discount for + */ + durationInMonths?: number; + /** + * Limit the discount to certain variants + */ + variantIds?: number[]; + /** + * The total number of redemptions allowed + */ + maxRedemptions?: number; + /** + * Date the discount code starts on (ISO 8601 format) + */ + startsAt?: string; + /** + * Date the discount code expires on (ISO 8601 format) + */ + expiresAt?: string; +} + +export interface CreateDiscountAttributes { + /** + * Name of discount + */ + name: string; + /** + * Discount code (uppercase letters and numbers, between 3 and 256 characters) + */ + code: string; + /** + * Amount the discount is for + */ + amount: number; + /** + * Type of discount + */ + amount_type: "percent" | "fixed"; + /** + * Duration of discount + */ + duration: "once" | "repeating" | "forever"; + /** + * Date the discount code starts on (ISO 8601 format) + */ + starts_at?: string; + /** + * Date the discount code expires on (ISO 8601 format) + */ + expires_at?: string; + /** + * Number of months to repeat the discount for + */ + duration_in_months?: number; + /** + * Is discount limited to a certain number of redemptions + */ + is_limited_redemptions?: boolean; + /** + * The total number of redemptions allowed + */ + max_redemptions?: number; + /** + * Is discount applied only to certain variants + */ + is_limited_to_products?: boolean; +} + +export interface DeleteDiscountOptions { + /** + * The ID of the discount to delete + */ + id: number; +} + +export interface GetDiscountRedemptionsOptions extends PaginatedOptions { + /** + * Filter discount redemptions by discount + */ + discountId?: number; + /** + * Filter discount redemptions by order + */ + orderId?: number; + /** + * List of record types to include + */ + include?: Array<"discount" | "order">; +} + +export interface GetDiscountRedemptionOptions { + /** + * ID of the discount to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"discount" | "order">; +} + +export interface GetLicenseKeysOptions extends PaginatedOptions { + /** + * Filter license keys by store + */ + storeId?: number; + /** + * Filter license keys by order + */ + orderId?: number; + /** + * Filter license keys by order item + */ + orderItemId?: number; + /** + * Filter license keys by product + */ + productId?: number; + /** + * List of record types to include + */ + include?: Array< + | "store" + | "customer" + | "order" + | "order-item" + | "product" + | "license-key-instances" + >; +} + +export interface GetLicenseKeyOptions { + /** + * ID of the license key to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array< + | "store" + | "customer" + | "order" + | "order-item" + | "product" + | "license-key-instances" + >; +} + +export interface GetLicenseKeyInstancesOptions extends PaginatedOptions { + /** + * Filter license key instances by license key + */ + licenseKeyId?: number; + /** + * List of record types to include + */ + include?: Array<"license-key">; +} + +export interface GetLicenseKeyInstanceOptions { + /** + * ID of the license key instance to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"license-key">; +} + +export interface GetWebhooksOptions extends PaginatedOptions { + /** + * Filter webhooks by store + */ + storeId?: number; + /** + * List of record types to include + */ + include?: Array<"store">; +} + +export interface GetWebhookOptions { + /** + * ID of the license key instance to retrieve + */ + id: number; + /** + * List of record types to include + */ + include?: Array<"store">; +} + + +export interface CreateWebhookOptions { + /** + * ID of the store the webhook is for + */ + storeId: number; + /** + * Endpoint URL that the webhooks should be sent to + */ + url: string; + /** + * List of webhook events to receive + * + * @see https://docs.lemonsqueezy.com/help/webhooks#event-types + */ + events: Array; + /** + * Signing secret (between 6 and 40 characters + */ + secret: string; +} + +export interface UpdateWebhookOptions { + /** + * ID of the webhook to update + */ + id: number; + /** + * Endpoint URL that the webhooks should be sent to + */ + url?: string; + /** + * List of webhook events to receive + * + * @see https://docs.lemonsqueezy.com/help/webhooks#event-types + */ + events?: Array; + /** + * Signing secret (between 6 and 40 characters + */ + secret?: string; +} + +export interface DeleteWebhookOptions { + /** + * ID of the webhook to delete + */ + id: number; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 99c2d41..214f2e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "strict": true, "target": "ESNext" }, - "include": ["./src/**/*.js"] + "include": ["./src/**/*.ts"] } diff --git a/tsup.config.ts b/tsup.config.ts index 2925700..0a003e7 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -6,7 +6,7 @@ export default defineConfig(({ watch = false }) => ({ clean: true, dts: true, entry: { - index: "./src/index.js", + index: "./src/index.ts", }, format: ["cjs", "esm"], minify: isProduction,