支付插件
支付插件体系说明
插件接口
PaymentPluginManager
插件方法描述
/**
* 获取支付插件beanId
*
* @return beanId
*/
String getPluginId();
/**
* 获取支付插件名称
*
* @return 插件名称
*/
String getPluginName();
/**
* 自定义客户端配置信息
*
* @return 配置信息
*/
List<TextField> definitionClientConfig();
/**
* 支付接口
*
* @param bill 支付参数
* @return 支付结果
*/
Map pay(PayBill bill);
/**
* 支付结果异步回调通知接口
*
* @param clientTypeEnum 支付客户端 {@link ClientTypeEnum}
* @return 通知结果
*/
String onCallback(ClientTypeEnum clientTypeEnum);
/**
* 主动查询支付结果
*
* @param billSn 支付账单编号
* @return 支付结果
*/
String onQuery(String billSn);
/**
* 支付交易申请退款接口
*
* @param bill 申请退款参数
* @return 退款结果
*/
Map onTradeRefund(RefundBill bill);
/**
* 查询支付交易申请退款结果接口
*
* @param bill 申请退款参数
* @return 退款结果
*/
Map queryRefundStatus(RefundBill bill);
/**
* 关闭支付交易
*
* @param outTradeNo 交易号
*/
void closeTrade(String outTradeNo);
现有支付方式
中国银联、支付宝、微信、银联在线
开发新的支付插件
实现插件接口
@Service
public class YourPlugin implements PaymentPluginManager {
//其他方法略
}
注意:用@Service注解为spring bean,以便spring ioc识别,将插件扫描加入插件体系。
定义插件id及名字
@Override
public String getPluginId() {
return "myPluginName";
}
@Override
public String getPluginName() {
return "xx支付";
}
注意:需要保持插件id的唯一性,这是插件机制调起插件依据
定义支付配置项
支付配置项的目的是定义用户使用支付插件前需要配置的一些支付参数,如秘钥、证书等,以支付宝配置举例,如下:
要达到如上效果,首先需要理解 TextField类:
public class TextField {
@ApiModelProperty(name = "label", value = "表单 名称")
private String label;
@ApiModelProperty(name = "name", value = "表单 name")
private String name;
@ApiModelProperty(name = "value", value = "值")
private String value;
@ApiModelProperty(name = "type", value = "前端input标签type类型,默认:text")
private String type;
}
上述这个类在支付接口的 definitionClientConfig 方法中,我们假设需要定义PC端支付参数中的一个配置项“商户秘钥”:
/**
* 自定义客户端配置信息
*
* @return 配置信息
*/
@Override
public List<TextField> definitionClientConfig() {
List<TextField> resultList = new ArrayList<>();
TextField appPrivateKey = new TextField();
appPrivateKey.setLabel("应用私钥");
appPrivateKey.setName("app_private_key");
appPrivateKey.setValue("");
appPrivateKey.setType("textarea");
TextField appId = new TextField();
appId.setLabel("应用ID");
appId.setName("app_id");
appId.setValue("");
appId.setType("textarea");
TextField appPublicCertPath = new TextField();
appPublicCertPath.setLabel("应用公钥证书文件");
appPublicCertPath.setName("app_public_cert_id");
appPublicCertPath.setValue("");
appPublicCertPath.setType("file");
TextField alipayPublicCertPath = new TextField();
alipayPublicCertPath.setLabel("支付宝公钥证书文件");
alipayPublicCertPath.setName("alipay_public_cert_id");
alipayPublicCertPath.setValue("");
alipayPublicCertPath.setType("file");
TextField alipayRootCertPath = new TextField();
alipayRootCertPath.setLabel("支付宝根证书文件");
alipayRootCertPath.setName("alipay_root_cert_id");
alipayRootCertPath.setValue("");
alipayRootCertPath.setType("file");
resultList.add(appPrivateKey);
resultList.add(appId);
resultList.add(appPublicCertPath);
resultList.add(alipayPublicCertPath);
resultList.add(alipayRootCertPath);
return resultList;
}
支付配置的获取
用户配置了支付的参数(如秘钥等)在支付调起时需要获取到这些参数,可以通过如下方法获取:
Map<String, String> ThirdPlatformManager.getConfig(clientType, pluginId);
入参:
clientType
客户端类型,参见: com.enation.app.javashop.model.payment.enums.ClientTypeEnum
pluginId
支付插件beanid
返回值
- 类型Map
编写支付逻辑
@Override
public Map pay(PayBill bill) {
//在这里编写插件的支付逻辑
}
支付调起插件时,会调用此方法,入参为PayBill(支付账单参数),返回值为一个Map
入参
在订单创建时,系统会创建一个“支付账单”,支付体系面向的是“支付账单”而非“订单”,这里的PayBill就是“支付账单”,对应的是es_payment_bill表
支付帐单表:es_payment_bill
字段名 类型与长度 备注 bill_id bigint(20) 主键ID bill_sn varchar(50) 付款单号,提交给第三方平台单号 sub_sn bigint(20) 子业务编号(如交易单编号或订单编号等) return_trade_no varchar(100) 第三方平台返回交易号 is_pay int(1) 是否已支付 0:否,1:是 service_type varchar(50) 子业务类型(参考com.enation.app.javashop.model.trade.order.enums.TradeTypeEnum) payment_name varchar(50) 支付方式名称 pay_config longtext 支付参数(730版本中此字段已废弃) trade_price decimal(20,2) 交易金额 payment_plugin_id decimal(20,2) 支付插件BeanId
PayBill类说明:
public class PayBill {
/**
* 编号(交易或订单编号,或其它要扩展的其它类型编号)
*/
private String subSn;
/**
* 账单编号,要传递给第三方平台的,不要管,系统自动生成
*/
private String billSn;
/**
* 要支付的金额
*/
private Double orderPrice;
/**
* normal:正常的网页跳转
* qr:二维码扫描
*/
private String payMode;
/**
* 交易类型
*/
private TradeTypeEnum tradeType;
/**
* 客户端类型
*/
private ClientTypeEnum clientTypeEnum;
/**
* 支付插件
*/
private String pluginId;
/**
* 订单商品描述
* 长度为1-127个字符,订单多个商品取其中一个商品名称即可
*/
private String goodsDesc;
}
这里值得关注的是:tradeType和subSn两个参数:
tradeType:交易类型,参见com.enation.app.javashop.model.trade.order.enums.TradeTypeEnum
subSn: 订单或交易的单号
当支付成功时,系统会通过这两个参数来确定要更改状态的订单。
返回值
返回值为Map类型,这里需要把前端需要的参数通过压入map传递给前端,如:
Map<String, String> result = new TreeMap<>();
params.put("gateway_url", getUrl());
return result;
回调地址的获取
第三方支付平台一般都需要在调用他的支付接口时传递回调地址和参数,在AbstractPaymentPlugin基类中内置了获取上述参数的方法:
/**
* 获取同步通知url
*/
protected String getReturnUrl(PayBill bill) {
//实现逻辑参考代码
}
/**
* 获取异步通知url
*/
protected String getCallBackUrl(TradeTypeEnum tradeType, ClientType clientType) {
//实现逻辑参考代码
}
支付回调方法的实现
@Override
public void onReturn(TradeTypeEnum tradeType) {
//在这里实现支付同步回调的逻辑(如果平台需要)
}
@Override
public String onCallback( ClientType clientType) {
//在这里实现支付异步回调的逻辑
}
第三方平台支付成功后,一般都需要异步回调通知,那会通过插件机制调用到插件的onCallback方法,在这里需要做的操作一般为:参数验证签名、更改支付账单状态.
支付账单状态的改变
可以通过下面的方法更改支付账单的支付状态:
/**
* 支付回调后执行方法
*
* @param billSn 支付账号单
* @param returnTradeNo 第三方平台回传的支付单号
* @param payPrice 支付金额
*/
com.enation.app.javashop.service.payment.PaymentBillManager.paySuccess(billSn, returnTradeNo, payPrice);
订单状态的改变
Javashop在内部处理了订单支付状态的变更,具体方式为:
三方支付退款逻辑的实现
需要实现下面两个方法:
@Override
public boolean onTradeRefund(RefundBill bill) {
}
@Override
public String queryRefundStatus(RefundBill bill) {
}