UNPKG

lazada-iop-client

Version:

Lazada IOP Client SDK for Node JS

247 lines (213 loc) 6.64 kB
import * as crypto from "crypto"; import axios, { AxiosRequestConfig } from "axios"; import FormData from "form-data"; const SDK_VERSION = "lazop-sdk-js-20250422"; const AUTH_URL = "https://auth.lazada.com/oauth/authorize"; const APIGateway: Record<string, string> = { SG: "https://api.lazada.sg/rest", MY: "https://api.lazada.com.my/rest", VN: "https://api.lazada.vn/rest", TH: "https://api.lazada.co.th/rest", PH: "https://api.lazada.com.ph/rest", ID: "https://api.lazada.co.id/rest", }; interface LazadaClientOptions { apiKey: string; apiSecret: string; region: keyof typeof APIGateway; callbackUrl: string; debug?: boolean; } class LazadaClient { private apiKey: string; private apiSecret: string; private region: keyof typeof APIGateway; private callbackUrl: string; private sysParams: Record<string, string>; private apiParams: Record<string, string | number>; private fileParams: Record<string, Buffer>; private debug: boolean; constructor({ apiKey, apiSecret, region, callbackUrl, debug = false, }: LazadaClientOptions) { if (!APIGateway[region]) throw new Error(`Unsupported region: ${region}`); this.apiKey = apiKey; this.apiSecret = apiSecret; this.region = region; this.callbackUrl = callbackUrl; this.debug = debug; this.sysParams = { app_key: this.apiKey, sign_method: "sha256", }; this.apiParams = {}; this.fileParams = {}; } private generateTimestamp(): number { return Date.now(); } private getSysParams(): Record<string, number> { return { ...this.sysParams, timestamp: this.generateTimestamp(), }; } setAccessToken(token: string): this { this.sysParams.access_token = token; return this; } changeRegion(region: keyof typeof APIGateway): this { if (!APIGateway[region]) throw new Error(`Unsupported region: ${region}`); this.region = region; return this; } addAPIParam(key: string, val: string | number): this { this.apiParams[key] = val; return this; } addFileParam(key: string, val: Buffer): this { this.fileParams[key] = val; return this; } setCallbackUrl(url: string): void { this.callbackUrl = url; } makeAuthURL(): string { const params = new URLSearchParams({ response_type: "code", force_auth: "true", country: this.region.toLowerCase(), redirect_uri: this.callbackUrl, client_id: this.apiKey, }); return `${AUTH_URL}?${params.toString()}`; } async requestToken(code: string): Promise<any> { const timestamp = this.generateTimestamp(); const sysParams = this.getSysParams(); const params = { ...sysParams, code, app_key: this.apiKey, sign_method: "sha256", timestamp, }; const sign = this.sign("/auth/token/create", params); const allParams = { ...params, sign }; const urlParams = new URLSearchParams( Object.entries(allParams).map(([k, v]) => [k, String(v)]) ); const fullUrl = `https://auth.lazada.com/rest/auth/token/create?${urlParams.toString()}`; if (this.debug) { console.log("🛠️ Lazada Token Request Debug:"); console.log("URL:", fullUrl); console.log("Params:", allParams); console.log("Sign:", sign); } try { const response = await axios.get(fullUrl); return response.data; } catch (err: any) { if (this.debug) { console.error( "❌ Lazada API Error:", err.response?.data || err.message ); } throw err; } } private sign( path: string, parameters: Record<string, string | number> ): string { const sortedKeys = Object.keys(parameters).sort(); let paramStr = path; if (this.debug) { console.log("🔐 SIGNING DEBUG:"); console.log("Path:", path); console.log("Sorted Params:"); sortedKeys.forEach((key) => { console.log(` ${key}: ${parameters[key]}`); }); } sortedKeys.forEach((key) => { paramStr += key + String(parameters[key]); }); const signature = crypto .createHmac("sha256", this.apiSecret) .update(paramStr) .digest("hex") .toUpperCase(); if (this.debug) { console.log("🔏 Signature Base String:", paramStr); console.log("🔑 Signature Result:", signature); } return signature; } private getServerURL(): string { const url = APIGateway[this.region]; if (!url) throw new Error(`Region "${this.region}" is not supported.`); return url; } async execute( apiPath: string, apiMethod: "GET" | "POST" = "GET", bodyParams: Record<string, string | number> = {} ): Promise<any> { const sysParams = this.getSysParams(); const sign = this.sign(apiPath, { ...sysParams, ...this.apiParams }); const allParams = { ...sysParams, ...this.apiParams, sign }; const urlParams = new URLSearchParams( Object.entries(allParams).map(([k, v]) => [k, String(v)]) ); const fullUrl = `${this.getServerURL()}${apiPath}?${urlParams.toString()}`; if (this.debug) { console.log("🛠️ Lazada Request Debug:"); console.log("URL:", fullUrl); console.log("Method:", apiMethod); console.log("Sys Params:", sysParams); console.log("API Params:", this.apiParams); console.log("Sign:", sign); } const config: AxiosRequestConfig = { method: apiMethod, url: fullUrl, headers: {}, }; if (apiMethod === "POST") { const form = new FormData(); Object.entries(this.apiParams).forEach(([key, val]) => form.append(key, String(val)) ); Object.entries(bodyParams).forEach(([key, val]) => form.append(key, String(val)) ); Object.entries(this.fileParams).forEach(([filename, buffer]) => form.append("image", buffer, filename) ); config.data = form; config.headers = form.getHeaders(); } try { const response = await axios(config); return response.data; } catch (err: any) { if (this.debug) { console.error( "❌ Lazada API Error:", err.response?.data || err.message ); } throw err; } finally { this.apiParams = {}; this.fileParams = {}; } } } export default LazadaClient;