压缩历史至 95081aff

This commit is contained in:
ryan
2026-06-08 20:34:27 +08:00
commit 8a782525de
435 changed files with 71146 additions and 0 deletions
@@ -0,0 +1,491 @@
import { AxiosHeaders, type InternalAxiosRequestConfig } from 'axios';
import { BaseService } from '../core/base.service';
import { encodeBase64 } from '../../utils';
import type {
MerchantAPIKey,
CreateAPIKeyRequest,
UpdateAPIKeyRequest,
PayMerchantOrderRequest,
GetMerchantOrderRequest,
GetMerchantOrderResponse,
PaymentLink,
CreatePaymentLinkRequest,
UpdatePaymentLinkRequest,
PayByLinkRequest,
QueryMerchantOrderRequest,
QueryMerchantOrderResponse,
RefundMerchantOrderRequest,
RefundMerchantOrderResponse,
MerchantDistributeRequest,
MerchantDistributeResponse,
} from './types';
/**
* 商户服务
* 处理商户 API Key 管理和支付订单相关的 API 请求
*/
export class MerchantService extends BaseService {
protected static readonly basePath = '/api/v1/merchant';
// ==================== API Key 管理 ====================
/**
* 创建商户 API Key
* @param request - 创建 API Key 的请求参数
* @returns 创建的 API Key 信息(包含 client_secret)
* @throws {UnauthorizedError} 当未登录时
* @throws {ValidationError} 当参数验证失败时
*
* @example
* ```typescript
* const apiKey = await MerchantService.createAPIKey({
* app_name: '我的应用',
* app_homepage_url: 'https://example.com',
* app_description: '应用描述',
* redirect_uri: 'https://example.com/callback'
* });
* console.log('Client ID:', apiKey.client_id);
* console.log('Client Secret:', apiKey.client_secret); // 请保存,之后无法再次获取
* ```
*/
static async createAPIKey(request: CreateAPIKeyRequest): Promise<MerchantAPIKey> {
return this.post<MerchantAPIKey>('/api-keys', request);
}
/**
* 获取商户 API Key 列表
* @returns API Key 列表
* @throws {UnauthorizedError} 当未登录时
*
* @example
* ```typescript
* const apiKeys = await MerchantService.listAPIKeys();
* console.log('API Keys 数量:', apiKeys.length);
* ```
*/
static async listAPIKeys(): Promise<MerchantAPIKey[]> {
return this.get<MerchantAPIKey[]>('/api-keys');
}
/**
* 获取单个商户 API Key
* @param id - API Key ID
* @returns API Key 详细信息
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
*
* @example
* ```typescript
* const apiKey = await MerchantService.getAPIKey(123);
* console.log('应用名称:', apiKey.app_name);
* ```
*/
static async getAPIKey(id: string): Promise<MerchantAPIKey> {
return this.get<MerchantAPIKey>(`/api-keys/${ id }`);
}
/**
* 更新商户 API Key
* @param id - API Key ID
* @param request - 更新 API Key 的请求参数
* @returns void
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
* @throws {ValidationError} 当参数验证失败时
*
* @example
* ```typescript
* await MerchantService.updateAPIKey(123, {
* app_name: '新的应用名称',
* app_description: '新的描述'
* });
* ```
*/
static async updateAPIKey(
id: string,
request: UpdateAPIKeyRequest,
): Promise<void> {
return this.put<void>(`/api-keys/${ id }`, request);
}
/**
* 删除商户 API Key
* @param id - API Key ID
* @returns void
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
*
* @example
* ```typescript
* await MerchantService.deleteAPIKey(123);
* ```
*/
static async deleteAPIKey(id: string): Promise<void> {
return this.delete<void>(`/api-keys/${ id }`);
}
// ==================== 支付链接管理 ====================
/**
* 创建支付链接
* @param apiKeyId - API Key ID
* @param request - 创建支付链接请求参数
* @returns 创建的支付链接信息
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
*
* @example
* ```typescript
* const link = await MerchantService.createPaymentLink(123, {
* product_name: '测试商品',
* amount: 100,
* remark: '备注信息'
* });
* console.log('支付链接 Token:', link.token);
* ```
*/
static async createPaymentLink(apiKeyId: string, request: CreatePaymentLinkRequest): Promise<PaymentLink> {
return this.post<PaymentLink>(`/api-keys/${ apiKeyId }/payment-links`, request);
}
/**
* 获取支付链接列表
* @param apiKeyId - API Key ID
* @returns 支付链接列表
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
*
* @example
* ```typescript
* const links = await MerchantService.listPaymentLinks(123);
* console.log('支付链接数量:', links.length);
* ```
*/
static async listPaymentLinks(apiKeyId: string): Promise<PaymentLink[]> {
return this.get<PaymentLink[]>(`/api-keys/${ apiKeyId }/payment-links`);
}
/**
* 删除支付链接
* @param apiKeyId - API Key ID
* @param linkId - 支付链接 ID
* @returns void
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 或支付链接不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
*
* @example
* ```typescript
* await MerchantService.deletePaymentLink(123, 456);
* ```
*/
static async deletePaymentLink(apiKeyId: string, linkId: string): Promise<void> {
return this.delete<void>(`/api-keys/${ apiKeyId }/payment-links/${ linkId }`);
}
/**
* 更新支付链接
* @param apiKeyId - API Key ID
* @param linkId - 支付链接 ID
* @param request - 更新支付链接请求参数
* @returns void
* @throws {UnauthorizedError} 当未登录时
* @throws {NotFoundError} 当 API Key 或支付链接不存在时
* @throws {ForbiddenError} 当无权访问该 API Key 时
* @throws {ValidationError} 当参数验证失败时
*
* @example
* ```typescript
* await MerchantService.updatePaymentLink(123, 456, {
* product_name: '更新后的商品',
* amount: 200,
* remark: '新的备注',
* total_limit: 100,
* user_limit: 1
* });
* ```
*/
static async updatePaymentLink(
apiKeyId: string,
linkId: string,
request: UpdatePaymentLinkRequest
): Promise<void> {
return this.put<void>(`/api-keys/${ apiKeyId }/payment-links/${ linkId }`, request);
}
/**
* 通过 Token 获取支付链接信息
*
* @description
* 公开接口,用于支付页面获取支付链接详情。
* 无需登录即可访问。
*
* @param token - 支付链接 Token
* @returns 支付链接信息(包含商品名称、金额等)
* @throws {NotFoundError} 当支付链接不存在时
*
* @example
* ```typescript
* const linkInfo = await MerchantService.getPaymentLinkByToken('abc123');
* console.log('商品名称:', linkInfo.payment_link.product_name);
* console.log('金额:', linkInfo.payment_link.amount);
* ```
*/
static async getPaymentLinkByToken(token: string): Promise<PaymentLink> {
return this.get<PaymentLink>(`/payment-links/${ token }`);
}
/**
* 通过支付链接支付
*
* @description
* 用户使用此接口通过支付链接进行支付。
* 需要用户登录,并且用户余额充足。
*
* @param request - 支付请求参数(token 和 pay_key)
* @returns void
* @throws {UnauthorizedError} 当用户未登录时
* @throws {NotFoundError} 当支付链接不存在时
* @throws {ApiErrorBase} 当余额不足、支付密码错误等业务错误时
*
* @example
* ```typescript
* try {
* await MerchantService.payByLink({
* token: 'abc123',
* pay_key: '123456',
* remark: '备注信息'
* });
* console.log('支付成功');
* } catch (error) {
* console.error('支付失败:', error.message);
* }
* ```
*
* @remarks
* - 用户不能支付自己创建的支付链接
* - 用户余额必须充足
* - 支付成功后会扣除手续费(根据商户的支付等级)
*/
static async payByLink(request: PayByLinkRequest): Promise<void> {
return this.post<void>('/payment-links/pay', request);
}
// ==================== 商户支付订单 ====================
/**
* 查询商户订单信息
*
* @description
* 查询商户创建的订单详细信息,用于支付页面显示订单信息。
* 订单必须处于待支付状态。
*
* @param request - 查询订单请求参数
* @returns 订单信息和用户积分配置
* @throws {UnauthorizedError} 当用户未登录时
* @throws {NotFoundError} 当订单不存在时
* @throws {ValidationError} 当订单号格式错误时
* @throws {ApiErrorBase} 当订单已过期或已支付等业务错误时
*
* @example
* ```typescript
* // 从 URL 获取订单号
* const params = new URLSearchParams(window.location.search);
* const orderNo = params.get('order_no');
*
* if (orderNo) {
* try {
* const orderInfo = await MerchantService.getMerchantOrder({ order_no: orderNo });
* console.log('订单信息:', orderInfo.order);
* console.log('积分配置:', orderInfo.user_pay_config);
* } catch (error) {
* console.error('查询订单失败:', error.message);
* }
* }
* ```
*
* @remarks
* - 订单必须处于待支付状态
* - 返回的用户积分配置包含手续费率等信息
*/
static async getMerchantOrder(request: GetMerchantOrderRequest): Promise<GetMerchantOrderResponse> {
return this.get<GetMerchantOrderResponse>('/payment/order', { order_no: request.order_no });
}
/**
* 支付商户订单
*
* @description
* 用户使用此接口支付商户创建的订单。
* 需要用户登录,并且用户余额充足。
*
* @param request - 支付订单请求参数
* @returns void
* @throws {UnauthorizedError} 当用户未登录时
* @throws {NotFoundError} 当订单不存在或已过期时
* @throws {ValidationError} 当订单号格式错误时
* @throws {ApiErrorBase} 当余额不足、订单已支付等业务错误时
*
* @example
* ```typescript
* // 从 URL 获取订单号
* const params = new URLSearchParams(window.location.search);
* const orderNo = params.get('order_no');
*
* if (orderNo) {
* try {
* await MerchantService.payMerchantOrder({
* order_no: orderNo,
* pay_key: '123456' // 用户的支付密码
* });
* // 支付成功
* console.log('支付成功');
* } catch (error) {
* console.error('支付失败:', error.message);
* }
* }
* ```
*
* @remarks
* - 用户不能支付自己作为商户创建的订单
* - 订单必须在有效期内(5分钟)
* - 用户余额必须充足
* - 支付成功后会扣除手续费(根据用户的积分等级)
*/
static async payMerchantOrder(request: PayMerchantOrderRequest): Promise<void> {
return this.post<void>('/payment', request);
}
/**
* 商户查询订单状态
*
* @description
* 商户使用此接口主动查询订单的支付状态。
* 需要提供商户凭证(Client ID 和 Client Secret)。
*
* @param params - 查询参数
* @returns 订单状态信息
* @throws {ValidationError} 当参数验证失败时
* @throws {ApiErrorBase} 当商户凭证无效或订单不存在时
*
* @example
* ```typescript
* const orderStatus = await MerchantService.queryMerchantOrder({
* trade_no: 12345,
* pid: 'your_client_id',
* key: 'your_client_secret'
* });
*
* if (orderStatus.status === 1) {
* console.log('订单已支付');
* } else {
* console.log('订单未支付');
* }
* ```
*
* @remarks
* - 使用 GET 请求调用 `/api.php` 接口
* - 返回的 status 字段:1 表示已支付,0 表示未支付
*/
static async queryMerchantOrder(
params: QueryMerchantOrderRequest
): Promise<QueryMerchantOrderResponse> {
return this.rawGet<QueryMerchantOrderResponse>('/epay/api.php', params);
}
/**
* 商户退款
*
* @description
* 商户使用此接口对已支付的订单进行退款。
* 需要提供商户凭证(Client ID 和 Client Secret)。
* 退款金额必须与订单金额一致。
*
* @param params - 退款请求参数
* @returns 退款结果
* @throws {ValidationError} 当参数验证失败时
* @throws {ApiErrorBase} 当商户凭证无效、订单不存在或退款失败时
*
* @example
* ```typescript
* const result = await MerchantService.refundMerchantOrder({
* trade_no: 12345,
* money: 99.99,
* pid: 'your_client_id',
* key: 'your_client_secret'
* });
*
* if (result.code === 1) {
* console.log('退款成功');
* } else {
* console.error('退款失败:', result.msg);
* }
* ```
*
* @remarks
* - 使用 POST 请求调用 `/api.php` 接口
* - 退款金额必须与订单金额完全一致
* - 只能对状态为"成功"的订单进行退款
* - 返回的 code 字段:1 表示成功,-1 表示失败
*/
static async refundMerchantOrder(
params: RefundMerchantOrderRequest
): Promise<RefundMerchantOrderResponse> {
return this.rawPost<RefundMerchantOrderResponse>('/epay/api.php', params);
}
// ==================== 商户分发 ====================
/**
* 商户向用户分发积分
*
* @description
* 商户使用此接口向指定用户分发积分。
* 需要商户认证(Basic Auth),分发金额会从商户余额中扣除,
* 收款人获得扣除分发费率后的金额。
*
* @param request - 分发请求参数
* @returns 分发结果(包含订单号)
* @throws {ValidationError} 当参数验证失败时
* @throws {ApiErrorBase} 当余额不足、用户不存在等业务错误时
*
* @example
* ```typescript
* const result = await MerchantService.distribute({
* user_id: 123,
* username: 'alice',
* amount: 100,
* out_trade_no: 'DIST20251231001',
* remark: '新年奖励'
* });
* console.log('订单号:', result.trade_no);
* ```
*
* @remarks
* - 使用 POST 请求调用 `/pay/distribute` 接口(前端通过 `/lpay/distribute` 代理)
* - 需要通过 Basic Auth 提供商户凭证
* - 不能分发给商户自己
* - 商户余额必须充足
* - 分发会扣除分发费率(根据商户的支付等级)
*/
static async distribute(
request: MerchantDistributeRequest,
auth?: { client_id: string; client_secret: string }
): Promise<MerchantDistributeResponse> {
const config: InternalAxiosRequestConfig | undefined = auth
? {
headers: new AxiosHeaders({
Authorization: `Basic ${encodeBase64(`${auth.client_id}:${auth.client_secret}`)}`,
}),
}
: undefined;
return this.rawPost<MerchantDistributeResponse>('/lpay/distribute', request, config);
}
}