支付寶H5支付---證書模式
在文章之前,簡單吐槽一下支付寶的官網文檔,官網文檔提供的demo跟例子都是基於普通公鑰模式,按照文檔來對接支付寶H5開發會一直提示驗簽錯誤,但是相比較與微信支付的文檔已經友好太多了
本文檔內容如下:
1.支付寶參數說明
2.初始化支付客戶端
3.調用支付寶H5支付
4.支付成功回調驗簽
5.根據商戶訂單號查詢是否支付
6.根據支付寶交易號進行退款
附上官網的一張流程圖,我會標記出本文檔的6條內容對應流程圖的具體那一步
必須要的配置
如果你跟我一樣,只是個簡單的開發,並沒有商戶支付寶賬號,你的領導會給你如下配置,讓你去開發支付寶支付功能。有了如下配置,你就可以動工了。如果是從頭開發,請閱讀官方文檔設置簽名,生成一對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();
}
}