一:簡介
手機網站支付的流程圖:
使用者點選H5應用中的支付按鈕點選支付按鈕會請求後臺介面,後臺介面請求支付寶的支付介面,支付介面會返回一段html程式碼其中包括一個form表單和一段js程式碼用於自動提交表單,表單提交後就會自動跳轉到支付寶的支付頁面(如果手機中裝了支付App就去開啟APP,如果沒有就在網頁版支付支付成功後會呼叫支付時設定的同步url, 然後跳轉到商戶的後臺系統,一般情況下商戶系統會展示一下支付成功,以及購買的商品資訊等檢視流程圖
二:整合步驟0. 建立應用、配置金鑰
整合前需要先建立應用、配置金鑰、回撥地址等,具體操作請檢視Spring Boot入門教程(三十五):支付寶整合-準備工作
3.AlipayProperties# 沙箱賬號pay: alipay: gatewayUrl: https://openapi.alipaydev.com/gateway.do appid: 2016091400508498 appPrivateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtXKWs+trRSuCxEUvlsEeSAuLWs3B/Dh74R8223BnfzoA29aGeoycAqfKlBMcbzU2G1KayESvZKGpwBLeejSbecRYjgZsQDyEaDimQQJtGFvZVV6u4XNnvIJ72eQzEaEIQfuiorjBTLm6DQuds4R0GxftqON6QFoIZkWB9ZrZKd02cuy16dW+UqtLVGGAHcCIAkB63zUiKSNfweMddneZ7MVs3lvu3xhMnD+5us/+n2Vp4qhfmpYLcdqIW6InU4GypeoOpyUTrfUGpgdR0l924vHy/GQJZEKFaRcK3cYK+ECyKpSIoqaJJFLHbkqsliuPpMUG+rM3jiqeIAH4psAznAgMBAAECggEBAJ5jyEbbxrJzrAh7GhHX1fwUQPYSadTbrPYAfHX2cHlnrQMJtsk+nTLhEv0r+VJwZ8WpYkfMong8kcqYtL7ajcmsHqMAFhE9EWxBxj2ymWsXLabZe93sj30IG9Rq0nxcGQgDO0RqKWLGSFgK93Al2KRInKT3InkY53K+vR61ihVLmGf7+GwPtIZteBy+tuAyvcj2RlkYvjiFi3ySyZ1wA3sJIlgrGiixd6fj20XFGNE3CnAwu0BJgXXbP/S9J4C0RRa3ZXj8fX7oONhVxz2xKxn7AT4e8OWjt7J41H2LRct8Fgl9pqgz2FJYv/WfbkG8x9fGiKYYvPXoxjjrk/tkewkCgYEA8f9Lcu5JPrE9rpw9zlwhm5cOO81xLxdwL5R5/1bRP48BZGIYuqlCbVvjJVqtO8eTnLhUwH7fG8B7cmoeO9bGr9GQrtfyCqz6FtVymTBieJlfgZDVhtzyv2qKOBMIFE8jsbSBK/NHHMvykJ+XdQ1riwCeQDdXICRuYTTFwGk2OsUCgYEAt2SoN95tVmVrvKG6ATLNEtge/ozeVywA4GjltrSw/G9vqp+DkkT2pY19uROuzMazoTzKWpPho2q/qzNlv/ANbOFM2GEmKamQ7CO88JgRxMsPTvc/HxCLU/ClMJU8LcOf9LfP2KYZpPwuheKJoF4vDGj8NsbFmccJyYSdpkNEk7sCgYBJlL2FMaz1sgC2Ue19DIhvfaunRV1P20mSPgwmNmijccETm7w3LXX0OIdFeV/JGHLqqSWj7i+6iXk/ncKZoUGCfi8G6sQ+uL/GJ5qTt6GJV+ExTS+PtSjeSO/EAw1m13Vb+C16hpstx1l23f+4aJ81gbecgPct38XsKpaiXZtOnQKBgQCMsN7QRYYxwoq9YoDUzIlAzKYyeBVWYL6najHYUZR5hG/xQIBqZRem9/4cTvpJxKInrwA6LrrqaEl0aHDFp75U6h7O3PCvA5PXZK9dD/yJsZIj7U/yX/nTQokn1UUegrYiwiTkusBvrrtuINWePsLvTVc4GpObHnPmsiNTWsWwYwKBgENaeTNOCHV2km/ysXQSEIhKbtlAMQPsgWHCt/bzHlF9m18izb1LrJyjzcSsd+Zy78R+pv4G50Q27c3e/DFPz/wYxN/yHWRbyLBA8ipJbCtMtPEdS9krpmN6cChIdLGbz4CVUqOPSRzNb9lhhgPCcCNRq6DG3HBceb1Se9VnO3zk alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApFQKccMq+wPoWh93DcX3XYrnT7FJ3gntJA/jEwgk6Jd3iEVS2CyUCCgFVcQn8xjXT81YbZHYvoC50IBuu+A+Ey+J8VIgsBm5g9uwbOLRf66GrZjuKOlalHm5gHXjcL2gZRMBJEStOxstcO2YQriqhQzdL3EKp+HQc9u14IOVFpZdR8qq1l7CzKHn1vQo/1fUPfUrTLQqSuQTU9Ospv/QHFqOJA7DPetUQ+jnaZ082f3clr4ITw4EE8A6IWqTXcETTx5j/udCGP84g2Y4j+8i9DqYGyD5ePVgt4G0ICBX1bi1qNlylfxRg8Y3c1DFrRGyr0NpKQxSVXkYaVNvrCoudQIDAQAB returnUrl: http://yxep7y.natappfree.cc/alipay/return notifyUrl: http://yxep7y.natappfree.cc/alipay/notifyspring: thymeleaf: prefix: classpath:/templates/ suffix: .html mode: HTML5 encoding: UTF-8
4.AlipayConfiguration@Data@Slf4j@ConfigurationProperties(prefix = "pay.alipay")public class AlipayProperties { /** 支付寶gatewayUrl */ private String gatewayUrl; /** 商戶應用id */ private String appid; /** RSA私鑰,用於對商戶請求報文加簽 */ private String appPrivateKey; /** 支付寶RSA公鑰,用於驗籤支付寶應答 */ private String alipayPublicKey; /** 簽名型別 */ private String signType = "RSA2"; /** 格式 */ private String formate = "json"; /** 編碼 */ private String charset = "UTF-8"; /** 同步地址 */ private String returnUrl; /** 非同步地址 */ private String notifyUrl; /** 最大查詢次數 */ private static int maxQueryRetry = 5; /** 查詢間隔(毫秒) */ private static long queryDuration = 5000; /** 最大撤銷次數 */ private static int maxCancelRetry = 3; /** 撤銷間隔(毫秒) */ private static long cancelDuration = 3000; private AlipayProperties() {} /** * PostContruct是spring框架的註解,在方法上加該註解會在專案啟動的時候執行該方法,也可以理解為在spring容器初始化的時候執行該方法。 */ @PostConstruct public void init() { log.info(description()); } public String description() { StringBuilder sb = new StringBuilder("\\nConfigs{"); sb.append("支付寶閘道器: ").append(gatewayUrl).append("\\n"); sb.append(", appid: ").append(appid).append("\\n"); sb.append(", 商戶RSA私鑰: ").append(getKeyDescription(appPrivateKey)).append("\\n"); sb.append(", 支付寶RSA公鑰: ").append(getKeyDescription(alipayPublicKey)).append("\\n"); sb.append(", 簽名型別: ").append(signType).append("\\n"); sb.append(", 查詢重試次數: ").append(maxQueryRetry).append("\\n"); sb.append(", 查詢間隔(毫秒): ").append(queryDuration).append("\\n"); sb.append(", 撤銷嘗試次數: ").append(maxCancelRetry).append("\\n"); sb.append(", 撤銷重試間隔(毫秒): ").append(cancelDuration).append("\\n"); sb.append("}"); return sb.toString(); } private String getKeyDescription(String key) { int showLength = 6; if (StringUtils.isNotEmpty(key) && key.length() > showLength) { return new StringBuilder(key.substring(0, showLength)).append("******") .append(key.substring(key.length() - showLength)).toString(); } return null; }}
5. AlipayWAPPayController@Configuration@EnableConfigurationProperties(AlipayProperties.class)public class AlipayConfiguration { @Autowired private AlipayProperties properties; @Bean public AlipayTradeService alipayTradeService() { return new AlipayTradeServiceImpl.ClientBuilder() .setGatewayUrl(properties.getGatewayUrl()) .setAppid(properties.getAppid()) .setPrivateKey(properties.getAppPrivateKey()) .setAlipayPublicKey(properties.getAlipayPublicKey()) .setSignType(properties.getSignType()) .build(); } @Bean public AlipayClient alipayClient(){ return new DefaultAlipayClient(properties.getGatewayUrl(), properties.getAppid(), properties.getAppPrivateKey(), properties.getFormate(), properties.getCharset(), properties.getAlipayPublicKey(), properties.getSignType()); }}
6. AlipayController/** * 支付寶-手機網站支付. * <p> * 手機網站支付 * * @author Mengday Zhang * @version 1.0 * @since 2018/6/11 */@Slf4j@Controller@RequestMapping("/alipay/wap")public class AlipayWAPPayController { @Autowired private AlipayProperties alipayProperties; @Autowired private AlipayClient alipayClient; /** * 去支付 * * 支付寶返回一個form表單,並自動提交,跳轉到支付寶頁面 * * @param response * @throws Exception */ @PostMapping("/alipage") public void gotoPayPage(HttpServletResponse response) throws AlipayApiException, IOException { // 訂單模型 String productCode="QUICK_WAP_WAY"; AlipayTradeWapPayModel model = new AlipayTradeWapPayModel(); model.setOutTradeNo(UUID.randomUUID().toString()); model.setSubject("支付測試"); model.setTotalAmount("0.01"); model.setBody("支付測試,共0.01元"); model.setTimeoutExpress("2m"); model.setProductCode(productCode); AlipayTradeWapPayRequest wapPayRequest =new AlipayTradeWapPayRequest(); wapPayRequest.setReturnUrl("http://yxep7y.natappfree.cc/alipay/wap/returnUrl"); wapPayRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); wapPayRequest.setBizModel(model); // 呼叫SDK生成表單, 並直接將完整的表單html輸出到頁面 String form = alipayClient.pageExecute(wapPayRequest).getBody(); System.out.println(form); response.setContentType("text/html;charset=" + alipayProperties.getCharset()); response.getWriter().write(form); response.getWriter().flush(); response.getWriter().close(); } /** * 支付寶頁面跳轉同步通知頁面 * @param request * @return * @throws UnsupportedEncodingException * @throws AlipayApiException */ @RequestMapping("/returnUrl") public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException, AlipayApiException { response.setContentType("text/html;charset=" + alipayProperties.getCharset()); //獲取支付寶GET過來反饋資訊 Map<String,String> params = new HashMap<>(); 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] + ","; } //亂碼解決,這段程式碼在出現亂碼時使用。如果mysign和sign不相等也可以使用這段程式碼轉化 valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } boolean verifyResult = AlipaySignature.rsaCheckV1(params, alipayProperties.getAlipayPublicKey(), alipayProperties.getCharset(), "RSA2"); if(verifyResult){ //驗證成功 //請在這裡加上商戶的業務邏輯程式程式碼,如儲存支付寶交易號 //商戶訂單號 String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8"); //支付寶交易號 String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8"); return "wapPaySuccess"; }else{ return "wapPayFail"; } } /** * 退款 * @param orderNo 商戶訂單號 * @return */ @PostMapping("/refund") @ResponseBody public String refund(String orderNo) throws AlipayApiException { AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest(); AlipayTradeRefundModel model=new AlipayTradeRefundModel(); // 商戶訂單號 model.setOutTradeNo(orderNo); // 退款金額 model.setRefundAmount("0.01"); // 退款原因 model.setRefundReason("無理由退貨"); // 退款訂單號(同一個訂單可以分多次部分退款,當分多次時必傳)// model.setOutRequestNo(UUID.randomUUID().toString()); alipayRequest.setBizModel(model); AlipayTradeRefundResponse alipayResponse = alipayClient.execute(alipayRequest); System.out.println(alipayResponse.getBody()); return alipayResponse.getBody(); } /** * 退款查詢 * @param orderNo 商戶訂單號 * @param refundOrderNo 請求退款介面時,傳入的退款請求號,如果在退款請求時未傳入,則該值為建立交易時的外部訂單號 * @return * @throws AlipayApiException */ @GetMapping("/refundQuery") @ResponseBody public String refundQuery(String orderNo, String refundOrderNo) throws AlipayApiException { AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest(); AlipayTradeFastpayRefundQueryModel model=new AlipayTradeFastpayRefundQueryModel(); model.setOutTradeNo(orderNo); model.setOutRequestNo(refundOrderNo); alipayRequest.setBizModel(model); AlipayTradeFastpayRefundQueryResponse alipayResponse = alipayClient.execute(alipayRequest); System.out.println(alipayResponse.getBody()); return alipayResponse.getBody(); } /** * 關閉交易 * @param orderNo * @return * @throws AlipayApiException */ @PostMapping("/close") @ResponseBody public String close(String orderNo) throws AlipayApiException { AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest(); AlipayTradeCloseModel model =new AlipayTradeCloseModel(); model.setOutTradeNo(orderNo); alipayRequest.setBizModel(model); AlipayTradeCloseResponse alipayResponse= alipayClient.execute(alipayRequest); System.out.println(alipayResponse.getBody()); return alipayResponse.getBody(); }}
7. WebMvcConfiguration/** * 支付寶通用介面. * <p> * detailed description * * @author Mengday Zhang * @version 1.0 * @since 2018/6/13 */@Slf4j@RestController@RequestMapping("/alipay")public class AlipayController { @Autowired private AlipayProperties aliPayProperties; @Autowired private AlipayTradeService alipayTradeService; /** * 支付非同步通知 * * https://docs.open.alipay.com/194/103296 */ @RequestMapping("/notify") public String notify(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException { // 一定要驗籤,防止黑客篡改引數 Map<String, String[]> parameterMap = request.getParameterMap(); StringBuilder notifyBuild = new StringBuilder("/****************************** alipay notify ******************************/\\n"); parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\\n") ); log.info(notifyBuild.toString()); // https://docs.open.alipay.com/54/106370 // 獲取支付寶POST過來反饋資訊 Map<String,String> params = new HashMap<>(); 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] + ","; } params.put(name, valueStr); } boolean flag = AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType()); if (flag) { /** * TODO 需要嚴格按照如下描述校驗通知資料的正確性 * * 商戶需要驗證該通知資料中的out_trade_no是否為商戶系統中建立的訂單號, * 並判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額), * 同時需要校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email), * * 上述有任何一個驗證不通過,則表明本次通知是異常通知,務必忽略。 * 在上述驗證通過後商戶必須根據支付寶不同型別的業務通知,正確的進行不同的業務處理,並且過濾重複的通知結果資料。 * 在支付寶的業務通知中,只有交易通知狀態為TRADE_SUCCESS或TRADE_FINISHED時,支付寶才會認定為買家付款成功。 */ //交易狀態 String tradeStatus = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8"); // TRADE_FINISHED(表示交易已經成功結束,並不能再對該交易做後續操作); // TRADE_SUCCESS(表示交易已經成功結束,可以對該交易做後續操作,如:分潤、退款等); if(tradeStatus.equals("TRADE_FINISHED")){ //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細, // 並判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額),並執行商戶的業務程式 //請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的 //如果有做過處理,不執行商戶的業務程式 //注意: //如果簽約的是可退款協議,退款日期超過可退款期限後(如三個月可退款),支付寶系統傳送該交易狀態通知 //如果沒有簽約可退款協議,那麼付款完成後,支付寶系統傳送該交易狀態通知。 } else if (tradeStatus.equals("TRADE_SUCCESS")){ //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細, // 並判斷total_amount是否確實為該訂單的實際金額(即商戶訂單建立時的金額),並執行商戶的業務程式 //請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的 //如果有做過處理,不執行商戶的業務程式 //注意: //如果簽約的是可退款協議,那麼付款完成後,支付寶系統傳送該交易狀態通知。 } return "success"; } return "fail"; } /** * 訂單查詢(最主要用於查詢訂單的支付狀態) * @param orderNo 商戶訂單號 * @return */ @GetMapping("/query") public String query(String orderNo){ AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder() .setOutTradeNo(orderNo); AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder); switch (result.getTradeStatus()) { case SUCCESS: log.info("查詢返回該訂單支付成功: )"); AlipayTradeQueryResponse resp = result.getResponse(); log.info(resp.getTradeStatus());// log.info(resp.getFundBillList()); break; case FAILED: log.error("查詢返回該訂單支付失敗!!!"); break; case UNKNOWN: log.error("系統異常,訂單支付狀態未知!!!"); break; default: log.error("不支援的交易狀態,交易返回異常!!!"); break; } return result.getResponse().getBody(); }}
通過訪問http://localhost:8080/toPay來跳轉到toPay.html頁面
8. templates@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { @Override protected void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/toPay").setViewName("toPay"); super.addViewControllers(registry); }}
toPay.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body style="font-size: 30px"><form method="post" action="/alipay/wap/alipage"> <h3>購買商品:越南新娘</h3> <h3>價格:20000</h3> <h3>數量:2個</h3> <button style="width: 100%; height: 60px; alignment: center; background: blue" type="submit">支付</button></form></body></html>
wapPaySuccess.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><h1>WAP 支付成功,請及時享用!歡迎下次再來</h1></body></html>
wapPayFail.html
三:執行結果<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body><h2>WAP 支付失敗,請重新支付</h2></body></html>
首先訪問去支付頁面:http://localhost:8080/toPay
最新評論