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

379 lines
10 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 { Config, Provide } from '@midwayjs/core';
import axios, { AxiosRequestConfig } from 'axios';
import { WpSite } from '../interface';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
@Provide()
export class WPService {
@Config('wpSite')
sites: WpSite[];
/**
* 构建 URL自动规范各段的斜杠避免出现多 / 或少 / 导致请求失败
* 使用示例this.buildURL(wpApiUrl, '/wp-json', 'wc/v3/products', productId)
*/
private buildURL(base: string, ...parts: Array<string | number>): string {
// 去掉 base 末尾多余斜杠,但不影响协议中的 //
const baseSanitized = String(base).replace(/\/+$/g, '');
// 规范各段前后斜杠
const segments = parts
.filter((p) => p !== undefined && p !== null)
.map((p) => String(p))
.map((s) => s.replace(/^\/+|\/+$/g, ''))
.filter(Boolean);
const joined = [baseSanitized, ...segments].join('/');
// 折叠除协议外的多余斜杠,例如 https://example.com//a///b -> https://example.com/a/b
return joined.replace(/([^:])\/{2,}/g, '$1/');
}
/**
* 获取 WordPress 数据
* @param wpApiUrl WordPress REST API 的基础地址
* @param endpoint API 端点路径(例如 wc/v3/products
* @param consumerKey WooCommerce 的消费者密钥
* @param consumerSecret WooCommerce 的消费者密钥
*/
geSite(id: string): WpSite {
let idx = this.sites.findIndex(item => item.id === id);
return this.sites[idx];
}
async fetchData<T>(
endpoint: string,
site: WpSite,
param: Record<string, any> = {}
): Promise<T> {
try {
const { wpApiUrl, consumerKey, consumerSecret } = site;
// 构建 URL规避多/或少/问题
const url = this.buildURL(wpApiUrl, '/wp-json', endpoint);
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const response = await axios.request({
url,
headers: {
Authorization: `Basic ${auth}`,
},
method: 'GET',
...param,
});
return response.data;
} catch (error) {
throw error;
}
}
async fetchPagedData<T>(
endpoint: string,
site: WpSite,
page: number = 1,
perPage: number = 100
): Promise<T[]> {
const allData: T[] = [];
const { wpApiUrl, consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
let hasMore = true;
while (hasMore) {
const config: AxiosRequestConfig = {
method: 'GET',
// 构建 URL规避多/或少/问题
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
headers: {
Authorization: `Basic ${auth}`,
},
params: {
page,
per_page: perPage,
},
};
try {
const response = await axios.request(config);
// Append the current page data
allData.push(...response.data);
// Check for more pages
const totalPages = parseInt(
response.headers['x-wp-totalpages'] || '1',
10
);
hasMore = page < totalPages;
page += 1;
} catch (error) {
throw error;
}
}
return allData;
}
async getProducts(site: WpSite): Promise<WpProduct[]> {
return await this.fetchPagedData<WpProduct>('/wc/v3/products', site);
}
async getVariations(site: WpSite, productId: number): Promise<Variation[]> {
return await this.fetchPagedData<Variation>(
`/wc/v3/products/${productId}/variations`,
site
);
}
async getVariation(
site: WpSite,
productId: number,
variationId: number
): Promise<Variation> {
return await this.fetchData<Variation>(
`/wc/v3/products/${productId}/variations/${variationId}`,
site
);
}
async getOrder(
siteId: string,
orderId: string
): Promise<Record<string, any>> {
const site = this.geSite(siteId);
return await this.fetchData<Record<string, any>>(
`/wc/v3/orders/${orderId}`,
site
);
}
async getOrders(siteId: string): Promise<Record<string, any>[]> {
const site = this.geSite(siteId);
return await this.fetchPagedData<Record<string, any>>(
'/wc/v3/orders',
site
);
}
/**
* 获取 WooCommerce Subscriptions
* 优先尝试 wc/v1/subscriptionsSubscriptions 插件提供),失败时回退 wc/v3/subscriptions。
* 返回所有分页合并后的订阅数组。
*/
async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
const site = this.geSite(siteId);
try {
return await this.fetchPagedData<Record<string, any>>(
'/wc/v1/subscriptions',
site
);
} catch (e) {
// fallback
return await this.fetchPagedData<Record<string, any>>(
'/wc/v3/subscriptions',
site
);
}
}
async getOrderRefund(
siteId: string,
orderId: string,
refundId: number
): Promise<Record<string, any>> {
const site = this.geSite(siteId);
return await this.fetchData<Record<string, any>>(
`/wc/v3/orders/${orderId}/refunds/${refundId}`,
site
);
}
async getOrderRefunds(
siteId: string,
orderId: number
): Promise<Record<string, any>[]> {
const site = this.geSite(siteId);
return await this.fetchPagedData<Record<string, any>>(
`/wc/v3/orders/${orderId}/refunds`,
site
);
}
async getOrderNote(
siteId: string,
orderId: number,
noteId: number
): Promise<Record<string, any>> {
const site = this.geSite(siteId);
return await this.fetchData<Record<string, any>>(
`/wc/v3/orders/${orderId}/notes/${noteId}`,
site
);
}
async getOrderNotes(
siteId: string,
orderId: number
): Promise<Record<string, any>[]> {
const site = this.geSite(siteId);
return await this.fetchPagedData<Record<string, any>>(
`/wc/v3/orders/${orderId}/notes`,
site
);
}
async updateData<T>(
endpoint: string,
site: WpSite,
data: Record<string, any>
): Promise<Boolean> {
const { wpApiUrl, consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const config: AxiosRequestConfig = {
method: 'PUT',
// 构建 URL规避多/或少/问题
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
headers: {
Authorization: `Basic ${auth}`,
},
data,
};
try {
await axios.request(config);
return true;
} catch (error) {
return false;
}
}
/**
* 更新 WooCommerce 产品
* @param productId 产品 ID
* @param data 更新的数据
*/
async updateProduct(
site: WpSite,
productId: string,
data: UpdateWpProductDTO
): Promise<Boolean> {
const { regular_price, sale_price, ...params } = data;
return await this.updateData(`/wc/v3/products/${productId}`, site, {
...params,
regular_price: regular_price ? regular_price.toString() : null,
sale_price: sale_price ? sale_price.toString() : null,
});
}
/**
* 更新 WooCommerce 产品 上下架状态
* @param productId 产品 ID
* @param status 状态
* @param stock_status 上下架状态
*/
async updateProductStatus(
site: WpSite,
productId: string,
status: ProductStatus,
stock_status: ProductStockStatus
): Promise<Boolean> {
const res = await this.updateData(`/wc/v3/products/${productId}`, site, {
status,
manage_stock: false, // 为true的时候用quantity控制库存为false时直接用stock_status控制
stock_status,
});
return res;
}
/**
* 更新 WooCommerce 产品变体
* @param productId 产品 ID
* @param variationId 变体 ID
* @param data 更新的数据
*/
async updateVariation(
site: WpSite,
productId: string,
variationId: string,
data: UpdateVariationDTO
): Promise<Boolean> {
const { regular_price, sale_price, ...params } = data;
return await this.updateData(
`/wc/v3/products/${productId}/variations/${variationId}`,
site,
{
...params,
regular_price: regular_price ? regular_price.toString() : null,
sale_price: sale_price ? sale_price.toString() : null,
}
);
}
/**
* 更新 Order
*/
async updateOrder(
site: WpSite,
orderId: string,
data: Record<string, any>
): Promise<Boolean> {
return await this.updateData(`/wc/v3/orders/${orderId}`, site, data);
}
async createShipment(
site: WpSite,
orderId: string,
data: Record<string, any>
) {
const { wpApiUrl, consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const config: AxiosRequestConfig = {
method: 'POST',
// 构建 URL规避多/或少/问题
url: this.buildURL(
wpApiUrl,
'/wp-json',
'wc-ast/v3/orders',
orderId,
'shipment-trackings'
),
headers: {
Authorization: `Basic ${auth}`,
},
data,
};
return await axios.request(config);
}
async deleteShipment(
site: WpSite,
orderId: string,
trackingId: string,
): Promise<Boolean> {
const { wpApiUrl, consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
console.log('del', orderId, trackingId);
// 删除接口: DELETE /wp-json/wc-shipment-tracking/v3/orders/<order_id>/shipment-trackings/<tracking_id>
const config: AxiosRequestConfig = {
method: 'DELETE',
// 构建 URL规避多/或少/问题
url: this.buildURL(
wpApiUrl,
'/wp-json',
'wc-ast/v3/orders',
orderId,
'shipment-trackings',
trackingId
),
headers: {
Authorization: `Basic ${auth}`,
},
};
return await axios.request(config);
}
}