forked from yoone/API
1
0
Fork 0
API/src/service/shopyy.service.ts

1037 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Inject, Provide } from '@midwayjs/core';
import axios, { AxiosRequestConfig } from 'axios';
import * as fs from 'fs';
import * as FormData from 'form-data';
import { SiteService } from './site.service';
import { Site } from '../entity/site.entity';
import { UnifiedReviewDTO } from '../dto/site-api.dto';
import { ShopyyReview } from '../dto/shopyy.dto';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
import { UnifiedSearchParamsDTO } from '../dto/api.dto';
/**
* ShopYY平台服务实现
*/
@Provide()
export class ShopyyService {
/**
* 获取ShopYY评论列表
* @param site 站点配置
* @param params 查询参数
* @returns 分页评论列表
*/
async getReviews(site: any, params: any): Promise<any> {
// ShopYY API: GET /reviews/list
const { items, total, totalPages, page, per_page } =
await this.fetchResourcePaged<any>(site, 'comments/list', params);
return {
items: items.map(this.mapReview),
total,
totalPages,
page,
per_page
};
}
mapReview(review: ShopyyReview): UnifiedReviewDTO {
return {
id: review.id,
product_id: review.product_id,
// countryId: mapReview.country_id,
// ip: mapReview.ip,
rating: review.star,
author: review.customer_name,
email: review.customer_email,
// reply_content: mapReview.reply_content,
content: review.content,
status: review.status.toString(),
// is_image: mapReview.is_image,
// images: mapReview.images,
date_modified: review.updated_at ? new Date(review.updated_at * 1000).toISOString() : undefined,
date_created: review.created_at ? new Date(review.created_at * 1000).toISOString() : undefined,
raw: review
}
}
/**
* 获取单个ShopYY评论
* @param site 站点配置
* @param reviewId 评论ID
* @returns 评论详情
*/
async getReview(site: any, reviewId: string | number): Promise<any> {
// ShopYY API: GET /reviews/{id}
const response = await this.request(site, `comments/${reviewId}`, 'GET');
return response.data;
}
/**
* 创建ShopYY评论
* @param site 站点配置
* @param data 评论数据
* @returns 创建结果
*/
async createReview(site: any, data: any): Promise<any> {
// ShopYY API: POST /reviews/create
const response = await this.request(site, 'comments/create', 'POST', data);
return response.data;
}
/**
* 更新ShopYY评论
* @param site 站点配置
* @param reviewId 评论ID
* @param data 更新数据
* @returns 更新结果
*/
async updateReview(site: any, reviewId: string | number, data: any): Promise<any> {
// ShopYY API: POST /reviews/update/{id}
const response = await this.request(site, `comments/update/${reviewId}`, 'POST', data);
return response.data;
}
/**
* 删除ShopYY评论
* @param site 站点配置
* @param reviewId 评论ID
* @returns 删除结果
*/
async deleteReview(site: any, reviewId: string | number): Promise<boolean> {
try {
// ShopYY API: DELETE /reviews/delete/{id}
await this.request(site, `comments/delete/${reviewId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY评论失败';
throw new Error(`删除ShopYY评论失败: ${errorMessage}`);
}
}
fetchMediaPaged(site: any, params: Record<string, any>): Promise<{ items: any[]; total: number; totalPages: number; page: number; per_page: number; }> {
throw new Error('Method not implemented.');
}
convertMediaToWebp(siteId: number, mediaIds: Array<number | string>): Promise<{ converted: any[]; failed: Array<{ id: number | string; error: string; }>; }> {
throw new Error('Method not implemented.');
}
getSubscriptions?(siteId: number): Promise<any[]> {
throw new Error('Method not implemented.');
}
getCustomers(site: any): Promise<any[]> {
throw new Error('Method not implemented.');
}
@Inject()
private readonly siteService: SiteService;
/**
* 构建ShopYY API请求URL
* @param baseUrl 基础URL
* @param endpoint API端点
* @returns 完整URL
*/
private buildURL(baseUrl: string, endpoint: string): string {
// ShopYY API URL格式https://{shop}.shopyy.com/openapi/{version}/{endpoint}
const base = baseUrl.replace(/\/$/, '');
const end = endpoint.replace(/^\//, '');
return `${base}/${end}`;
}
/**
* 构建ShopYY API请求头
* @param site 站点配置
* @returns 请求头
*/
private buildHeaders(site: Site): Record<string, string> {
if (!site?.token) {
throw new Error(`获取站点${site?.name}数据,但失败,因为未设置站点令牌配置`)
}
return {
'Content-Type': 'application/json',
token: site.token || ''
};
}
/**
* 发送ShopYY API请求
* @param site 站点配置
* @param endpoint API端点
* @param method 请求方法
* @param data 请求数据
* @param params 请求参数
* @returns 响应数据
*/
private async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
const url = this.buildURL(site.apiUrl, endpoint);
const headers = this.buildHeaders(site);
const config: AxiosRequestConfig = {
url,
method,
headers,
params,
data
};
try {
const response = await axios(config);
return response.data;
} catch (error) {
console.error('ShopYY API请求失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 通用分页获取资源
*/
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
const page = Number(params.page || 1);
const limit = Number(params.per_page ?? 20);
const where = params.where && typeof params.where === 'object' ? params.where : {};
let orderby: string | undefined = params.orderby;
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
if (!orderby && params.order && typeof params.order === 'object') {
const entries = Object.entries(params.order as Record<string, any>);
if (entries.length > 0) {
const [field, dir] = entries[0];
orderby = field;
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
}
}
// 映射统一入参到平台入参
const requestParams = {
...where,
...(params.search ? { search: params.search } : {}),
...(params.status ? { status: params.status } : {}),
...(orderby ? { orderby } : {}),
...(order ? { order } : {}),
page,
limit
};
const response = await this.request(site, endpoint, 'GET', null, requestParams);
if (response?.code !== 0) {
throw new Error(response?.msg)
}
return {
items: (response.data.list || []) as T[],
total: response.data?.paginate?.total || 0,
totalPages: response.data?.paginate?.pageTotal || 0,
page: response.data?.paginate?.current || requestParams.page,
per_page: response.data?.paginate?.pagesize || requestParams.limit,
};
}
/**
* 获取ShopYY产品列表
* @param site 站点配置
* @param page 页码
* @param pageSize 每页数量
* @returns 分页产品列表
*/
async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> {
// ShopYY API: GET /products
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
const response = await this.request(site, 'products', 'GET', null, {
page,
page_size: pageSize,
fields: 'id,name,sku,handle,status,type,stock_status,stock_quantity,images,regular_price,sale_price,tags,variations'
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
totalPages: response.meta?.pagination?.total_pages || 0,
page: response.meta?.pagination?.current_page || page,
per_page: response.meta?.pagination?.per_page || pageSize
};
}
/**
* 获取单个ShopYY产品
* @param site 站点配置
* @param productId 产品ID
* @returns 产品详情
*/
async getProduct(site: any, productId: string | number): Promise<any> {
// ShopYY API: GET /products/{id}
const response = await this.request(site, `products/${productId}`, 'GET');
return response.data;
}
/**
* 获取ShopYY产品变体列表
* @param site 站点配置
* @param productId 产品ID
* @param page 页码
* @param pageSize 每页数量
* @returns 分页变体列表
*/
async getVariations(site: any, productId: number, page: number = 1, pageSize: number = 100): Promise<any> {
// ShopYY API: GET /products/{id}/variations
const response = await this.request(site, `products/${productId}/variations`, 'GET', null, {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
totalPages: response.meta?.pagination?.total_pages || 0,
page: response.meta?.pagination?.current_page || page,
per_page: response.meta?.pagination?.per_page || pageSize
};
}
/**
* 获取ShopYY产品变体详情
* @param site 站点配置
* @param productId 产品ID
* @param variationId 变体ID
* @returns 变体详情
*/
async getVariation(site: any, productId: number, variationId: number): Promise<any> {
// ShopYY API: GET /products/{product_id}/variations/{variation_id}
const response = await this.request(site, `products/${productId}/variations/${variationId}`, 'GET');
return response.data;
}
mapOrderSearchParams(params: UnifiedSearchParamsDTO){
const { after, before, ...restParams } = params;
return {
...restParams,
...(after ? { created_at_min: after } : {}),
...(before ? { created_at_max: before } : {}),
}
}
/**
* 获取ShopYY订单列表
* @param site 站点配置或站点ID
* @param page 页码
* @param pageSize 每页数量
* @returns 分页订单列表
*/
async getOrders(site: any | number, page: number = 1, pageSize: number = 100, params: UnifiedSearchParamsDTO = {}): Promise<any> {
// 如果传入的是站点ID则获取站点配置
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
// ShopYY API: GET /orders
const response = await this.request(siteConfig, 'orders', 'GET', null, {
page,
limit: pageSize,
...params
});
return {
items: response.data?.list || [],
total: response?.data?.paginate?.total || 0,
totalPages: response?.data?.paginate?.pageTotal || 0,
page: response?.data?.paginate?.current || page,
per_page: response?.data?.paginate?.pagesize || pageSize
};
}
async getAllOrders(site: any | number, params: Record<string, any> = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
const firstPage = await this.getOrders(site, 1, 100);
const { items: firstPageItems, totalPages} = firstPage;
// const { page = 1, per_page = 100 } = params;
// 如果只有一页数据,直接返回
if (totalPages <= 1) {
return firstPageItems;
}
// 限制最大页数,避免过多的并发请求
const actualMaxPages = Math.min(totalPages, maxPages);
// 收集所有页面数据,从第二页开始
const allItems = [...firstPageItems];
let currentPage = 2;
// 使用并发限制,避免一次性发起过多请求
while (currentPage <= actualMaxPages) {
const batchPromises: Promise<any[]>[] = [];
const batchSize = Math.min(concurrencyLimit, actualMaxPages - currentPage + 1);
// 创建当前批次的并发请求
for (let i = 0; i < batchSize; i++) {
const page = currentPage + i;
const pagePromise = this.getOrders(site, page, 100)
.then(pageResult => pageResult.items)
.catch(error => {
console.error(`获取第 ${page} 页数据失败:`, error);
return []; // 如果某页获取失败,返回空数组,不影响整体结果
});
batchPromises.push(pagePromise);
}
// 等待当前批次完成
const batchResults = await Promise.all(batchPromises);
// 合并当前批次的数据
for (const pageItems of batchResults) {
allItems.push(...pageItems);
}
// 移动到下一批次
currentPage += batchSize;
}
return allItems;
}
/**
* 获取ShopYY订单详情
* @param siteId 站点ID
* @param orderId 订单ID
* @returns 订单详情
*/
async getOrder(siteId: string, orderId: string): Promise<any> {
const site = await this.siteService.get(Number(siteId));
// ShopYY API: GET /orders/{id}
const response = await this.request(site, `orders/${orderId}`, 'GET');
return response.data;
}
/**
* 创建ShopYY产品
* @param site 站点配置
* @param data 产品数据
* @returns 创建结果
*/
async createProduct(site: any, data: any): Promise<any> {
// ShopYY API: POST /products
const response = await this.request(site, 'products', 'POST', data);
return response.data;
}
/**
* 更新ShopYY产品
* @param site 站点配置
* @param productId 产品ID
* @param data 更新数据
* @returns 更新结果
*/
async updateProduct(site: any, productId: string, data: any): Promise<boolean> {
try {
// ShopYY API: PUT /products/{id}
await this.request(site, `products/${productId}`, 'PUT', data);
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '更新ShopYY产品失败';
throw new Error(`更新ShopYY产品失败: ${errorMessage}`);
}
}
/**
* 更新ShopYY产品状态
* @param site 站点配置
* @param productId 产品ID
* @param status 产品状态
* @param stockStatus 库存状态
* @returns 更新结果
*/
async updateProductStatus(site: any, productId: string, status: string, stockStatus: string): Promise<boolean> {
// ShopYY产品状态映射
const shopyyStatus = status === 'publish' ? 1 : 0;
const shopyyStockStatus = stockStatus === 'instock' ? 1 : 0;
try {
await this.request(site, `products/${productId}`, 'PUT', {
status: shopyyStatus,
stock_status: shopyyStockStatus
});
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '更新ShopYY产品状态失败';
throw new Error(`更新ShopYY产品状态失败: ${errorMessage}`);
}
}
/**
* 更新ShopYY产品变体
* @param site 站点配置
* @param productId 产品ID
* @param variationId 变体ID
* @param data 更新数据
* @returns 更新结果
*/
async updateVariation(site: any, productId: string, variationId: string, data: any): Promise<boolean> {
try {
// ShopYY API: PUT /products/{product_id}/variations/{variation_id}
await this.request(site, `products/${productId}/variations/${variationId}`, 'PUT', data);
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '更新ShopYY产品变体失败';
throw new Error(`更新ShopYY产品变体失败: ${errorMessage}`);
}
}
/**
* 更新ShopYY订单
* @param site 站点配置
* @param orderId 订单ID
* @param data 更新数据
* @returns 更新结果
*/
async updateOrder(site: any, orderId: string, data: Record<string, any>): Promise<boolean> {
try {
// ShopYY API: PUT /orders/{id}
await this.request(site, `orders/${orderId}`, 'PUT', data);
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '更新ShopYY订单失败';
throw new Error(`更新ShopYY订单失败: ${errorMessage}`);
}
}
/**
* 创建ShopYY履约信息
* @param site 站点配置
* @param orderId 订单ID
* @param data 履约数据
* @returns 创建结果
*/
async createFulfillment(site: Site, orderId: string, data: any): Promise<any> {
// ShopYY API: POST /orders/{id}/shipments
const fulfillmentData = {
tracking_number: data.tracking_number,
carrier_code: data.carrier_code,
carrier_name: data.carrier_name,
shipping_method: data.shipping_method
};
const response = await this.request(site, `orders/${orderId}/shipments`, 'POST', fulfillmentData);
return response.data;
}
/**
* 删除ShopYY履约信息
* @param site 站点配置
* @param orderId 订单ID
* @param fulfillmentId 履约跟踪ID
* @returns 删除结果
*/
async deleteFulfillment(site: any, orderId: string, fulfillmentId: string): Promise<boolean> {
try {
// ShopYY API: DELETE /orders/{order_id}/shipments/{fulfillment_id}
await this.request(site, `orders/${orderId}/fulfillments/${fulfillmentId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY履约信息失败';
throw new Error(`删除ShopYY履约信息失败: ${errorMessage}`);
}
}
/**
* 获取ShopYY订单履约跟踪信息
* @param site 站点配置
* @param orderId 订单ID
* @returns 履约跟踪信息列表
*/
async getFulfillments(site: any, orderId: string): Promise<any[]> {
try {
// ShopYY API: GET /orders/{id}/shipments
const response = await this.request(site, `orders/${orderId}/fulfillments`, 'GET');
// 返回履约跟踪信息列表
return response.data || [];
} catch (error) {
// 如果订单没有履约跟踪信息,返回空数组
if (error.response?.status === 404) {
return [];
}
const errorMessage = error.response?.data?.msg || error.message || '获取履约跟踪信息失败';
throw new Error(`获取履约跟踪信息失败: ${errorMessage}`);
}
}
/**
* 更新ShopYY订单履约跟踪信息
* @param site 站点配置
* @param orderId 订单ID
* @param trackingId 履约跟踪ID
* @param data 更新数据
* @returns 更新结果
*/
async updateFulfillment(site: any, orderId: string, trackingId: string, data: {
tracking_number?: string;
tracking_provider?: string;
date_shipped?: string;
status_shipped?: string;
}): Promise<any> {
try {
// ShopYY API: PUT /orders/{order_id}/shipments/{tracking_id}
const fulfillmentData: any = {};
// 只传递有值的字段
if (data.tracking_number !== undefined) {
fulfillmentData.tracking_number = data.tracking_number;
}
if (data.tracking_provider !== undefined) {
fulfillmentData.carrier_name = data.tracking_provider;
}
if (data.date_shipped !== undefined) {
fulfillmentData.shipped_at = data.date_shipped;
}
if (data.status_shipped !== undefined) {
fulfillmentData.status = data.status_shipped;
}
const response = await this.request(site, `orders/${orderId}/fulfillments/${trackingId}`, 'PUT', fulfillmentData);
return response.data;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '更新履约跟踪信息失败';
throw new Error(`更新履约跟踪信息失败: ${errorMessage}`);
}
}
/**
* 获取ShopYY订单备注
* @param site 站点配置
* @param orderId 订单ID
* @param page 页码
* @param pageSize 每页数量
* @returns 分页订单备注列表
*/
async getOrderNotes(site: any, orderId: string | number, page: number = 1, pageSize: number = 100): Promise<any> {
// ShopYY API: GET /orders/{id}/notes
const response = await this.request(site, `orders/${orderId}/notes`, 'GET', null, {
page,
page_size: pageSize
});
return {
items: response.data || [],
total: response.meta?.pagination?.total || 0,
totalPages: response.meta?.pagination?.total_pages || 0,
page: response.meta?.pagination?.current_page || page,
per_page: response.meta?.pagination?.per_page || pageSize
};
}
/**
* 创建ShopYY订单备注
* @param site 站点配置
* @param orderId 订单ID
* @param data 备注数据
* @returns 创建结果
*/
async createOrderNote(site: any, orderId: string | number, data: any): Promise<any> {
// ShopYY API: POST /orders/{id}/notes
const noteData = {
note: data.note,
is_customer_note: data.is_customer_note || false
};
const response = await this.request(site, `orders/${orderId}/notes`, 'POST', noteData);
return response.data;
}
/**
* 创建ShopYY订单
* @param site 站点配置
* @param data 订单数据
* @returns 创建结果
*/
async createOrder(site: any, data: any): Promise<any> {
// ShopYY API: POST /orders
const response = await this.request(site, 'orders', 'POST', data);
return response.data;
}
/**
* 删除ShopYY订单
* @param site 站点配置
* @param orderId 订单ID
* @returns 删除结果
*/
async deleteOrder(site: any, orderId: string | number): Promise<boolean> {
try {
// ShopYY API: DELETE /orders/{id}
await this.request(site, `orders/${orderId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY订单失败';
throw new Error(`删除ShopYY订单失败: ${errorMessage}`);
}
}
/**
* 批量处理ShopYY产品
* @param site 站点配置
* @param data 批量操作数据
* @returns 处理结果
*/
async batchProcessProducts(site: any, data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
// ShopYY API: POST /products/batch
const response = await this.request(site, 'products/batch', 'POST', data);
const result = response.data;
// 转换 ShopYY 批量操作结果为统一格式
const errors: Array<{identifier: string, error: string}> = [];
// 假设 ShopYY 返回格式与 WooCommerce 类似: { create: [...], update: [...], delete: [...] }
// 错误信息可能在每个项目的 error 字段中
const checkForErrors = (items: any[]) => {
items.forEach(item => {
if (item.error) {
errors.push({
identifier: String(item.id || item.sku || 'unknown'),
error: typeof item.error === 'string' ? item.error : JSON.stringify(item.error)
});
}
});
};
// 检查每个操作类型的结果中的错误
if (result.create) checkForErrors(result.create);
if (result.update) checkForErrors(result.update);
if (result.delete) checkForErrors(result.delete);
return {
total: (data.create?.length || 0) + (data.update?.length || 0) + (data.delete?.length || 0),
processed: (result.create?.length || 0) + (result.update?.length || 0) + (result.delete?.length || 0),
created: result.create?.length || 0,
updated: result.update?.length || 0,
deleted: result.delete?.length || 0,
errors: errors
};
}
/**
* 获取ShopYY客户列表
* @param site 站点配置
* @param params 查询参数
* @returns 分页客户列表
*/
async fetchCustomersPaged(site: any, params: any): Promise<any> {
// ShopYY API: GET /customers
const { items, total, totalPages, page, per_page } =
await this.fetchResourcePaged<any>(site, 'customers/list', params);
return {
items,
total,
totalPages,
page,
per_page
};
}
/**
* 获取单个ShopYY客户
* @param site 站点配置
* @param customerId 客户ID
* @returns 客户详情
*/
async getCustomer(site: any, customerId: string | number): Promise<any> {
// ShopYY API: GET /customers/{id}
const response = await this.request(site, `customers/${customerId}`, 'GET');
return response.data;
}
/**
* 创建ShopYY客户
* @param site 站点配置
* @param data 客户数据
* @returns 创建结果
*/
async createCustomer(site: any, data: any): Promise<any> {
// ShopYY API: POST /customers
const customerData = {
firstname: data.first_name || '',
lastname: data.last_name || '',
email: data.email || '',
phone: data.phone || '',
password: data.password || ''
};
const response = await this.request(site, 'customers', 'POST', customerData);
return response.data;
}
/**
* 更新ShopYY客户
* @param site 站点配置
* @param customerId 客户ID
* @param data 更新数据
* @returns 更新结果
*/
async updateCustomer(site: any, customerId: string | number, data: any): Promise<any> {
// ShopYY API: PUT /customers/{id}
const customerData = {
firstname: data.first_name || '',
lastname: data.last_name || '',
email: data.email || '',
phone: data.phone || ''
};
const response = await this.request(site, `customers/${customerId}`, 'PUT', customerData);
return response.data;
}
/**
* 删除ShopYY客户
* @param site 站点配置
* @param customerId 客户ID
* @returns 删除结果
*/
async deleteCustomer(site: any, customerId: string | number): Promise<boolean> {
try {
// ShopYY API: DELETE /customers/{id}
await this.request(site, `customers/${customerId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY客户失败';
throw new Error(`删除ShopYY客户失败: ${errorMessage}`);
}
}
/**
* 创建ShopYY媒体
* @param site 站点配置
* @param file 文件
* @returns 创建结果
*/
async createMedia(site: any, file: any): Promise<any> {
// ShopYY API: POST /media/create
const response = await this.requestWithFormData(site, 'media/create', {
file: fs.createReadStream(file.filepath),
});
return response.data;
}
/**
* 更新ShopYY媒体
* @param site 站点配置
* @param mediaId 媒体ID
* @param data 更新数据
* @returns 更新结果
*/
async updateMedia(site: any, mediaId: string | number, data: any): Promise<any> {
// ShopYY API: POST /media/update/{id}
const response = await this.requestWithFormData(site, `media/update/${mediaId}`, data);
return response.data;
}
/**
* 删除ShopYY媒体
* @param site 站点配置
* @param mediaId 媒体ID
* @returns 删除结果
*/
async deleteMedia(site: any, mediaId: string | number): Promise<boolean> {
try {
// ShopYY API: DELETE /media/delete/{id}
await this.request(site, `media/delete/${mediaId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY媒体失败';
throw new Error(`删除ShopYY媒体失败: ${errorMessage}`);
}
}
/**
* 使用multipart/form-data发送ShopYY API请求
* @param site 站点配置
* @param endpoint API端点
* @param formData 表单数据
* @returns 响应数据
*/
private async requestWithFormData(site: any, endpoint: string, formData: Record<string, any>): Promise<any> {
const url = this.buildURL(site.apiUrl, endpoint);
const headers = this.buildHeaders(site);
const form = new FormData();
for (const key in formData) {
form.append(key, formData[key]);
}
const config: AxiosRequestConfig = {
url,
method: 'POST',
headers: {
...headers,
...form.getHeaders(),
},
data: form,
};
try {
const response = await axios(config);
return response.data;
} catch (error) {
console.error('ShopYY API (form-data)请求失败:', error.response?.data || error.message);
throw error;
}
}
/**
* 构建产品链接
* @param site 站点信息
* @param product 产品信息
* @param preview 是否是预览链接
* @returns
*/
buildProductPermalink(site: Site, product: any, preview = false): string {
// 确保 product 和 product.handle 存在
if (!product || !product.handle) {
// 如果 product 或 product.handle 不存在, 返回空字符串
return '';
}
// 移除 websiteUrl 末尾的斜杠 (如果有)
const websiteUrl = site.websiteUrl.endsWith('/') ? site.websiteUrl.slice(0, -1) : site.websiteUrl;
// 拼接 URL
let permalink = `${websiteUrl}/products/${product.handle}`;
// 如果是预览, 添加预览参数
if (preview) {
permalink += '?preview=1';
}
// 返回拼接后的产品 URL
return permalink;
}
getApiClient(site: any): any {
throw new Error('Method not implemented.');
}
/**
* 获取ShopYY webhook列表
* @param site 站点配置
* @param params 查询参数
* @returns 分页webhook列表
*/
async getWebhooks(site: any, params: any): Promise<any> {
// ShopYY API: GET /webhooks
const { items, total, totalPages, page, per_page } =
await this.fetchResourcePaged<any>(site, 'webhooks', params);
return {
items,
total,
totalPages,
page,
per_page
};
}
/**
* 获取单个ShopYY webhook
* @param site 站点配置
* @param webhookId webhook ID
* @returns webhook详情
*/
async getWebhook(site: any, webhookId: string | number): Promise<any> {
// ShopYY API: GET /webhooks/{id}
const response = await this.request(site, `webhooks/${webhookId}`, 'GET');
return response.data;
}
/**
* 创建ShopYY webhook
* @param site 站点配置
* @param data webhook数据
* @returns 创建结果
*/
async createWebhook(site: any, data: any): Promise<any> {
// ShopYY API: POST /webhooks
const response = await this.request(site, 'webhooks', 'POST', data);
return response.data;
}
/**
* 更新ShopYY webhook
* @param site 站点配置
* @param webhookId webhook ID
* @param data 更新数据
* @returns 更新结果
*/
async updateWebhook(site: any, webhookId: string | number, data: any): Promise<any> {
// ShopYY API: PUT /webhooks/{id}
const response = await this.request(site, `webhooks/${webhookId}`, 'PUT', data);
return response.data;
}
/**
* 删除ShopYY webhook
* @param site 站点配置
* @param webhookId webhook ID
* @returns 删除结果
*/
async deleteWebhook(site: any, webhookId: string | number): Promise<boolean> {
try {
// ShopYY API: DELETE /webhooks/{id}
await this.request(site, `webhooks/${webhookId}`, 'DELETE');
return true;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '删除ShopYY webhook失败';
throw new Error(`删除ShopYY webhook失败: ${errorMessage}`);
}
}
/**
* 批量履约订单
* @param site 站点配置
* @param data 履约数据
* @returns 履约结果
*/
async batchFulfillOrders(site: any, data: {
order_number: string;
tracking_company: string;
tracking_number: string;
courier_code: number;
note: string;
mode: "replace" | 'cover' | null;
}): Promise<any> {
try {
// ShopYY API: POST /orders/fulfillment/batch
const response = await this.request(site, 'orders/fulfillment/batch', 'POST', data);
return response.data;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '批量履约订单失败';
throw new Error(`批量履约订单失败: ${errorMessage}`);
}
}
/**
* 部分履约订单
* @param site 站点配置
* @param data 部分履约数据
* @returns 履约结果
*/
async partFulfillOrder(site: any, data: {
order_number: string;
note: string;
tracking_company: string;
tracking_number: string;
courier_code: string;
products: Array<{
quantity: number;
order_product_id: string;
}>;
}): Promise<any> {
try {
// ShopYY API: POST /orders/fulfillment/part
const response = await this.request(site, 'orders/fulfillment/part', 'POST', data);
return response.data;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '部分履约订单失败';
throw new Error(`部分履约订单失败: ${errorMessage}`);
}
}
/**
* 取消履约订单
* @param site 站点配置
* @param data 取消履约数据
* @returns 取消结果
*/
async cancelFulfillment(site: any, data: {
order_id: string;
fullfillment_id: string;
}): Promise<boolean> {
try {
// ShopYY API: POST /orders/fulfillment/cancel
const response = await this.request(site, 'orders/fulfillment/cancel', 'POST', data);
return response.code === 0;
} catch (error) {
const errorMessage = error.response?.data?.msg || error.message || '取消履约订单失败';
throw new Error(`取消履约订单失败: ${errorMessage}`);
}
}
}