lazada-iop-client
Version:
Lazada IOP Client SDK for Node JS
247 lines (213 loc) • 6.64 kB
text/typescript
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;