Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

feat: add webhook event constructor #23

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/client/client.class.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
constructEvent,
createCheckout,
getUser,
listAllCheckouts,
listAllCustomers,
listAllDiscounts,
listAllFiles,
listAllLicenseKeyInstances,
Expand All @@ -14,6 +16,7 @@ import {
listAllSubscriptions,
listAllVariants,
retrieveCheckout,
retrieveCustomer,
retrieveDiscount,
retrieveFile,
retrieveLicenseKey,
Expand All @@ -32,6 +35,7 @@ import type {
CreateCheckoutOptions,
GetUserOptions,
ListAllCheckoutsOptions,
ListAllCustomersOptions,
ListAllDiscountsOptions,
ListAllFilesOptions,
ListAllLicenseKeyInstancesOptions,
Expand All @@ -44,6 +48,7 @@ import type {
ListAllSubscriptionsOptions,
ListAllVariantsOptions,
RetrieveCheckoutOptions,
RetrieveCustomerOptions,
RetrieveDiscountOptions,
RetrieveFileOptions,
RetrieveLicenseKeyInstanceOptions,
Expand All @@ -57,6 +62,7 @@ import type {
RetrieveVariantOptions,
UpdateSubscriptionOptions,
} from "~/modules";
import { LemonsqueezyWebhookPayload } from "~/modules/webhook/webhook.types";

export class LemonsqueezyClient {
private _apiKey: string;
Expand Down Expand Up @@ -573,4 +579,59 @@ export class LemonsqueezyClient {
...options,
});
}

/**
* Retrieve customer
*
* @description Retrieves the customer with the given ID
*
* @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer
*
* @param {String} options.id - The ID of the customer to retrieve
*
* @returns A customer object
*/
public async retrieveCustomer(options: RetrieveCustomerOptions) {
return retrieveCustomer({
apiKey: this._apiKey,
...options,
});
}

/**
* List all customers
*
* @description Returns a paginated list of customers
*
* @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers
*
* @param {Object} [options]
*
* @returns Returns a paginated list of customer objects ordered by `created_at` (descending)
*/
public async listAllCustomers(options: ListAllCustomersOptions = {}) {
return listAllCustomers({
apiKey: this._apiKey,
...options,
});
}

/**
* Construct webhook event
*
* @description Constructs an event object
*
* @param {String | Uint8Array} payload - Raw text body received from Lemonsqueezy
* @param {String} header - Value of the `X-Signature` header received from Lemonsqueezy
* @param {String} secret - Your Lemonsqueezy webhook signing secret
*
* @returns An event object
*/
public constructEvent(
payload: LemonsqueezyWebhookPayload,
header: string,
secret: string
) {
return constructEvent(payload, header, secret);
}
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export { LemonsqueezyClient } from "./client";
export {
constructEvent,
createCheckout,
getUser,
listAllCheckouts,
listAllCustomers,
listAllDiscounts,
listAllFiles,
listAllLicenseKeyInstances,
Expand All @@ -14,6 +16,7 @@ export {
listAllSubscriptions,
listAllVariants,
retrieveCheckout,
retrieveCustomer,
retrieveDiscount,
retrieveFile,
retrieveLicenseKey,
Expand Down
30 changes: 17 additions & 13 deletions src/modules/checkout/checkout.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ export interface LemonsqueezyBillingAddress {
*
* @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
*/
country: string;
country?: string;
/**
* A pre-filled billing address zip/postal code
*/
zip: string;
zip?: string;
}

export interface LemonsqueezyCheckoutData {
billing_address: LemonsqueezyBillingAddress;
billing_address?: LemonsqueezyBillingAddress;
/**
* An object containing any custom data to be passed to the checkout
*/
custom?: Array<any>;
custom?: Record<string, any>;
/**
* A pre-filled discount code
*/
discount_code?: string;
/**
* A pre-filled email address
*/
email: string;
email?: string;
/**
* A pre-filled name
*/
name: string;
name?: string;
/**
* A pre-filled tax number
*/
Expand Down Expand Up @@ -73,6 +73,10 @@ export interface LemonsqueezyCheckoutOptions {
* If `false`, hide the product media
*/
media?: boolean;
/**
* If false, hide the "You will be charged..." subscription preview text
*/
subscription_preview?: boolean;
}

export interface LemonsqueezyCheckoutPreview {
Expand All @@ -96,7 +100,7 @@ export interface LemonsqueezyProductOptions {
/**
* A custom description for the product
*/
description: string;
description?: string;
/**
* An array of variant IDs to enable for this checkout. If this is empty, all variants will be enabled
*/
Expand All @@ -108,23 +112,23 @@ export interface LemonsqueezyProductOptions {
/**
* A custom name for the product
*/
name: string;
name?: string;
/**
* A custom text to use for the order receipt email button
*/
receipt_button_text: string;
receipt_button_text?: string;
/**
* A custom URL to use for the order receipt email button
*/
receipt_link_url: string;
receipt_link_url?: string;
/**
* A custom thank you note to use for the order receipt email
*/
receipt_thank_you_note: string;
receipt_thank_you_note?: string;
/**
* A custom URL to redirect to after a successful purchase
*/
redirect_url: string;
redirect_url?: string;
}

/**
Expand Down Expand Up @@ -217,7 +221,7 @@ export interface CreateCheckoutOptions extends SharedLemonsqueezyOptions {
* future (i.e. the customer is moved to a different subscription "tier") the
* new variant's price will be used from that moment forward.
*/
custom_price: number;
custom_price?: number | null;
/**
* An ISO-8601 formatted date-time string indicating when the checkout expires
*
Expand Down
28 changes: 28 additions & 0 deletions src/modules/customer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## 👤 Customer

[![Docs](https://img.shields.io/badge/-Docs-blue.svg?style=for-the-badge)](https://docs.lemonsqueezy.com/api/customers)

```typescript
import { LemonsqueezyClient } from 'lemonsqueezy.ts';

const client = new LemonsqueezyClient('YOUR_API_KEY');

const customer = await client.retrieveCustomer({
id: '...',
});

const customers = await client.listAllCustomers();
```

```typescript
import { retrieveCustomer, listAllCustomers } from 'lemonsqueezy.ts/customer';

const customer = await retrieveCustomer({
apiKey: 'YOUR_API_KEY',
id: '...',
});

const customers = await listAllCustomers({
apiKey: 'YOUR_API_KEY',
});
```
56 changes: 56 additions & 0 deletions src/modules/customer/customer.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
ListAllCustomersOptions,
ListAllCustomersResult,
RetrieveCustomerOptions,
RetrieveCustomerResult,
} from "./customer.types";
import type { SharedModuleOptions } from "~/shared";
import { requestLemonSqueeze } from "~/shared";

/**
* List all customers
*
* @description Returns a paginated list of customers
*
* @docs https://docs.lemonsqueezy.com/api/customers#list-all-customers
*
* @param {Object} [options]
*
* @returns Returns a paginated list of customer objects ordered by `created_at` (descending)
*/
export async function listAllCustomers(
options: ListAllCustomersOptions & SharedModuleOptions
): Promise<ListAllCustomersResult> {
const { storeId, email, ...rest } = options;

return requestLemonSqueeze<ListAllCustomersResult>({
params: {
...(storeId ? { store_id: storeId } : {}),
...(email ? { email: email } : {}),
},
path: "/customers",
...rest,
});
}

/**
* Retrieve customer
*
* @description Retrieves the customer with the given ID
*
* @docs https://docs.lemonsqueezy.com/api/customers#retrieve-a-customer
*
* @param {String} options.id - The ID of the customer to retrieve
*
* @returns A customer object
*/
export async function retrieveCustomer(
options: RetrieveCustomerOptions & SharedModuleOptions
): Promise<RetrieveCustomerResult> {
const { id, ...rest } = options;

return requestLemonSqueeze<RetrieveCustomerResult>({
path: `/customers/${id}`,
...rest,
});
}
38 changes: 38 additions & 0 deletions src/modules/customer/customer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it, expect, beforeAll } from "vitest";

import { listAllCustomers, retrieveCustomer } from ".";

describe.concurrent("Customer", () => {
const apiKey = process.env.LEMON_SQUEEZY_API_KEY as string;

beforeAll(() => {
if (!apiKey) throw "No LEMON_SQUEEZY_API_KEY environment variable found";
});

it("Retrieve customer", async () => {
const customers = await listAllCustomers({
apiKey,
});
if (!customers.data.length) throw new Error("No customers found");

const customer = await retrieveCustomer({
apiKey,
id: customers.data.at(0)!.id,
});

expect(customer).toBeDefined();
expect(customer.data).toBeDefined();
expect(customer.data).not.toBeNull();
expect(customer.errors).toBeUndefined();
});

it("List all customers", async () => {
const customers = await listAllCustomers({
apiKey,
});

expect(customers).toBeDefined();
expect(Array.isArray(customers.data)).toBe(true);
expect(customers.errors).toBeUndefined();
});
});
Loading