​ 支付宝H5支付---证书模式 ​



支付宝H5支付---证书模式

在文章之前,简单吐槽一下支付宝的​​官网文档​​,官网文档提供的demo跟例子都是基于普通公钥模式,按照文档来对接支付宝H5开发会一直提示验签错误,但是相比较与微信支付的文档已经友好太多了

本文档内容如下:

1.支付宝参数说明
2.初始化支付客户端
3.调用支付宝H5支付
4.支付成功回调验签
5.根据商户订单号查询是否支付
6.根据支付宝交易号进行退款

附上官网的一张流程图,我会标记出本文档的6条内容对应流程图的具体那一步

支付宝H5支付---证书模式_官网

必须要的配置

如果你跟我一样,只是个简单的开发,并没有商户支付宝账号,你的领导会给你如下配置,让你去开发支付宝支付功能。有了如下配置,你就可以动工了。如果是从头开发,请阅读​​官方文档​​设置签名,生成一对RAS密钥(应用公钥、应用私钥),获得证书,并开通H5支付。

fuxiao:
ali:
pay:
app-id: 20210011***
format: json
charset: UTF-8
sign-type: RSA2
app-cert-path: \cert\appCertPublicKey_20210***.crt
ali-pay-cert-path: \cert\alipayCertPublicKey_RSA2.crt
ali-pay-root-cert-path: \cert\alipayRootCert.crt
private_key: MIIEvwIBADANBgk******
public_key: MIIBIjANBgkqhkiG9w0BAQE******
server-url: https://openapi.alipay.com/gateway.do
domain: http://puzpa1xe.xiaomy.net:56794/member/ali/payNotifyWithExamination
front-notify-url: https://pre.*****.com/qyf/report?orderId=


1.支付宝参数说明
@Data
@Component
@ConfigurationProperties(prefix = "fuxiao.ali.pay")
public class AliPayBean {
//APPID 即创建小程序后生成。
public String appId;
//开发者私钥,由开发者自己生成。用户消息加签之后,将消息和签名传递给支付宝
public String privateKey;
//开发者公钥,由开发者自己生成。支付成功后,对支付回调进行验签
public String publicKey;
//应用公钥证书文件---证书模式使用
public String appCertPath;
//支付宝公钥证书文件---证书模式使用
public String aliPayCertPath;
//支付宝根证书文件---证书模式使用
public String aliPayRootCertPath;
//支付宝网关(固定)。
public String serverUrl;
/**
* h5支付成功后,后端回调的地址
*/
public String domain;
/**
* 字符编码格式
*/
public String charset;

/**
* 格式
*/
public String format;

/**
* 签名方式
*/
public String signType;

/**
* h5支付成功后,前段回调地址
*/
public String frontNotifyUrl;


}


