165 lines
4.5 KiB
TypeScript
165 lines
4.5 KiB
TypeScript
/**
|
|
* Prodigi v4 API client.
|
|
*
|
|
* Handles order submission, product specs, and quotes.
|
|
* Sandbox: https://api.sandbox.prodigi.com/v4.0/
|
|
* Production: https://api.prodigi.com/v4.0/
|
|
*/
|
|
|
|
import type { PodOrder, PodRecipient, PodQuote } from "./types";
|
|
|
|
const SANDBOX_URL = "https://api.sandbox.prodigi.com/v4.0";
|
|
const PRODUCTION_URL = "https://api.prodigi.com/v4.0";
|
|
|
|
export class ProdigiClient {
|
|
private apiKey: string;
|
|
private baseUrl: string;
|
|
readonly enabled: boolean;
|
|
|
|
constructor() {
|
|
this.apiKey = process.env.PRODIGI_API_KEY || "";
|
|
const sandbox = process.env.POD_SANDBOX_MODE !== "false";
|
|
this.baseUrl = sandbox ? SANDBOX_URL : PRODUCTION_URL;
|
|
this.enabled = !!this.apiKey;
|
|
}
|
|
|
|
private get headers(): Record<string, string> {
|
|
return {
|
|
"X-API-Key": this.apiKey,
|
|
"Content-Type": "application/json",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a Prodigi print order.
|
|
*
|
|
* Items should include: sku, copies, sizing, assets.
|
|
* Recipient uses Prodigi address format.
|
|
*/
|
|
async createOrder(
|
|
items: {
|
|
sku: string;
|
|
copies: number;
|
|
sizing?: "fillPrintArea" | "fitPrintArea" | "stretchToPrintArea";
|
|
assets: { printArea: string; url: string }[];
|
|
}[],
|
|
recipient: PodRecipient,
|
|
shippingMethod: "Budget" | "Standard" | "Express" = "Budget",
|
|
metadata?: Record<string, string>,
|
|
): Promise<PodOrder> {
|
|
if (!this.enabled) throw new Error("Prodigi API key not configured");
|
|
|
|
const payload: any = {
|
|
shippingMethod,
|
|
recipient: {
|
|
name: recipient.name,
|
|
...(recipient.email ? { email: recipient.email } : {}),
|
|
address: {
|
|
line1: recipient.address1,
|
|
...(recipient.address2 ? { line2: recipient.address2 } : {}),
|
|
townOrCity: recipient.city,
|
|
...(recipient.stateCode ? { stateOrCounty: recipient.stateCode } : {}),
|
|
postalOrZipCode: recipient.zip,
|
|
countryCode: recipient.countryCode,
|
|
},
|
|
},
|
|
items: items.map((item) => ({
|
|
sku: item.sku,
|
|
copies: item.copies,
|
|
sizing: item.sizing || "fillPrintArea",
|
|
assets: item.assets,
|
|
})),
|
|
};
|
|
|
|
if (metadata) payload.metadata = metadata;
|
|
|
|
const resp = await fetch(`${this.baseUrl}/Orders`, {
|
|
method: "POST",
|
|
headers: this.headers,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!resp.ok) {
|
|
const text = await resp.text();
|
|
throw new Error(`Prodigi order error ${resp.status}: ${text.slice(0, 500)}`);
|
|
}
|
|
|
|
const result = await resp.json();
|
|
|
|
return {
|
|
id: String(result.id || result.order?.id || ""),
|
|
provider: "prodigi",
|
|
status: result.status?.stage || "InProgress",
|
|
items: items.map((i) => ({
|
|
sku: i.sku,
|
|
quantity: i.copies,
|
|
imageUrl: i.assets[0]?.url || "",
|
|
})),
|
|
recipient,
|
|
createdAt: new Date().toISOString(),
|
|
raw: result,
|
|
};
|
|
}
|
|
|
|
async getOrder(orderId: string): Promise<PodOrder> {
|
|
const resp = await fetch(`${this.baseUrl}/Orders/${orderId}`, {
|
|
headers: this.headers,
|
|
});
|
|
if (!resp.ok) throw new Error(`Prodigi get order error: ${resp.status}`);
|
|
|
|
const result = await resp.json();
|
|
const order = result.order || result;
|
|
|
|
return {
|
|
id: String(order.id || orderId),
|
|
provider: "prodigi",
|
|
status: order.status?.stage || "unknown",
|
|
items: [],
|
|
recipient: { name: "", address1: "", city: "", countryCode: "", zip: "" },
|
|
createdAt: order.created || new Date().toISOString(),
|
|
raw: result,
|
|
};
|
|
}
|
|
|
|
async getProduct(sku: string): Promise<any> {
|
|
const resp = await fetch(`${this.baseUrl}/products/${sku}`, {
|
|
headers: this.headers,
|
|
});
|
|
if (!resp.ok) throw new Error(`Prodigi product error: ${resp.status}`);
|
|
return resp.json();
|
|
}
|
|
|
|
async getQuote(
|
|
items: { sku: string; copies: number }[],
|
|
shippingMethod: "Budget" | "Standard" | "Express" = "Budget",
|
|
destinationCountry = "US",
|
|
): Promise<PodQuote> {
|
|
const payload = {
|
|
shippingMethod,
|
|
destinationCountryCode: destinationCountry,
|
|
items: items.map((i) => ({ sku: i.sku, copies: i.copies })),
|
|
};
|
|
|
|
const resp = await fetch(`${this.baseUrl}/quotes`, {
|
|
method: "POST",
|
|
headers: this.headers,
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!resp.ok) throw new Error(`Prodigi quote error: ${resp.status}`);
|
|
|
|
const result = await resp.json();
|
|
const quotes = result.quotes || [result];
|
|
const q = quotes[0] || {};
|
|
|
|
return {
|
|
items: (q.items || []).map((i: any) => ({
|
|
sku: i.sku,
|
|
unitCost: parseFloat(i.unitCost?.amount || "0"),
|
|
quantity: i.copies || 1,
|
|
})),
|
|
shippingCost: parseFloat(q.shipmentSummary?.items?.[0]?.cost?.amount || "0"),
|
|
totalCost: parseFloat(q.totalCost?.amount || "0"),
|
|
currency: q.totalCost?.currency || "USD",
|
|
};
|
|
}
|
|
}
|