本章是跟支付寶進行簽約對接商戶服務端(也就是自行開發的JAVA後端),做此記錄。
文獻基本都來源於支付寶,詳情請看支付寶官方文檔:APP支付
目錄
系統交互圖
先來查看下主要系統交互圖,方便後面的流程梳理。
文檔位置:https://docs.open.alipay.com/204/105297/ 的快速接入,第四步:調用接口
看到這裏,可以清楚的知道,java後端並不需要進行支付操作,只負責生成簽名後的訂單信息和最終驗籤,並最後異步接收支付通知。
服務端demo
下載 服務端mode 依賴包
文檔地址:https://docs.open.alipay.com/54/106370/
我這JAVA MAVEN項目 , 直接引入依賴即可
<dependencies>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.8.10.ALL</version>
</dependency>
</dependencies>
服務端demo 文檔地址:https://docs.open.alipay.com/54/106370/
代碼塊文檔內有展示,這裏就不顯示了,(簡易固定參數不做說明)主要展示咱們需要自助獲取的參數:
- app_id :應用Id
- sign_type :簽名類型,demo採用了(證書)版本,這裏選用推薦的 RSA2,詳情可看:RSA 和 RSA2 簽名算法區別
- private_key :應用私鑰
- app_cert_path :應用公鑰證書路徑
- alipay_cert_path:支付寶公鑰證書路徑
- alipay_root_cert_path:支付寶根證書路徑
配置參數
先登錄到支付寶開放平臺,這裏的實名認證,綁定手機號等操作這裏就不講解了。
獲取APPID
如下圖進行3步進入創建應用界面
創建完成之後,就獲取到咱們要用的應用APPID
獲取公密鑰
阿里文檔鏈接參考:
第一步:生成 RSA 密鑰(這裏選擇公鑰證書方式)
上傳完成後,可在設置頁面下載到 應用公鑰證書,支付寶公鑰證書,支付寶根證書
使用證書的方式,直接查看demo就可以。
使用demo
查看demo其中的一句
//SDK已經封裝掉了公共參數,這裏只需要傳入業務參數。
//以下方法爲sdk的model入參方式(model和biz_content同時存在的情況下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
該業務對象已經幫我們封裝好了,也夠用了,demo展示的set只是其中幾個參數,其他可以繼續賦值。
使用demo提供的AlipayTradeAppPayRequest好處是,自行幫我們做好了sign簽名處理,我們無需再自己簽名。
麻煩的問題在於證書路徑的讀取。如下標識:
//設置應用公鑰證書路徑
certAlipayRequest.setCertPath(app_cert_path);
//設置支付寶公鑰證書路徑
certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path);
//設置支付寶根證書路徑
certAlipayRequest.setRootCertPath(alipay_root_cert_path);
如果項目是war,那還好,這三個參數直接搜索項目根路徑比較簡單,但如果是springboot打包成jar項目的啓動方式的話,就比較困難。jar包如果想讀取內部配置文件,只能通過流來讀取,網上搜索的讀取配置文件大多是InputStream,可我們要的是文件的路徑而不是內容。在開發測試的時候,Linux環境只能讀取絕對路徑,Window可以讀取相對路徑。所以做了兩個版本的路徑切換。下面展示下效果。
// Linux文件絕對路徑 --- ${jar包所在“根”路徑}/crt/
// String outpath = System.getProperty("user.dir") + File.separator + "crt" + File.separator;
// window文件相對路徑
String outpath = this.getClass().getResource("/").getPath()
// 設置應用公鑰證書路徑
// linux getPath: ${jar包所在“根”路徑}/crt/appCertPublicKey_2019102168481752.crt,其他兩個相同
// window getPath: G:\git\leopard-console\target\classes\appCertPublicKey_2019102168481752.crt
File appCertfile = new File(outpath + "appCertPublicKey_2019102168481752.crt");
if (appCertfile == null || appCertfile.getPath() == null) {
logger.error("------/crt/------找不到應用公鑰證書");
return null;
}
// 設置應用公鑰證書路徑
certAlipayRequest.setCertPath(appCertfile.getPath());
本地window測試還方便,如果是linux記得切換路徑,實例裏面有個 “crt“ 文件夾,是我創建後將文件放進去的。
如果是通過dockerfile來構建docker讀取配置。相應配置如下:
FROM java:7
VOLUME /tmp
ADD leopard-console*.jar /leopard-console.jar
ADD classes/alipayCertPublicKey_RSA2.crt /crt/alipayCertPublicKey_RSA2.crt
ADD classes/alipayRootCert.crt /crt/alipayRootCert.crt
ADD classes/appCertPublicKey_2019102168481752.crt /crt/appCertPublicKey_2019102168481752.crt
RUN bash -c 'touch /leopard-console.jar'
EXPOSE 8080
CMD ["java", "-jar","leopard-console.jar"]
將jar編譯的配置文件,創建到 /crt 目錄下,代碼不變,就可以引用到。
自建的 JAR 項目地址:https://github.com/leopardF/alipay , 有需要參考的可自行下載。
該JAR以嵌入依賴爲主,等下測試時候會看到引用。
獲取簽名後的訂單信息
開始測試請求,引入自建項目依賴
<dependency>
<groupId>com.pay</groupId>
<artifactId>alipay</artifactId>
<version>0.0.1</version>
</dependency>
編寫測試代碼:
/**
* 請求生成簽名信息
*
* @return
*/
@RequestMapping(value = "/getOrderInfoSign")
public Message getOrderInfoSign() {
AliAppPayResponseCode appPayRequest = new AliAppPayRequest().appPayRequest(30, "測試-model", "預約3天,詳情如下。。。",
"0.01", "201910221548751212", "paramUrl;paramUrl2", "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
return new Message("200", "aa", appPayRequest.alias());
}
瀏覽器輸入: localhost:8080/getOrderInfoSign , 成功獲取簽名驗證信息。
data就是移動端需要的簽名信息。
我們再重新看下系統流程圖:
第2、3步我們已經完成。
接下來開始我們的驗籤。
驗籤
直接上代碼:
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.alipay.AliAppPayRequest;
import com.alipay.config.AlipayConfig;
import com.alipay.enums.AliAppPayResponseCode;
import com.alipay.enums.AliPayPublicCode;
import com.leopard.util.bean.Message;
import com.leopard.util.enums.SystemCodeAndMsg;
/**
* ali支付
*/
@RestController
@RequestMapping(value = "/alipay")
public class AliPayAction {
private static final Logger logger = LoggerFactory.getLogger(AliPayAction.class);
/**
* 請求生成訂單簽名信息
*
* @param subject
* 標題
* @param body
* 內容
* @param totalAmount
* 金額
* @param passbackParams
* 附加參數
* @return
*/
@RequestMapping(value = "/getModelOrderInfoSign", method = RequestMethod.POST)
public Message getModelOrderInfoSign(@RequestParam(value = "subject") String subject,
@RequestParam(value = "body") String body, @RequestParam(value = "totalAmount") String totalAmount,
@RequestParam(value = "passbackParams") String passbackParams) {
AliAppPayResponseCode appPayRequest = new AliAppPayRequest().appPayRequest(30, subject, body, totalAmount,
"201910221548751212", passbackParams, "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
// AliAppPayResponseCode appPayRequest = new
// AliAppPayRequest().appPayRequest(30, "測試-model", "預約3天,詳情如下。。。",
// "0.01", "201910221548751212", "paramUrl;paramUrl2",
// "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
return new Message("200", "aa", appPayRequest.alias());
}
/**
* 同步獲取前端成功返回結果
*
* @param result
* 支付成功後的result的json串
* @return
*/
@RequestMapping(value = "/syncNotifyValidate", method = RequestMethod.POST)
public Message syncNotifyValidate(@RequestParam(value = "result") String result) {
JSONObject resultObject = JSONObject.parseObject(result);
String alipayTradeAppPayResponse = resultObject.getString("alipay_trade_app_pay_response");
if (StringUtils.isBlank(alipayTradeAppPayResponse)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "響應參數不存在");
}
String sign = resultObject.getString("sign");
if (StringUtils.isBlank(sign)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "驗籤信息不能爲空");
}
String signType = resultObject.getString("sign_type");
if (!AlipayConfig.sign_type.equals(signType)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "簽名類型不匹配");
}
JSONObject responseObject = JSONObject.parseObject(alipayTradeAppPayResponse);
if (!AliPayPublicCode.Success.code().equals(responseObject.getString("code"))) {
logger.info("---syncNotifyValidate-----responseObject:" + responseObject.toString());
return new Message(responseObject.getString("code"), responseObject.getString("msg"));
}
// 以下驗證業務邏輯需補充
Message publicValidateBaseInfo = publicValidateBaseInfo(responseObject);
if(!SystemCodeAndMsg.SUCCESS.code().equals(publicValidateBaseInfo.getCode())){
//驗證失敗
return publicValidateBaseInfo;
}
// 驗證通過,買家付款成功,將信息記錄到數據庫
// 業務代碼
// 反饋給前端
return new Message(SystemCodeAndMsg.SUCCESS.code(), SystemCodeAndMsg.SUCCESS.msg());
}
/**
* 異步獲取支付寶POST通知。 此地址是一開始生成訂單傳入的異步地址,請保留好路徑
*
* @param request
* @return
*/
@RequestMapping(value = "/asynNotifyValidate", method = RequestMethod.POST)
public String asynNotifyValidate(HttpServletRequest request) {
// 支付寶提供的驗籤方法,已經封裝到依賴包,請求調用就行
AliAppPayResponseCode verifyOrderInfoSign = new AliAppPayRequest().verifyOrderInfoSign(request);
if (!AliAppPayResponseCode.SUCCESS.code().equals(verifyOrderInfoSign.code())) {
return "failure";
}
// 以下驗證業務邏輯需補充
// 已經將驗證過程中的訂單信息和響應信息放入到alias內回傳回來
JSONObject verifyOrderInfoJson = JSONObject.parseObject(verifyOrderInfoSign.alias());
Message publicValidateBaseInfo = publicValidateBaseInfo(verifyOrderInfoJson);
if(!SystemCodeAndMsg.SUCCESS.code().equals(publicValidateBaseInfo.getCode())){
//驗證失敗
return "failure";
}
//驗證通過,開始業務代碼,並標記訂單支付成功,該信息可覆蓋同步回調信息,較爲準確
//最好將回調信息都保存在數據庫做記錄
//verifyOrderInfoJson已經保存好回調數據,直接放入數據庫用保存即可
return "success";
}
/**
* 支付寶公用回調信息驗籤
*
* @param jsonObject
* @return
*/
private Message publicValidateBaseInfo(JSONObject jsonObject) {
// 1、商戶需要驗證該通知數據中的 out_trade_no 是否爲商戶系統中創建的訂單號;
String outTradeNo = jsonObject.getString("out_trade_no");
// 業務查找訂單號對應的信息,並進行匹配驗證
// 2、判斷 total_amount 是否確實爲該訂單的實際金額(即商戶訂單創建時的金額);
String totalAmount = jsonObject.getString("total_amount");
// 根據1獲取到的信息,進行匹配驗證
// 3、校驗通知中的 seller_id(或者 seller_email) 是否爲 out_trade_no
// 這筆單據對應的操作方(有的時候,一個商戶可能有多個 seller_id/seller_email);
String sellerId = jsonObject.getString("seller_id");
if (!AliAppPayRequest.verifySellerId(sellerId)) {
logger.info("---publicValidateBaseInfo-----verifySellerId:" + jsonObject.toString());
return new Message(SystemCodeAndMsg.FAIL.code(), "驗證信息有誤");
}
// 4、驗證 app_id
// 是否爲該商戶本身。上述1、2、3、4有任何一個驗證不通過,則表明同步校驗結果是無效的,只有全部驗證通過後,纔可以認定買家付款成功。
String appId = jsonObject.getString("app_id");
if (!AliAppPayRequest.verifyAppId(appId)) {
logger.info("---publicValidateBaseInfo-----verifyAppId:" + jsonObject.toString());
return new Message(SystemCodeAndMsg.FAIL.code(), "驗證信息有誤");
}
return new Message(SystemCodeAndMsg.SUCCESS.code(), SystemCodeAndMsg.SUCCESS.msg());
}
}
這是自己寫的驗籤demo,可直接更改使用到自己的業務內,其中的Message是自己封裝的相應格式,這裏就不羅列了。
驗籤請求可參考支付寶技術文檔:
代碼已經將系統流程圖的,9、10、12、13步完成。
聯調測試,請自行跟移動端聯調對接。
調用支付寶的聯調日誌查詢地址爲:https://openmonitor.alipay.com/acceptance/cloudparse.htm