2. 支付渠道抽象接口设计
为什么需要支付渠道抽象接口
移动支付应用通常需要同时接入微信支付、支付宝、银联云闪付等多个渠道。如果业务代码直接调用各渠道 SDK,那么每新增一个渠道,订单、退款、对账等业务逻辑都要跟着改。抽象接口的目标是把"渠道差异"收敛到接口实现内部,业务层只依赖统一契约。
本案例定义四个核心方法:下单(pay)、查询(query)、退款(refund)、回调解析(parseNotify)。业务层只持有 PayChannel 引用,运行时注入具体渠道实现。
接口契约设计
先定义统一请求/响应对象,再定义渠道接口。请求对象承载业务层能提供的通用字段,响应对象承载业务层需要消费的通用结果,渠道特有字段放入 extra 扩展区,避免污染主契约。
// pay_channel.dart —— 统一支付渠道抽象
abstract class PayChannel {
/// 渠道标识,如 wechat / alipay / unionpay
String get channelId;
/// 统一下单:返回渠道预支付信息(如 prepayId、二维码链接)
Future<PayOrderResult> pay(PayOrderRequest request);
/// 订单查询:以商户订单号查询渠道侧状态
Future<PayQueryResult> query(String outTradeNo);
/// 退款:按商户订单号发起退款
Future<RefundResult> refund(RefundRequest request);
/// 回调解析:把渠道异步通知转成统一事件
Future<PayNotifyEvent> parseNotify(Map<String, String> raw);
}
class PayOrderRequest {
final String outTradeNo; // 商户订单号,全局唯一
final int totalFee; // 金额,单位:分
final String subject; // 商品标题
final String notifyUrl; // 异步通知地址
final Map<String, String> extra; // 渠道特有参数
PayOrderRequest({
required this.outTradeNo,
required this.totalFee,
required this.subject,
required this.notifyUrl,
this.extra = const {},
});
}
class PayOrderResult {
final bool success;
final String? prepayId; // 渠道预支付标识
final String? codeUrl; // 扫码支付二维码链接
final String? errCode;
final String? errMsg;
PayOrderResult({required this.success, this.prepayId,
this.codeUrl, this.errCode, this.errMsg});
}
class PayQueryResult {
final String outTradeNo;
final PayStatus status; // 统一状态枚举
final int? paidFee;
PayQueryResult({required this.outTradeNo,
required this.status, this.paidFee});
}
enum PayStatus { pending, paying, success, failed, refunded }
class RefundRequest {
final String outTradeNo;
final String outRefundNo;
final int refundFee;
final int totalFee;
RefundRequest({required this.outTradeNo, required this.outRefundNo,
required this.refundFee, required this.totalFee});
}
class RefundResult {
final bool accepted;
final String? refundId;
final String? errMsg;
RefundResult({required this.accepted, this.refundId, this.errMsg});
}
class PayNotifyEvent {
final String outTradeNo;
final PayStatus status;
final int paidFee;
final String rawChannel; // 来源渠道
PayNotifyEvent({required this.outTradeNo, required this.status,
required this.paidFee, required this.rawChannel});
}
渠道实现与业务层解耦
每个渠道实现 PayChannel,内部完成签名、参数映射、SDK 调用和状态翻译。业务层通过工厂或依赖注入获取实例,只调用接口方法。
// wechat_channel.dart —— 微信渠道实现(骨架)
class WechatChannel implements PayChannel {
@override
String get channelId => 'wechat';
@override
Future<PayOrderResult> pay(PayOrderRequest req) async {
// 1. 组装微信统一下单参数(appid、mch_id、nonce_str...)
// 2. 计算签名 sign
// 3. 调用微信统一下单 API
// 4. 将返回的 prepay_id / code_url 映射为 PayOrderResult
return PayOrderResult(success: true, prepayId: 'wx_prepay_xxx');
}
@override
Future<PayQueryResult> query(String outTradeNo) async {
// 调用微信订单查询,将 trade_state 翻译为 PayStatus
return PayQueryResult(outTradeNo: outTradeNo, status: PayStatus.success);
}
@override
Future<RefundResult> refund(RefundRequest req) async {
// 调用微信退款 API,返回 refund_id
return RefundResult(accepted: true, refundId: 'wx_refund_xxx');
}
@override
Future<PayNotifyEvent> parseNotify(Map<String, String> raw) async {
// 验签 + 解密 + 字段映射
return PayNotifyEvent(
outTradeNo: raw['out_trade_no']!,
status: PayStatus.success,
paidFee: int.parse(raw['total_fee']!),
rawChannel: 'wechat',
);
}
}
// 业务层:只依赖抽象,不关心具体渠道
class OrderService {
final PayChannel channel;
OrderService(this.channel);
Future<PayOrderResult> createOrder(PayOrderRequest req) {
return channel.pay(req); // 切换渠道时此处代码不变
}
}
新增支付宝渠道时,只需新建
AlipayChannel implements PayChannel,业务层 OrderService 无需任何修改。这就是抽象接口带来的开闭原则收益。渠道抽象接口的结构与调用关系
下图展示业务层、抽象接口、各渠道实现以及渠道 SDK 之间的依赖方向。注意依赖箭头始终指向抽象,具体实现可替换。
验证抽象接口是否达标
用以下检查项确认接口设计满足"业务层无需改动"的目标:
| 检查项 | 通过标准 |
|---|---|
| 业务层是否直接 import 渠道 SDK | 否,只 import 抽象接口文件 |
| 新增渠道是否需要改 OrderService | 否,只新增实现类并注册 |
| 渠道特有字段是否污染主契约 | 否,放入 extra 扩展区 |
| 渠道状态码是否统一翻译 | 是,统一为 PayStatus 枚举 |
| 回调解析是否返回统一事件 | 是,返回 PayNotifyEvent |
核心原则:依赖倒置——高层业务模块不依赖低层渠道实现,两者都依赖抽象接口。这样后续接入新渠道时,业务层代码零改动。