1024 lines
32 KiB
TypeScript
1024 lines
32 KiB
TypeScript
/**
|
||
* https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||
*/
|
||
import { ILogger, 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 { ShopyyGetOneOrderResult, ShopyyReview } from '../dto/shopyy.dto';
|
||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||
import { UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
||
/**
|
||
* ShopYY平台服务实现
|
||
*/
|
||
@Provide()
|
||
export class ShopyyService {
|
||
@Inject()
|
||
logger: ILogger;
|
||
/**
|
||
* 获取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 响应数据
|
||
*/
|
||
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 response = await this.request(site, endpoint, 'GET', null, params);
|
||
return this.mapPageResponse<T>(response, params);
|
||
}
|
||
mapPageResponse<T>(response: any, query: Record<string, any>) {
|
||
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 || query.page,
|
||
per_page: response.data?.paginate?.pagesize || query.limit,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取ShopYY产品列表
|
||
* @param site 站点配置
|
||
* @param page 页码
|
||
* @param pageSize 每页数量
|
||
* @returns 分页产品列表
|
||
*/
|
||
async getProducts(site: any, page: number = 1, pageSize: number = 100, where: Record<string, any> = {}): Promise<any> {
|
||
// ShopYY API: GET /products
|
||
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
||
const response = await this.request(site, 'products', 'GET', null, {
|
||
page,
|
||
page_size: pageSize,
|
||
...where
|
||
});
|
||
|
||
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 = 3000, params: ShopyyGetAllOrdersParams = {}): 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: ShopyyGetAllOrdersParams = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
|
||
const firstPage = await this.getOrders(site, 1, 100, params);
|
||
|
||
const { items: firstPageItems, totalPages } = firstPage;
|
||
|
||
// 如果只有一页数据,直接返回
|
||
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, params)
|
||
.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<ShopyyGetOneOrderResult> {
|
||
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 = {
|
||
data: [{
|
||
order_number: orderId,
|
||
tracking_company: data.tracking_company,
|
||
tracking_number: data.tracking_number,
|
||
carrier_code: data.carrier_code,
|
||
note: "note",
|
||
mode: ""
|
||
}]
|
||
};
|
||
const response = await this.request(site, `orders/fulfillments`, '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/fulfillments/{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;
|
||
fulfillment_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}`);
|
||
}
|
||
}
|
||
|
||
}
|