上一篇介紹了最常用的普通公鑰方式整合app支付寶支付,此篇主要介紹公鑰證書方式,實際項目中因爲業務的特殊性兩種支付方式統一用同一套接口,根據不同參數對差異部分進行區分,這裏爲更清楚的介紹整合方法便提煉出另一套接口,大同小異,可以根據實際情況處理:
支付寶支付(1)之普通公鑰方式
一、簽名
參考鏈接:https://docs.open.alipay.com/291/105971/
跟上一篇不同的是這次選擇公鑰證書方式:
按照文檔一步步就可以生成公鑰證書及私鑰等相關文件
需要將這三個文件上傳到服務器,使用中都是使用的文件根路徑
依賴跟工具類等其他文件跟普通公鑰一樣,這裏無需再重複,直接上重點
二、生成APP支付訂單信息
//構造client
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
//設置網關地址
certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do");
//設置應用Id
certAlipayRequest.setAppId(app_id);
//設置應用私鑰
certAlipayRequest.setPrivateKey(privateKey);
//設置請求格式,固定值json
certAlipayRequest.setFormat("json");
//設置字符集
certAlipayRequest.setCharset(charset);
//設置簽名類型
certAlipayRequest.setSignType(sign_type);
//設置應用公鑰證書路徑
certAlipayRequest.setCertPath(app_cert_path);
//設置支付寶公鑰證書路徑
certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path);
//設置支付寶根證書路徑
certAlipayRequest.setRootCertPath(alipay_root_cert_path);
//構造client
AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest);
//實例化具體API對應的request類,類名稱和接口名稱對應,當前調用接口名稱:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已經封裝掉了公共參數,這裏只需要傳入業務參數。以下方法爲sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("我是測試數據");
model.setSubject("App支付測試Java");
model.setOutTradeNo(outtradeno);
model.setTimeoutExpress("30m");
model.setTotalAmount("0.01");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("商戶外網可以訪問的異步地址");
try {
//這裏和普通的接口調用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
System.out.println(response.getBody());//就是orderString 可以直接給客戶端請求,無需再做處理。
} catch (AlipayApiException e) {
e.printStackTrace();
}
調用此邏輯,返回orderString 字符串,此字符串直接發給客戶端調起支付寶支付即可,無需做其他處理。
其中需要配置回調地址,就是下面要說的處理支付寶回調的接口地址。
三、處理支付寶異步回調
//獲取支付寶POST過來反饋信息
Map<String,String> params = new HashMap<String,String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
//切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
//boolean AlipaySignature.rsaCertCheckV1(Map<String, String> params, String publicKeyCertPath, String charset,String signType)
boolean flag = AlipaySignature.rsaCertCheckV1(params, publicKeyCertPath, charset,"RSA2")
四、具體項目代碼
(代碼使用了springboot工程,用到lombok等,這些都可以根據實際情況自己選擇使用,這都不是重點)
這裏附上整合支付寶app支付的整體後臺服務代碼,相關參數跟業務邏輯需要根據自己業務場景跟需求進行調整或者補充,這裏只做與支付寶對接的部分:
項目整體結構
整體結構與上一篇大致相同,其中加了配置類跟證書文件
1、ZrkAliPayCertConfig.java
package com.zrk.alipay;
/**
* @Description: 支付寶支付配置類(公鑰證書)
* @Author: zrk
* @Date: 2019/9/10
*/
public class ZrkAliPayCertConfig {
/**
* 商戶私鑰
*/
public static String APP_PRIVATE_KEY = "***";
/**
* 支付寶APPID
*/
public static String APPID = "***";
/**
* 應用公鑰證書路徑
*/
public static String app_cert_path = "CRT/appCertPublicKey.crt";
/**
* 支付寶公鑰證書文件路徑
*/
public static String alipay_cert_path = "CRT/alipayCertPublicKey_RSA2.crt";
/**
* 支付寶CA根證書文件路徑
*/
public static String alipay_root_cert_path = "CRT/alipayRootCert.crt";
/**
* 請求網關
*/
public static String SERVERURL = "https://openapi.alipay.com/gateway.do";
/**
* 回調地址
*/
public static String ALIPAY_NOTIFY_URL = "http://zrk.com/thirdPay/alipayCertNotify";
/**
* 字符集
*/
public static String CHARSET = "utf-8";
/**
* 簽名類型
*/
public static String SIGN_TYPE = "RSA2";
/**
* 格式
*/
public static String FORMAT = "json";
}
注:其中三個路徑是文件名,使用時需要拼接上根路徑
2、ThirdPayController.java
package com.zrk.controller;
import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;
import com.zrk.service.ThirdPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @Description: 第三方支付接口
* @Author: zrk
* @Date: 2019/9/10
*/
@Slf4j
@RestController
@RequestMapping("thirdPay")
public class ThirdPayController {
@Resource
private ThirdPayService thirdPayService;
/**
* 支付寶下訂單接口
* 參數可根據自己的業務需求傳相應參數
* @param request
* @return
*/
@GetMapping(value = "aliPayUnifiedOrder")
public ResultStatus aliPayUnifiedOrder(PayRequest request){
return thirdPayService.aliPayUnifiedOrder(request);
}
/**
* 支付寶回調接口
* @param request
* @param response
* @return
* @throws Exception
*/
@RequestMapping(value = "aliPayNotify")
@ResponseBody
public String aliPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
//獲取支付寶POST過來反饋信息
Map requestParams = request.getParameterMap();
return thirdPayService.aliPayNotify(requestParams);
}
/**
* 支付寶下訂單接口(公鑰證書方式)
* 參數可根據自己的業務需求傳相應參數
* @param request
* @return
*/
@GetMapping(value = "aliPayCertUnifiedOrder")
public ResultStatus aliPayCertUnifiedOrder(PayRequest request){
return thirdPayService.aliPayCertUnifiedOrder(request);
}
/**
* 支付寶回調接口(公鑰證書方式)
* @param request
* @param response
* @return
* @throws Exception
*/
@RequestMapping(value = "aliPayCertNotify")
@ResponseBody
public String aliPayCertNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
//獲取支付寶POST過來反饋信息
Map requestParams = request.getParameterMap();
return thirdPayService.aliPayCertNotify(requestParams);
}
}
3、service
ThirdPayService.java
package com.zrk.service;
import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;
import java.util.Map;
/**
* @Description:
* @Author: zrk
* @Date: 2019/9/10
*/
public interface ThirdPayService {
/**
* 支付寶支付統一下單接口
* @param request
* @return
*/
ResultStatus aliPayUnifiedOrder(PayRequest request);
/**
* 支付寶回調
* @param requestParams
* @return
*/
String aliPayNotify(Map requestParams);
/**
* 支付寶支付統一下單接口(公鑰證書方式)
* @param request
* @return
*/
ResultStatus aliPayCertUnifiedOrder(PayRequest request);
/**
* 支付寶回調(公鑰證書方式)
* @param requestParams
* @return
*/
String aliPayCertNotify(Map requestParams);
}
ThirdPayServiceImpl.java
package com.zrk.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.zrk.alipay.ZrkAliPayCertConfig;
import com.zrk.alipay.ZrkAliPayConfig;
import com.zrk.alipay.ZrkAliPayUtil;
import com.zrk.enums.ResultStatusEnum;
import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;
import com.zrk.service.ThirdPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @Description:
* @Author: zrk
* @Date: 2019/9/10
*/
@Slf4j
@Service
public class ThirdPayServiceImpl implements ThirdPayService {
/**
* 文件跟路徑
*/
@Value("${alipay_cert_file_path}")
private String AliPay_CERT_FILE_PATH;
/**
* 分隔符
*/
private final static String attachRegex = "ZRKAPP";
@Override
public ResultStatus aliPayUnifiedOrder(PayRequest payRequest) {
/**
* 定義變量,可以根據實際需求獲取並生成相應變量,變量值僅供參考
*/
String body = "商品名稱";
String outTradeNo = ZrkAliPayUtil.getOutTradeNo();
Double totalFee = 0.01;
/**
* 拼接自己的業務參數
* 例如 用戶id + 商品id + 訂單id
* 用指定分隔符進行拼接,以便後續做業務處理
*/
String passBackParams = "123" + attachRegex + "111" + attachRegex + "222";
/**
* 以下部分可以共用,複製即可
* *********************************************************
*/
//實例化具體API對應的request類,類名稱和接口名稱對應,當前調用接口名稱:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已經封裝掉了公共參數,這裏只需要傳入業務參數。以下方法爲sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody(body);
model.setSubject(body);
model.setOutTradeNo(outTradeNo);
model.setTimeoutExpress("30m");
model.setTotalAmount(totalFee + "");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setPassbackParams(URLEncoder.encode(passBackParams));
request.setBizModel(model);
request.setNotifyUrl(ZrkAliPayConfig.ALIPAY_NOTIFY_URL);
log.info(">>>>支付寶統一下單接口請求參數:" + model.getBody() + "," + model.getOutTradeNo() + "," + model.getTotalAmount());
/**實例化客戶端*/
AlipayClient alipayClient = new DefaultAlipayClient(
ZrkAliPayConfig.SERVERURL,
ZrkAliPayConfig.APPID,
ZrkAliPayConfig.APP_PRIVATE_KEY,
ZrkAliPayConfig.FORMAT,
ZrkAliPayConfig.CHARSET,
ZrkAliPayConfig.ALIPAY_PUBLIC_KEY,
ZrkAliPayConfig.SIGN_TYPE);
try {
//這裏和普通的接口調用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
//就是orderString 可以直接給客戶端請求,無需再做處理。
log.info(">>>生成調用支付寶參數" + response.getBody());
/**************************************************************/
ResultStatus resultStatus = new ResultStatus(ResultStatusEnum.SUCCESS.getStatus());
resultStatus.setData(response.getBody());
return resultStatus;
} catch (AlipayApiException e) {
log.error(e.getMessage(), e);
}
return new ResultStatus(ResultStatusEnum.ERROR.getStatus(),"支付寶下訂單失敗");
}
@Override
public String aliPayNotify(Map requestParams) {
log.info(">>>支付寶回調參數:" + requestParams);
Map<String,String> params = new HashMap<>();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
log.info(">>>支付寶回調參數解析:" + params);
try {
//切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
boolean flag = AlipaySignature.rsaCheckV1(
params,
ZrkAliPayConfig.ALIPAY_PUBLIC_KEY,
ZrkAliPayConfig.CHARSET, ZrkAliPayConfig.SIGN_TYPE);
if(flag) {
log.info(">>>支付寶回調簽名認證成功");
//商戶訂單號
String out_trade_no = params.get("out_trade_no");
//交易狀態
String trade_status = params.get("trade_status");
//交易金額
String amount = params.get("total_amount");
//商戶app_id
String app_id = params.get("app_id");
if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
/**
* 自己的業務處理
*/
} else {
log.error("沒有處理支付寶回調業務,支付寶交易狀態:{},params:{}",trade_status,params);
}
} else {
log.info("支付寶回調簽名認證失敗,signVerified=false, params:{}", params);
return "failure";
}
} catch (Exception e){
log.error(e.getMessage(), e);
log.info("支付寶回調簽名認證失敗,signVerified=false, params:{}", params);
return "failure";
}
return "success";//請不要修改或刪除
}
@Override
public ResultStatus aliPayCertUnifiedOrder(PayRequest payRequest) {
/**
* 定義變量,可以根據實際需求獲取並生成相應變量,變量值僅供參考
*/
String body = "商品名稱";
String outTradeNo = ZrkAliPayUtil.getOutTradeNo();
Double totalFee = 0.01;
/**
* 拼接自己的業務參數
* 例如 用戶id + 商品id + 訂單id
* 用指定分隔符進行拼接,以便後續做業務處理
*/
String passBackParams = "123" + attachRegex + "111" + attachRegex + "222";
/**
* 以下部分可以共用,複製即可
* *********************************************************
*/
//實例化具體API對應的request類,類名稱和接口名稱對應,當前調用接口名稱:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已經封裝掉了公共參數,這裏只需要傳入業務參數。以下方法爲sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody(body);
model.setSubject(body);
model.setOutTradeNo(outTradeNo);
model.setTimeoutExpress("30m");
model.setTotalAmount(totalFee + "");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setPassbackParams(URLEncoder.encode(passBackParams));
request.setBizModel(model);
request.setNotifyUrl(ZrkAliPayConfig.ALIPAY_NOTIFY_URL);
log.info(">>>>支付寶統一下單接口請求參數:" + model.getBody() + "," + model.getOutTradeNo() + "," + model.getTotalAmount());
/**實例化客戶端*/
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
certAlipayRequest.setServerUrl(ZrkAliPayCertConfig.SERVERURL);
certAlipayRequest.setAppId(ZrkAliPayCertConfig.APPID);
certAlipayRequest.setPrivateKey(ZrkAliPayCertConfig.APP_PRIVATE_KEY);
certAlipayRequest.setFormat("json");
certAlipayRequest.setCharset(ZrkAliPayCertConfig.CHARSET);
certAlipayRequest.setSignType(ZrkAliPayCertConfig.SIGN_TYPE);
certAlipayRequest.setCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.app_cert_path);
certAlipayRequest.setAlipayPublicCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.alipay_cert_path);
certAlipayRequest.setRootCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.alipay_root_cert_path);
try {
//構造client
AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest);
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
//就是orderString 可以直接給客戶端請求,無需再做處理。
log.info(">>>生成調用支付寶參數" + response.getBody());
/**************************************************************/
ResultStatus resultStatus = new ResultStatus(ResultStatusEnum.SUCCESS.getStatus());
resultStatus.setData(response.getBody());
return resultStatus;
} catch (AlipayApiException e) {
log.error(e.getMessage(), e);
}
return new ResultStatus(ResultStatusEnum.ERROR.getStatus(),"支付寶下訂單失敗");
}
@Override
public String aliPayCertNotify(Map requestParams) {
log.info(">>>支付寶回調參數:" + requestParams);
Map<String,String> params = new HashMap<>();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
//亂碼解決,這段代碼在出現亂碼時使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
log.info(">>>支付寶回調參數解析:" + params);
try {
//切記alipaypublickey是支付寶的公鑰,請去open.alipay.com對應應用下查看。
boolean flag = AlipaySignature.rsaCertCheckV1(
params,
AliPay_CERT_FILE_PATH+ZrkAliPayCertConfig.alipay_cert_path,
ZrkAliPayCertConfig.CHARSET,
ZrkAliPayCertConfig.SIGN_TYPE);
if(flag) {
log.info(">>>支付寶回調簽名認證成功");
//商戶訂單號
String out_trade_no = params.get("out_trade_no");
//交易狀態
String trade_status = params.get("trade_status");
//交易金額
String amount = params.get("total_amount");
//商戶app_id
String app_id = params.get("app_id");
if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
/**
* 自己的業務處理
*/
} else {
log.error("沒有處理支付寶回調業務,支付寶交易狀態:{},params:{}",trade_status,params);
}
} else {
log.info("支付寶回調簽名認證失敗,signVerified=false, params:{}", params);
return "failure";
}
} catch (Exception e){
log.error(e.getMessage(), e);
log.info("支付寶回調簽名認證失敗,signVerified=false, params:{}", params);
return "failure";
}
return "success";//請不要修改或刪除
}
}
4、application-dev.properties
##文件跟路徑
#本地
alipay_cert_file_path=D:/microservice/pay-service/src/main/resources/static/
#測試
#alipay_cert_file_path=/home/zrk/microservice/pay-service/src/main/resources/static/
五、測試
使用postman訪問 localhost:30040/thirdPay/aliPayCertUnifiedOrder,
返回結果:
其中data內容就是給客戶端調起支付寶支付用的,出於保密對參數進行了部分處理
參考鏈接:
1、App支付服務端 DEMO & SDK :https://docs.open.alipay.com/54/106370/
2、簽名文檔:https://docs.open.alipay.com/291/105974/
3、參數說明:https://docs.open.alipay.com/204/105301/