Files
platform/packages/commerce/src/printful.ts
T

182 lines
6.8 KiB
TypeScript

import type { Printful } from "./util/types";
import { type DeepPartial, type PagingOptions } from "./util/misc";
const parsePayload = (res: Response): Promise<unknown> =>
res.json().catch(() => res.text().catch(() => undefined));
type ClientOptions = {
token: string;
storeId: string;
};
type Credentials = {
apiUrl: string;
authHeaders: Record<string, string>;
};
export class PrintfulError extends Error {
constructor(
message: string,
public status: number,
public payload: unknown,
) {
super(message);
this.name = "PrintfulError";
}
}
class VariantsClient {
constructor(private creds: Credentials) { }
async get(variantId: number | string): Promise<Printful.Products.SyncVariant | undefined> {
const res = await fetch(`${this.creds.apiUrl}/store/variants/${variantId}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (res.status === 404 || res.status === 400) return;
if (!res.ok) throw new PrintfulError("Failed to get Printful variant", res.status, await parsePayload(res));
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.SyncVariant>;
return payload.result;
}
}
class ProductsClient {
readonly Variants: VariantsClient;
constructor(private creds: Credentials) {
this.Variants = new VariantsClient(creds);
}
async list(opt: PagingOptions = {}): Promise<Printful.Products.SyncProduct[]> {
const allSyncProducts: Printful.Products.SyncProduct[] = [];
let offset = opt.offset ?? 0;
do {
const res = await fetch(`${this.creds.apiUrl}/store/products?offset=${offset}&limit=${opt.limit ?? 100}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (!res.ok) throw new PrintfulError("Failed to get all Printful products", res.status, await parsePayload(res));
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Products.SyncProduct>;
allSyncProducts.push(...payload.result);
offset = allSyncProducts.length;
if (allSyncProducts.length >= payload.paging.total) break;
} while (opt.forceAll);
return allSyncProducts;
}
async get(productId: number | string): Promise<Printful.Products.Product | undefined> {
const res = await fetch(`${this.creds.apiUrl}/store/products/${productId}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (res.status === 404 || res.status === 400) return;
if (!res.ok) throw new PrintfulError("Failed to get Printful product", res.status, await parsePayload(res));
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.Product>;
return payload.result;
}
async update(printfulProductId: number, printfulProduct: DeepPartial<Printful.Products.Product>) {
const res = await fetch(`${this.creds.apiUrl}/store/products/${printfulProductId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
},
body: JSON.stringify(printfulProduct),
});
if (!res.ok) throw new PrintfulError("Failed to update Printful product", res.status, await parsePayload(res));
}
}
class OrdersClient {
constructor(private creds: Credentials) { }
async create(printfulOrder: Printful.Orders.Order) {
const res = await fetch(`${this.creds.apiUrl}/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
},
body: JSON.stringify(printfulOrder),
});
if (!res.ok) throw new PrintfulError("Failed to create Printful order", res.status, await parsePayload(res));
}
async list(opt: PagingOptions = {}): Promise<Printful.Orders.Order[]> {
const allPrintfulOrders: Printful.Orders.Order[] = [];
let offset = opt.offset ?? 0;
do {
const res = await fetch(`${this.creds.apiUrl}/orders?offset=${offset}&limit=${opt.limit ?? 100}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (!res.ok) throw new PrintfulError("Failed to get all Printful orders", res.status, await parsePayload(res));
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Orders.Order>;
allPrintfulOrders.push(...payload.result);
offset = allPrintfulOrders.length;
if (allPrintfulOrders.length >= payload.paging.total) break;
} while (opt.forceAll);
return allPrintfulOrders;
}
}
class WebhooksClient {
constructor(private creds: Credentials) { }
async getConfig(): Promise<Printful.Webhook.Config | undefined> {
const res = await fetch(`${this.creds.apiUrl}/webhooks`, {
method: "GET",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
}
});
if (res.status === 404 || res.status === 400) return;
if (!res.ok) throw new PrintfulError("Failed to get webhook configuration", res.status, await parsePayload(res));
const payload = (await res.json()) as Printful.Webhook.Config;
return payload;
}
async register(url: string, events: Printful.Webhook.Event[]) {
const res = await fetch(`${this.creds.apiUrl}/webhooks`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
},
body: JSON.stringify({
"url": url,
"types": events
}),
});
if (!res.ok) throw new PrintfulError("Failed to register webhook URL", res.status, await parsePayload(res));
}
}
export class PrintfulClient {
readonly Products: ProductsClient;
readonly Orders: OrdersClient;
readonly Webhooks: WebhooksClient;
constructor(options: ClientOptions) {
const creds: Credentials = {
apiUrl: "https://api.printful.com",
authHeaders: {
Authorization: `Bearer ${options.token}`,
"X-PF-Store-Id": options.storeId,
},
};
this.Products = new ProductsClient(creds);
this.Orders = new OrdersClient(creds);
this.Webhooks = new WebhooksClient(creds);
}
static Util = {
getVariantMainImage(syncVariant: Printful.Products.SyncVariant): string {
const previewFile = syncVariant.files.find((f) => f.type === "preview");
return previewFile?.preview_url ?? syncVariant.product.image;
},
};
}