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 之间的依赖方向。注意依赖箭头始终指向抽象,具体实现可替换。

业务层 OrderService createOrder / queryOrder / doRefund «abstract» PayChannel pay() · query() · refund() · parseNotify() channelId 依赖 WechatChannel 微信支付 SDK 签名 / 解密 / 映射 AlipayChannel 支付宝 SDK RSA2 签名 / 映射 UnionPayChannel 银联 SDK 证书 / 映射 implements implements implements 业务层只依赖 PayChannel 抽象,新增渠道只需新增实现类 渠道差异(签名、参数、状态码)全部封装在实现内部

验证抽象接口是否达标

用以下检查项确认接口设计满足"业务层无需改动"的目标:

检查项通过标准
业务层是否直接 import 渠道 SDK否,只 import 抽象接口文件
新增渠道是否需要改 OrderService否,只新增实现类并注册
渠道特有字段是否污染主契约否,放入 extra 扩展区
渠道状态码是否统一翻译是,统一为 PayStatus 枚举
回调解析是否返回统一事件是,返回 PayNotifyEvent
核心原则:依赖倒置——高层业务模块不依赖低层渠道实现,两者都依赖抽象接口。这样后续接入新渠道时,业务层代码零改动。