2.初始化证书模式支付客户端
@Configuration
@Slf4j
public class AliClient {

@Resource
private AliPayBean aliPayBean;

@Bean
public DefaultAlipayClient alipayClient() {
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
//设置appid
certAlipayRequest.setAppId(aliPayBean.getAppId());
// 设置网关地址
certAlipayRequest.setServerUrl(aliPayBean.getServerUrl());
// 设置字符集
certAlipayRequest.setCharset(aliPayBean.getCharset());
// 设置签名类型
certAlipayRequest.setSignType(aliPayBean.getSignType());
// 设置请求格式,默认json
certAlipayRequest.setFormat(aliPayBean.getFormat());
// 设置应用私钥
certAlipayRequest.setPrivateKey(aliPayBean.getPrivateKey());
// 设置应该公钥证书路径 app-cert-path appCertPublicKey_2021001159659046.crt
certAlipayRequest.setCertPath(aliPayBean.getAppCertPath());
// 设置支付宝公钥证书路径 / ali-pay-cert-path alipayCertPublicKey_RSA2.crt
certAlipayRequest.setAlipayPublicCertPath(aliPayBean.getAliPayCertPath());
// 设置支付宝跟证书路径 ali-pay-root-cert-path alipayRootCert.crt
certAlipayRequest.setRootCertPath(aliPayBean.getAliPayRootCertPath());
try {
return new DefaultAlipayClient(certAlipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
log.error("初始化AlipayClient失败," + e);
}
return null;
}
}


3.调用支付宝H5支付,对应流程图的1.1--->1.2--->1.3--->1.4
    @Resource
private DefaultAlipayClient defaultAlipayClient;
/**
* 支付宝H5支付官网例子
* 已改造,官网的例子这块用的是公钥模式调用,如果用官网的例子会一直提示验签错误
* @param response 响应
* @param amount 金额
* @param orderNo 商户订单号
* @param desc 订单描述
*/
private void aliPayH5WithTest(HttpServletResponse response, BigDecimal amount, String orderNo,
String desc) {
try {

AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
// 商户订单号,商户网站订单系统中唯一订单号,必填
String outTradeNo = new String(orderNo.getBytes(aliPayBean.getCharset()));

// 订单名称,必填T
String subject = new String(desc.getBytes(aliPayBean.getCharset()));
// 封装请求支付信息
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
// 商户订单号,商户网站订单系统中唯一订单号,必填
model.setOutTradeNo(outTradeNo);
// 订单名称,必填T
model.setSubject(subject);
// 付款金额,必填

model.setTotalAmount(amount.toString());

//放弃支付的地址
//model.setQuitUrl("https://www.baidu.com/");


// 超时时间 可空
//model.setTimeoutExpress(PayConfig.TIMEOUT_EXPRESS);
// 销售产品码 必填
model.setProductCode("QUICK_WAP_WAY");
alipayRequest.setBizModel(model);
// 设置异步通知地址
alipayRequest.setNotifyUrl(aliPayBean.getSignNotifyUrl());
// 设置同步地址
// alipayRequest.setReturnUrl("http://www.taobao.com/product/113714.html");
// form表单生产
String form;

// 调用SDK生成表单
AlipayTradeWapPayResponse alipayTradeWapPayResponse = defaultAlipayClient.pageExecute(alipayRequest);
String code = alipayTradeWapPayResponse.getCode();
log.info("调用状态:{}", code);
form = alipayTradeWapPayResponse.getBody();
response.setContentType("text/html;charset=" + aliPayBean.getCharset());
log.info("完整相应:{}", form);
//直接将完整的表单html输出到页面
response.getWriter().write(form);
response.getWriter().flush();
response.getWriter().close();
} catch (AlipayApiException | IOException e) {
log.error("aliPayWithH5 error", e);
}
}


4.支付成功回调验签 对应流程图的1.8--->1.9
    /** 支付成功回调验签
* 已改造,官网的例子这块用的是公钥模式验签,如果用官网的例子会一直提示验签错误
* @param request
* @return
*/
@RequestMapping(value = "/payNotifyWithExamination", method = {RequestMethod.POST, RequestMethod.GET})
@ApiOperation("支付宝支付回调")
public String payCallBack(HttpServletRequest request) {

try {
// 获取支付宝POST过来反馈信息
Map<String, String> params = toMap(request);

for (Map.Entry<String, String> entry : params.entrySet()) {
log.info("key是:{},value是:{}", entry.getKey(), entry.getValue());
}
//notify_url 验证
boolean verifyResult = AlipaySignature.rsaCertCheckV1(params, aliPayBean.getAliPayCertPath(),
aliPayBean.getCharset(), "RSA2");

log.info("验签结果:{}", verifyResult);
if (verifyResult) {
//回调触发类型.交易关闭,交易完结,交易成功
if ("TRADE_SUCCESS".equals(params.get("trade_status"))) {
//商户交易号
String orderNo = params.get("out_trade_no");
//支付宝交易号,必须保存,后面退款要用到
String tradeNo = params.get("trade_no");
//支付金额
String amount = params.get("total_amount");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//支付时间
Date dt2 = df.parse(params.get("gmt_payment"));
LocalDateTime payTime = DateUtil.toLocalDateTime(dt2);
//处理业务逻辑,处理业务逻辑的时候一定要先幂等性校验.因为网络存在波动,支付宝回调会有补偿机制

}


return "success";
} else {
log.info("支付宝notify_url验证失败");
return "failure";
}
} catch (Exception e) {
e.printStackTrace();
return "failure";
}
}

public static Map<String, String> toMap(HttpServletRequest request) {
Map<String, String> params = new HashMap<>(24);
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);

String valueStr = "";

for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}

params.put(name, valueStr);
}
return params;
}


5.根据商户订单号查询是否支付 对应流程图的3.1--->3.2
    @Resource
private DefaultAlipayClient defaultAlipayClient;
/** 根据商户订单号查询是是否支付
* @param request
* @return
*/
public void tradeQuery(String orderNo){
try {

AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(orderNo);
alipayRequest.setBizModel(model);

AlipayTradeQueryResponse alipayResponse = defaultAlipayClient.certificateExecute(alipayRequest);
String body = alipayResponse.getBody();
log.info("查询的结果:{}", body);
AlipayTradeQueryResponse alipayTradeQueryResponse = JSONUtil.toBean(body, AlipayTradeQueryResponse.class);
//支付成功不做任何修改
if ("10000".equals(alipayTradeQueryResponse.getCode())
&& "Success".equals(alipayTradeQueryResponse.getMsg())) {
log.info("调用支付宝查询订单已支付,商户号id:{}", orderNo);
return;
}

} catch (AlipayApiException e) {
log.error("查询支付宝是否支付失败:{},商户号id:{}", e, orderNo);
}
}


6.根据支付宝交易号进行退款 对应流程图的4.1--->4.2--->4.3

支付宝交易号:支付成功回调之后里面的有个参数的key是tradeNo,需要保存下来 ,这里退款使用

public void tradeRefund(String tradeNo){
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();

AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setTradeNo(limitOne.getTransactionId());
model.setRefundAmount(limitOne.getAmount().toString());
// model.setOutRequestNo("");
model.setRefundReason("正常退款");
alipayRequest.setBizModel(model);

try {
AlipayTradeRefundResponse alipayResponse = defaultAlipayClient.pageExecute(alipayRequest);
log.info("支付宝退款结果查看:{}",alipayResponse.getBody());
} catch (AlipayApiException e) {
e.printStackTrace();
}
}