支付寶對接文檔
一、准備工作
1. 首先要到 螞蟻金服開發者中心 https://openhome.alipay.com/platform/home.htm 注冊商家賬戶,並認證。
2.下載java版的sdk和demo sdk&demo https://docs.open.alipay.com/270/106291/ 下載地址
3.將sdk加入到項目中,在項目根路徑下新建libs文件夾,將jar包復制進去,我使用的是maven,用maven打包並上傳到公司的jar包管理。
4.利用RSARSA簽名驗簽工具生成公鑰、私鑰並保存。生成公鑰放到如圖應用公鑰的位置 https://docs.open.alipay.com/291/105971/ 。詳細操作按照官網教程操作,很簡單的,這里就不上圖片了。
二、開發接口
1、因為開發環境是使用沙箱環境,上線后會使用真實環境,所以支付寶的一些參數我們放到配置文件里 pay-dev.properties appid和支付寶公鑰上面圖片中有,直接在網頁上復制就好。
2、支付寶配置可以選擇配置類也可以選擇配置參數
#支付寶配置 #支付同步返回地址 ali_return_url = #支付異步通知地址 ali_notify_url = #產品碼 product_no = FAST_INSTANT_TRADE_PAY #超時時間 time_express = 15m #支付網關 url = https://openapi.alipaydev.com/gateway.do #商戶號 appid = 2016091500519530 #私鑰 private_key = #公鑰 ali_public_key = #加密方式 sign_type = RSA2
3、開始編寫寫接口
這里支付寶要用的一些參數,我是通過@Value自動注入進來的,官方給的demo是,定義個AlipayConfig類,然后全部定義成靜態變量,根據個人喜好問題選擇,官方的demo中有,可以直接復制,然后修改為你自己的參數即可
注意:並非配置文件中寫全部參數,具體參數設置可以參開官網參數列表
數據庫操作:根據自己的業務需求,我這里是支付提交創建現金表,每次交互要記錄支付過程產生的日志信息,我這里還需要創建交易日志表。
Param:
public class AlipayParam implements Param { @ApiModelProperty(value = "金額", required = true) @NotNull(message = "金額不能為空}") private String amount; @ApiModelProperty(value = "訂單名稱", required = true) @NotNull(message = "訂單名稱 不能為空}") private String orderName; @ApiModelProperty(value = "訂單號", required = true) @NotNull(message = "訂單號 不能為空}") private String orderId; } public class AlipayOrderParam implements Param { private String out_trade_no;//商戶訂單號 private String product_code;//銷售產品碼 private String total_amount;//總金額 private String subject;//訂單標題 private String timeout_express;//該筆訂單允許的最晚付款時間,逾期將關閉交易 private String passback_params;//公共校驗參數 }
Controller:
public class AlipayController { @Resource private AlipayService alipayService; //1.申請付款 @ApiOperation("申請付款") @RequestMapping(value = "/order", method = RequestMethod.POST) public String alipay(@Valid AlipayParam alipayParam, BindingResult result) { return alipayService.alipay(alipayParam); } //2.a1lipay支持同步返回地址 @ApiOperation("同步") @RequestMapping(value = "/getReturnUrlInfo",method = RequestMethod.GET) public String alipayReturnUrlInfo(HttpServletRequest request) { return alipayService.synchronous(request); } //3.alipay異步通知調用地址 @ApiOperation("異步通知") @RequestMapping(value = "/getNotifyUrlInfo",method = RequestMethod.POST) public void alipayNotifyUrlInfo(HttpServletRequest request,HttpServletResponse response){ alipayService.notify(request,response); } }
Service:
/** * 付款 * @param alipayParam 付款參數 * @return 付款返回值 */ String alipay(AlipayParam alipayParam); /** * 付款同步返回地址 * @param request * @return */ String synchronous(HttpServletRequest request); /** * 付款異步通知調用地址 * @param request 新增參數 * @return 新增返回值 */ void notify(HttpServletRequest request,HttpServletResponse response);
ServiceImpl:
public class AlipayServiceImpl implements AlipayService { @Value("${ali_return_url}") private String ali_return_url; @Value("${ali_notify_url}") private String ali_notify_url; @Value("${product_no}") private String product_no; @Value("${time_express}") private String time_express; @Value("${url}") private String url; @Value("${appid}") private String appid; @Value("${private_key}") private String private_key; @Value("${ali_public_key}") private String ali_public_key; @Value("${sign_type}") private String sign_type; @Resource private CashLogMapper cashLogMapper; @Resource private CashMapper cashMapper; @Resource private OrderMapper orderMapper; public static final String TRADE_SUCCESS = "TRADE_SUCCESS"; //支付成功標識 public static final String TRADE_CLOSED = "TRADE_CLOSED";//交易關閉 @Override public String alipay(AlipayParam param) { // 從 session 中獲取用戶 UserData userData = UserContext.getUserData(); if (userData == null || userData.getUserId() == null) { return WebUtils.buildPage("登錄失效, 請重新登錄"); } if (!BusinessTypeConstant.USER_CATEGORY_SELLER.equals(userData.getUserCategory())) { log.info("賣家用戶才可以進行支付"); return WebUtils.buildPage("賣家用戶才可以進行支付"); } // 訂單已支付 Cash cash = cashMapper.getCashByOrderId(param.getOrderId()); if (cash != null && "3".equals(cash.getStatus())) { log.info("訂單已支付"); return WebUtils.buildPage("訂單已支付"); } String merchantOrderNo; Boolean isRetry = false; if (cash != null && new Date(System.currentTimeMillis() - 7800000).before(cash.getCreateTime())) { merchantOrderNo = cash.getMerchantOrderNo(); isRetry = true; } else { merchantOrderNo = param.getOrderId() + new SimpleDateFormat("HHmmss").format(new Date()); } String urlEncodeOrderNum = ""; try { urlEncodeOrderNum = URLEncoder.encode(merchantOrderNo, "UTF-8");//公告參數訂單號 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //向支付寶發送支付請求 AlipayClient alipayClient = new DefaultAlipayClient(url, appid , private_key, "json", "UTF-8" , ali_public_key, sign_type); //創建Alipay支付請求對象 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setReturnUrl(ali_return_url); //同步通知url request.setNotifyUrl(ali_notify_url);//異步通知url AlipayOrderParam alipayOrderParam = new AlipayOrderParam(); alipayOrderParam.setOut_trade_no(merchantOrderNo);//唯一標識 alipayOrderParam.setProduct_code(product_no); alipayOrderParam.setSubject(param.getOrderName()); alipayOrderParam.setTotal_amount(param.getAmount()); alipayOrderParam.setTimeout_express(time_express); alipayOrderParam.setPassback_params(urlEncodeOrderNum); request.setBizContent(JSON.toJSONString(alipayOrderParam));//設置參數 String webForm = "";//輸出頁面的表單 try { webForm = alipayClient.pageExecute(request).getBody(); //調用SDK生成表單 } catch (Exception e) { log.info("支付請求發送失敗"); return WebUtils.buildPage("支付請求發送失敗,請聯系我們客服協助處理"); } // 記錄下發起付款 if (isRetry) { cashLogMapper.add(merchantOrderNo, "RETRY", JSON.toJSONString(alipayOrderParam), new Date()); } else { cashLogMapper.add(merchantOrderNo, "ADD", JSON.toJSONString(alipayOrderParam), new Date()); } // 創建一條支付狀態記錄 cashMapper.add(param.getOrderId(), merchantOrderNo, new Date()); return webForm; } @Override public String synchronous(HttpServletRequest request) { Map<String, String> parameters = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); log.info("支付寶同步參數", requestParams); for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } parameters.put(key, valueStr); } //記錄日志 String merchantOrderNo = request.getParameter("out_trade_no");//商戶訂單號 cashLogMapper.add(request.getParameter("out_trade_no"), "SYNCHRONOUS", JSON.toJSONString(parameters), new Date()); return "<html>\n" + "<head>\n" + "<script type=\"text/javascript\"> function load() { window.close(); } </script>\n" + "</head>\n" + "<body onload=\"" + "load()\"> </body>\n" + "</html>"; } @Override public void notify(HttpServletRequest request, HttpServletResponse response) { //接收參數進行校驗 Map<String, String> parameters = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { String key = entry.getKey(); String[] values = entry.getValue(); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } parameters.put(key, valueStr); } log.info("parameters is [parameters={}]", parameters); String appId = request.getParameter("app_id");//appid String merchantOrderNo = request.getParameter("out_trade_no");//商戶訂單號 String orderId = cashMapper.getCashByMerchantOrderNo(merchantOrderNo).getOrderId(); if (orderId == null) { log.error("orderId is null"); ReturnData.fail(ReturnCode.SERVER_EXCEPTION); } log.info("orderId: {}", orderId); String payState = request.getParameter("trade_status");//交易狀態 String encodeOrderNum = null; cashLogMapper.add(request.getParameter("out_trade_no"), "NOTIFY", JSON.toJSONString(parameters), new Date()); try { encodeOrderNum = URLDecoder.decode(request.getParameter("passback_params"), "UTF-8"); log.info("encodeOrderNum is [encodeOrderNum={}]", encodeOrderNum); boolean signVerified; signVerified = AlipaySignature.rsaCheckV1(parameters, ali_public_key, "UTF-8", sign_type);//驗證簽名 log.info("signVerified is [signVerified={}]", signVerified); if (signVerified) { //通過驗證 log.info("payState: {}", payState); if (payState.equals(TRADE_SUCCESS)) { //判斷訂單號與插入的訂單號是否一樣 if (merchantOrderNo.equals(encodeOrderNum) == false || appid.equals(appId) == false) { log.info("vali failure"); cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } cashMapper.update(merchantOrderNo, 3); orderMapper.afterPay(orderId ); response.getOutputStream().print("success"); return; } else if (payState.equals(TRADE_CLOSED)) { //交易關閉 cashMapper.update(merchantOrderNo, 7); } else { cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } } else { //簽名校驗失敗更狀態 cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } log.info("encodeOrderNum is [encodeOrderNum={}]", encodeOrderNum); cashMapper.update(merchantOrderNo, 4); response.getOutputStream().print("failure"); return; } catch (AlipayApiException e) { log.error(e.getErrMsg()); throw new RuntimeException("調用支付寶接口發生異常"); } catch (UnsupportedEncodingException e) { log.error(e.getMessage()); throw new RuntimeException("URLDecoderf發生異常"); } catch (IOException e) { log.error(e.getMessage()); throw new RuntimeException("IO發生異常"); } } }
總結:與支付寶對接,本地生成訂單進行提交到支付寶同步處理完支付,支付寶會通過異步來獲取用戶給支付寶反饋的信息會最終確定是否支付成功,在開發過程中注意支付確認(異步)的邏輯處理即可,具體返回參數可以參開
4、進行沙箱環境的測試
訪問接口地址 項目地址/alipay/pay 我的是 127.0.0.1/alipay/pay/order
如果后台沒有報錯的話,他會自動重定向到,支付寶的付款頁面,如下圖所示。這時候我們下載安裝沙箱版的app,然后使用官方提供的賬戶掃描然后直接付款,付款成功后會回調后面那兩個接口,在通知的那個接口里處理你的業務邏輯。查看沙箱app的登錄帳戶名和密碼.
總結:沙箱環境每個人都可以開通支付賬戶並可以查看相應的買家和商家的賬號,商家用戶需要公司走相關流程大致一周多的時間。
