對接支付寶流程:
一、步驟分析
❶以post方式進行提交參數請求到支付寶接口進行支付
❷支付寶收到支付後進行通知給你安全、支付回調等問題(兩種通知方式:1.同步通知,2.異步通知)
①同步通知
支付寶同步通知本地瀏覽器進行重定向操作,主要給用戶展示支付寶支付結果是否成功,不會處理訂單狀態
②異步通知
支付寶服務器使用httpclient技術調用你的接口進行通知,支付寶把支付結果以報文方式給你,你進行解析是支付成功還是失敗,根據結果修改訂單狀態,可能由於網絡原因,你沒有響應支付寶,這時候支付寶可能會進行補償機制,解決支付回調冪等使用全局ID
二、圖解DEMO配置步驟
公鑰加密,私鑰解密
-
將下載的alipay.trade.page.pay-JAVA-UTF-8項目導入到eclipse中,可以通過如下鏈接下載
鏈接:https://pan.baidu.com/s/1g-uOZ_HJ3qj2QiWe3-qmqw
提取碼:f3er -
通過如下鏈接進入支付寶沙箱登錄頁面
https://openhome.alipay.com/platform/appDaily.htm
-
進入到如下界面
-
使用支付寶生成公鑰應用代碼生成公鑰和私鑰,可通過如下鏈接下載
鏈接:https://pan.baidu.com/s/1DCCxrzFGll_A7jg3OceVMg
提取碼:ezft -
生成公鑰和私鑰之後,複製公鑰配置到如下處
-
將生成的私鑰粘貼到項目中的如下處
-
在步驟5中的圖片中查看支付寶公鑰並複製
-
將複製的支付寶公鑰粘貼到項目中的如下處
-
複製APPID,並粘貼
-
複製支付寶網關,並粘貼
-
配置下面的兩個參數要使用外網映射工具(如natapp)
在natapp中可以購買一個隧道,然後將下載的客戶端啓動獲取複製它的地址並粘貼
其它參數選擇默認 -
將該項目添加到tomcat中,如下配置
-
在瀏覽器中進行訪問
-
進入測試賬號
-
使用測試賬號進行測試付款
三、將DEMO整合到項目中 -
創建表
CREATE TABLE `payment_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) DEFAULT NULL,
`typeid` int(2) DEFAULT NULL,
`orderid` varchar(50) DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`source` varchar(10) DEFAULT NULL,
`state` int(2) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
`platformorderid` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
- 支付流程
1.向請求支付支付表創建一條支付信息
2.生成支付token,存入redis,key爲支付token,value爲支付表id
3.返回支付token給客戶端
4.使用支付token,向redis查找對應支付表的id
5.使用支付表的id,獲取支付信息
6.封裝支付寶from表單提交參數
- 進行項目整合
❶創建自己的實體類這塊省略不寫
❷在Payapi接口的服務PayService類中創建接口
//創建支付令牌
@RequestMapping("/createPayToken")
public ResponseBase createToken(@RequestBody PaymentInfo paymentInfo);
// 使用支付令牌查找支付信息
@RequestMapping("/findPayToken")
public ResponseBase findPayToken (@RequestParam("payToken") String payToken);
❸創建DAO層
@Select("select * from payment_info where id=#{id}")
public PaymentInfo getPaymentInfo(@Param("id") Long id);
@Insert("insert into payment_info ( id,userid,typeid,orderid,platformorderid,price,source,state,created,updated) value(null,#{userId},#{typeId},#{orderId},#{platformorderId},#{price},#{source},#{state},#{created},#{updated})")
@Options(useGeneratedKeys = true, keyProperty = "id")
public Integer savePaymentType(PaymentInfo paymentInfo);
@Select("select * from payment_info where orderId=#{orderId}")
public PaymentInfo getByOrderIdPayInfo(@Param("orderId") String orderId);
@Update("update payment_info set state =#{state},payMessage=#{payMessage},platformorderId=#{platformorderId},updated=#{updated} where orderId=#{orderId} ")
public void updatePayInfo(PaymentInfo paymentInfo);
❹將上面指的DEMO中的AlipayConfig類複製到實現類的項目中
❺引入如下jar
<dependency>
<groupId>com.github.1991wangliang</groupId>
<artifactId>alipay-sdk</artifactId>
<version>1.0.0</version>
</dependency>
❻書寫實現類
@Autowired
private PaymentInfoDao paymentInfoDao;
/**
* 創建支付令牌
*/
@RequestMapping(value = "/createPayToken", method = RequestMethod.POST)
public ResponseBase createToken(@RequestBody PaymentInfo paymentInfo) {
// 1.創建支付請求信息
Integer savePaymentType = paymentInfoDao.savePaymentType(paymentInfo);
if (savePaymentType <= 0) {
return setResultError("創建支付訂單支付失敗");
}
// 2.生成對應的token
String payToken = TokenUtils.getMemberToken();
// 3.存放在redis中,key爲token,value爲支付id
baseRedisService.setString(payToken, paymentInfo.getId() + "", Constants.PAY_TOKEN_MEMBER_TIME);
// 4.返回token
JSONObject data = new JSONObject();
data.put("payToken", payToken);
return setResultSuccess(data);
}
/**
* 使用支付令牌查找支付信息
*/
@RequestMapping(value="/findPayToken")
public ResponseBase findPayToken(String payToken) {
// 1.參數驗證
if (StringUtils.isEmpty(payToken)) {
return setResultError("token不能爲空");
}
// 2.判斷token有效期
// 3.使用token查找redis,找到對應支付id
String payId = (String) baseRedisService.getString(payToken);
if (StringUtils.isEmpty(payId)) {
return setResultError("支付請求已經超時");
}
// 4.使用支付id,進行下單
Long payID = Long.parseLong(payId);
// 5.使用支付id查詢支付信息
PaymentInfo paymentInfo = paymentInfoDao.getByOrderIdPayInfo(payId);
if (paymentInfo == null) {
return setResultError("未找到支付信息");
}
// 6.對接支付代碼,返回提交支付from表單元素給客戶端
// 獲得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id,
AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key,
AlipayConfig.sign_type);
// 設置請求參數
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
// 商戶訂單號,商戶網站訂單系統中唯一訂單號,必填
String out_trade_no = paymentInfo.getOrderId();
// 付款金額,必填
String total_amount = paymentInfo.getPrice() + "";
// 訂單名稱,必填
String subject = "vm商城充值";
// 商品描述,可空
// String body = new
// String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\"" + total_amount
+ "\"," + "\"subject\":\"" + subject + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
// 若想給BizContent增加其他可選請求參數,以增加自定義超時時間參數timeout_express來舉例說明
// alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no
// +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
// 請求參數可查閱【電腦網站支付的API文檔-alipay.trade.page.pay-請求參數】章節
// 請求
String result;
try {
result = alipayClient.pageExecute(alipayRequest).getBody();
JSONObject data = new JSONObject();
data.put("payHtml", result);
return setResultSuccess(data);
} catch (AlipayApiException e) {
return setResultError("支付異常");
}
}
上面代碼中生成token類和封裝的響應類以及redis類就不展示了
❼使用postman進行測試,首先測試創建支付令牌方法
❽測試使用支付令牌查找支付信息方法
19. 整合到web層
❶將實現類引入到web層服務的pom中
❷寫PayServiceFegin接口
@Component
@FeignClient("pay")
public interface PayServiceFegin extends PayService {
}
❸在web層服務中寫Controller類
@Autowired
private PayServiceFegin payServiceFegin;
/**
* 使用token進行支付
* @throws IOException
*/
@RequestMapping("/aliPay")
public void aliPay(String payToken,HttpServletResponse response) throws IOException{
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
//1.參數驗證
if (StringUtils.isEmpty(payToken)) {
return;
}
//2.調用支付服務接口,獲取支付寶html元素
ResponseBase payTokenResult = payServiceFegin.findPayToken(payToken);
if (!payTokenResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
String msg = payTokenResult.getMsg();
writer.println(msg);
return;
}
//3.返回可以執行的html元素給客戶端
LinkedHashMap data = (LinkedHashMap)payTokenResult.getData();
String payHtml = (String)data.get("payHtml");
log.info("###PayControllerpayHtml{}",payHtml);
//4.頁面上進行渲染
writer.println(payHtml);
writer.close();
}
❹在瀏覽器中進行測試,要傳入token參數,如下界面
20. 同步和異步回調
❶同步回調
①在Payapi接口的服務CallBackService類中創建接口
// 同步通知
@RequestMapping("/synCallBack")
public ResponseBase synCallBack(@RequestParam Map<String, String> params);
// 異步通知
@RequestMapping("/asynCallBack")
public String asynCallBack(@RequestParam Map<String, String> params);
②在實現payapi的服務中寫實現類
/**
* 同步回調
*/
@Override
public ResponseBase synCallBack(Map<String, String> params) {
//1.日誌記錄
log.info("#####支付寶同步通知synCallBack#####開始,params:{}", params);
//2.驗籤操作
try {
//調用SDK驗證簽名
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
log.info("#####支付寶同步通知signVerified:{}######", signVerified);
//——請在這裏編寫您的程序(以下代碼僅作參考)——
if(!signVerified) {
return setResultError("驗籤失敗");
}
//商戶訂單號
String outTradeNo = params.get("out_trade_no");
//支付寶交易號
String tradeNo = params.get("trade_no");
//付款金額
String totalAmount = params.get("total_amount");
JSONObject data = new JSONObject();
data.put("outTradeNo", outTradeNo);
data.put("tradeNo", tradeNo);
data.put("totalAmount", totalAmount);
return setResultSuccess(data);
} catch (Exception e) {
log.error("####支付寶同步通知出現異常,ERROR:", e);
return setResultError("系統錯誤");
}finally {
log.info("#####支付寶同步通知synCallBack#####結束,params:{}", params);
}
/**
* 異步回調
*/
@Override
public String asynCallBack(@RequestParam Map<String, String> params) {
// 1.日誌記錄
log.info("#####支付寶異步通知synCallBack#####開始,params:{}", params);
// 2.驗籤操作
try {
// 調用SDK驗證簽名
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type);
log.info("#####支付寶異步通知signVerified:{}######", signVerified);
// ——請在這裏編寫您的程序(以下代碼僅作參考)——
if (!signVerified) {
return Constants.PAY_FAIL;
}
// 商戶訂單號
String outTradeNo = params.get("out_trade_no");
PaymentInfo paymentInfo = paymentInfoDao.getByOrderIdPayInfo(outTradeNo);
if (paymentInfo == null) {
return Constants.PAY_FAIL;
}
// 支付寶重試機制
Integer state = paymentInfo.getState();
if (state == 1) {
return Constants.PAY_SUCCESS;
}
// 支付寶交易號
String tradeNo = params.get("trade_no");
// 付款金額
// String totalAmount = params.get("total_amount");
// 判斷實際付款金額與商品金額是否一致
// 修改支付寶狀態
paymentInfo.setState(1);
paymentInfo.setPayMessage(params.toString());
paymentInfo.setPlatformorderId(tradeNo);
// 手動begin
Integer updateResult = paymentInfoDao.updatePayInfo(paymentInfo);
if (updateResult <= 0) {
return Constants.PAY_FAIL;
}
// 調用訂單接口通知,支付狀態
ResponseBase orderResult = orderServiceFegin.updateOrderIdInfo(1l, tradeNo, outTradeNo);
if (!orderResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
// 回滾rollback
return Constants.PAY_FAIL;
}
// 2PC 3PC TCC MQ
// 手動 提交 comiit;
return Constants.PAY_SUCCESS;
} catch (Exception e) {
log.error("####支付寶異步通知出現異常,ERROR:", e);
// 回滾 手動回滾 rollback
return Constants.PAY_FAIL;
} finally {
log.info("#####支付寶異步通知synCallBack#####結束,params:{}", params);
}
}
③在web層的服務中寫Controller
@Autowired
private PayCallBackFegin payCallBackFegin;
// 錯誤頁面
private static final String ERROR = "";
//成功返回的頁面,這塊用的是templates模板
private static final String PAY_SUCCESS = "pay_success";
/**
* 使用token進行支付
*
* @throws IOException
*/
@RequestMapping("/aliPay")
public void aliPay(String payToken, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
// 1.參數驗證
if (StringUtils.isEmpty(payToken)) {
return;
}
// 2.調用支付服務接口,獲取支付寶html元素
ResponseBase payTokenResult = payServiceFegin.findPayToken(payToken);
if (!payTokenResult.getCode().equals(Constants.HTTP_RES_CODE_200)) {
String msg = payTokenResult.getMsg();
writer.println(msg);
return;
}
// 3.返回可以執行的html元素給客戶端
LinkedHashMap data = (LinkedHashMap) payTokenResult.getData();
String payHtml = (String) data.get("payHtml");
log.info("###PayControllerpayHtml{}", payHtml);
// 4.頁面上進行渲染
writer.println(payHtml);
writer.close();
}
/**
* 同步回調
* @param request
* @param response
* @throws IOException
*/
@RequestMapping("/callBack/synCallBack")
public void synCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String[]> requestParams = request.getParameterMap();
Map<String, String> params = new HashMap<String, String>();
for (Iterator<String> 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);
}
PrintWriter writer = response.getWriter();
ResponseBase synCallBack = payCallBackFegin.synCallBack(params);
if (!synCallBack.getCode().equals(Constants.HTTP_RES_CODE_200)) {
return;
}
LinkedHashMap data = (LinkedHashMap) synCallBack.getData();
String htmlFrom = "<form name='punchout_form'"
+ " method='post' action='http://127.0.0.1/callBack/synSuccessPage'>"
+ "<input type='hidden' name='outTradeNo' value='" + data.get("out_trade_no") + "'>"
+ "<input type='hidden' name='tradeNo' value='" + data.get("trade_no") + "'>"
+ "<input type='hidden' name='totalAmount' value='" + data.get("total_amount") + "'>"
+ "<input type='submit' value='立即支付' style='display:none'>"
+ "</form><script>document.forms[0].submit();" + "</script>";
writer.println(htmlFrom);
writer.close();
}
/**
* 同步回調,解決隱藏參數
*
* @param request
* @param outTradeNo
* @param tradeNo
* @param totalAmount
* @return
*/
@RequestMapping(value = "/callBack/synSuccessPage", method = RequestMethod.POST)
public String synSuccessPage(HttpServletRequest request, String outTradeNo, String tradeNo, String totalAmount) {
request.setAttribute("outTradeNo", outTradeNo);
request.setAttribute("tradeNo", tradeNo);
request.setAttribute("totalAmount", totalAmount);
return PAY_SUCCESS;
}
④在實現payapi的服務中複製的支付寶的AlipayConfig類改變如下兩個參數
// 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
public static String notify_url = "http://rtwtfv.natappfree.cc/callBack/asynCallBack";
// 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問
public static String return_url = "http://rtwtfv.natappfree.cc/callBack/synCallBack";
⑤成功頁面的templates模板
<h3>您的訂單號爲 ${outTradeNo} ,支付寶F易號${tradeNo} ,支付成功了一筆${totalAmount}元。</h3>
⑥用postman測試
⑦複製上面的token,在瀏覽器中進行測試
http:127.0.0.1/aliPay?payToken=TOKEN_MEMBER-b2439a51-ad44-49b0-81cb-086ee8e51bc3
用測試用戶支付成功後會跳轉到templates的成功模板中
頁面的回調還有寫完,歡迎關注,會持續補全
待更新。。。