我使用springboot+jsp做的簡單測試
官方電腦網站支付文檔https://docs.open.alipay.com/270
首先加入支付寶接口的依賴、配置文件和工具類
<!-- 支付寶支付接口依賴 --> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.9.13.ALL</version> </dependency>
添加請求時要用的參數配置文件alipay.properties
#應用ID,您的APPID,收款賬號既是您的APPID對應支付寶賬號
alipay.app_id=
#商戶私鑰,您的PKCS8格式RSA2私鑰
alipay.merchant_private_key=
#支付寶公鑰
alipay.alipay_public_key=
#服務器異步通知頁面路徑
alipay.notify_url=
#頁面跳轉同步通知頁面路徑
alipay.return_url=
#簽名方式
alipay.sign_type=RSA2
#字符編碼格式
alipay.charset=utf-8
#支付寶網關
alipay.gatewayUrl=https://openapi.alipaydev.com/gateway.do
alipay.log_path=C:\
請到支付寶沙箱中找相關參數填上(異步通知和同步通知可以先不填,后面慢慢講)
開發者中心-控制台-開發服務-研發服務-沙箱
在啟動類加載配置文件
@SpringBootApplication @PropertySource({"classpath:alipay.properties"}) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
添加AlipayConfig類
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import java.io.FileWriter; import java.io.IOException; /** * 類名:AlipayConfig 功能:基礎配置類 詳細:設置帳戶有關信息及返回路徑 */ @Configuration public class AlipayConfig { // 應用ID,您的APPID,收款賬號既是您的APPID對應支付寶賬號 public static String app_id; // 商戶私鑰,您的PKCS8格式RSA2私鑰 public static String merchant_private_key; // 支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。 public static String alipay_public_key; // 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 public static String notify_url; // 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 public static String return_url; // 簽名方式 public static String sign_type; // 字符編碼格式 public static String charset; // 支付寶網關 public static String gatewayUrl; // 支付寶網關 public static String log_path; public String getApp_id() { return app_id; } @Value("${alipay.app_id}") public void setApp_id(String app_id) { AlipayConfig.app_id = app_id; } public String getMerchant_private_key() { return merchant_private_key; } @Value("${alipay.merchant_private_key}") public void setMerchant_private_key(String merchant_private_key) { AlipayConfig.merchant_private_key = merchant_private_key; } public String getAlipay_public_key() { return alipay_public_key; } @Value("${alipay.alipay_public_key}") public void setAlipay_public_key(String alipay_public_key) { AlipayConfig.alipay_public_key = alipay_public_key; } public String getNotify_url() { return notify_url; } @Value("${alipay.notify_url}") public void setNotify_url(String notify_url) { AlipayConfig.notify_url = notify_url; } public String getReturn_url() { return return_url; } @Value("${alipay.return_url}") public void setReturn_url(String return_url) { AlipayConfig.return_url = return_url; } public String getSign_type() { return sign_type; } @Value("${alipay.sign_type}") public void setSign_type(String sign_type) { AlipayConfig.sign_type = sign_type; } public String getCharset() { return charset; } @Value("${alipay.charset}") public void setCharset(String charset) { AlipayConfig.charset = charset; } public String getGatewayUrl() { return gatewayUrl; } @Value("${alipay.gatewayUrl}") public void setGatewayUrl(String gatewayUrl) { this.gatewayUrl = gatewayUrl; } public String getLog_path() { return log_path; } @Value("${alipay.log_path}") public void setLog_path(String log_path) { AlipayConfig.log_path = log_path; } /** * 寫日志,方便測試(看網站需求,也可以改成把記錄存入數據庫) * * @param sWord 要寫入日志里的文本內容 */ public static void logResult(String sWord) { FileWriter writer = null; try { writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt"); writer.write(sWord); } catch (Exception e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
添加AlipayUtil工具類
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.response.AlipayTradePagePayResponse; import com.example.config.AlipayConfig; import javax.servlet.http.HttpServletRequest; import java.text.SimpleDateFormat; import java.util.*; /** * 支付寶接口工具類 * * @author mowen * */ public class AlipayUtil { /** * 發起支付 * * @param amount 金額 * @param subject 主題 * @param notice 內容 * @param passback_params 自定義回調參數 * @return * @throws AlipayApiException * @throws Exception */ public static String sponsorPay(String amount, String subject, String notice, String passback_params) throws AlipayApiException { // 獲得初始化的AlipayClient DefaultAlipayClient 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); // 商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMddHHmmssSSS"); String out_trade_no = simpleDateFormat.format(new Date()); // 付款金額,必填 String total_amount = amount; String body = notice; alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\"" + total_amount + "\"," + "\"subject\":\"" + subject + "\"," + "\"body\":\"" + body + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + "\"passback_params\":\"" + passback_params + "\"}"); // 請求 AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest); String result = ""; if (response.isSuccess()) { result = response.getBody(); } return result; } /** * 支付寶退款接口 * * @param outTradeNo 商戶訂單號 * @param tradeNo 支付寶交易號 * @param refundAmount 退款的金額 * @param refundReason 退款的原因說明 * @param out_request_no 標識一次退款請求,同一筆交易多次退款需要保證唯一,如需部分退款,則此參數必傳 * @return */ public static String aliRefund(String outTradeNo, String tradeNo, String refundAmount, String refundReason, String out_request_no) { // 獲得初始化的AlipayClient AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type); // 設置請求參數 AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest(); alipayRequest.setReturnUrl(AlipayConfig.return_url); alipayRequest.setNotifyUrl(AlipayConfig.notify_url); try { alipayRequest.setBizContent("{\"out_trade_no\":\"" + outTradeNo + "\"," + "\"trade_no\":\"" + tradeNo + "\"," + "\"refund_amount\":\"" + refundAmount + "\"," + "\"refund_reason\":\"" + refundReason + "\"," + "\"out_request_no\":\"" + out_request_no + "\"}"); // 請求 String result; result = alipayClient.execute(alipayRequest).getBody(); return result; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } /** * 支付寶的驗簽方法 * * @param req * @return */ public static boolean checkSign(HttpServletRequest req) { Map<String, String[]> requestMap = req.getParameterMap(); Map<String, String> paramsMap = new HashMap<>(); requestMap.forEach((key, values) -> { String strs = ""; for (String value : values) { strs = strs + value; } System.out.println(("key值為" + key + "value為:" + strs)); paramsMap.put(key, strs); }); // 調用SDK驗證簽名 try { return AlipaySignature.rsaCheckV1(paramsMap, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); } catch (AlipayApiException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("*********************驗簽失敗********************"); return false; } } }
請求時執行步驟:
跳轉到支付頁面
新建index.jsp和pay.jsp
index.jsp:
<%@ page language="java" contentType="text/html; charset=utf-8"%> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post" action="http://127.0.0.1:8080/demo/toPay"> <label>金額</label><input type="text" name="money" > <input type="submit" value="付款"> </form> </body> </html>
pay.jsp:
<%@ page language="java" contentType="text/html; charset=utf-8"%> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div>${requestScope.result}</div> </body> </html>
新建一個controller類
@Controller public class index { @RequestMapping public String index() { return "index"; } @RequestMapping(value = "/toPay",method = RequestMethod.POST) public String toPay(String money, ModelMap map) { try { String result= AlipayUtil.sponsorPay(money,"我買了啥?","不知道啊",null); map.addAttribute("result",result); } catch (AlipayApiException e) { e.printStackTrace(); System.out.println("請求支付寶接口失敗"); } return "pay"; } }
項目開啟后訪問進入index.jsp
輸入框內輸入金額點擊付款就會進入后台控制器的toPay方法,然后請求支付寶接口,把請求得到的body內容放到request作用域,
轉跳到pay.jsp,這個頁面其實就是把得到的內容放到一個div里,然后會自動跳轉到付款頁面,因為內容就是一個會自動提交的from表單,restful模式的話,把它傳給前端,前端找個div放進去就會自動跳轉。
使用沙箱版的支付寶就可付款,上線環境一樣的道理。
同步通知
官方文檔:https://docs.open.alipay.com/59/103665
支付寶對商戶的請求數據處理完成后,會將處理的結果數據直接通知給商戶。這些處理結果數據就是同步通知參數。比如我填:
#頁面跳轉同步通知頁面路徑 alipay.return_url=http://127.0.0.1:8080/demo/toReturnURL
后台控制器添加方法:
@RequestMapping(value = "/toReturnURL") public String toReturnURL() { return "returnURL"; }
添加returnURL.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <span>同步頁面</span> </body> </html>
到達付款頁面付款幾秒后會根據你填的同步通知地址請求,我后端又跳到jsp頁面,也可以直接寫一個htnl頁面的路徑地址,參數有以下這些,也可以用參數驗證完后再跳轉頁面
參數 | 參數名稱 | 類型 | 描述 | 范例 |
out_trade_no | 商戶訂單號 | String(64) | 原支付請求的商戶訂單號 | 6823789339978248 |
total_amount |
訂單金額 | Number(9,2) | 本次交易支付的訂單金額,單位為人民幣(元),精確到小數點后2位 | 20.00 |
sign | 簽名 | String(256) | 601510b7970e52cc63db0f44997cf70e | |
sign_type | 簽名類型 | String(10) | 簽名算法類型,目前支持RSA2和RSA,推薦使用RSA2 |
RSA2 |
trade_no | 支付寶交易號 | String(64) | 支付寶交易憑證號 |
2013112011001004330000121536 |
auth_app_id | 授權方的app_id | String(32) | 授權方的appid,由於本接口暫不開放第三方應用授權,因此auth_app_id=app_id | 2014072300007148 |
app_id | 開發者的app_id | String(32) | 支付寶分配給開發者的應用 ID | 2014072300007148 |
seller_id | 賣家支付寶用戶號 | String(30) | 賣家支付寶用戶號 | 2088101106499364 |
timestamp |
異步通知
當收銀台調用預下單請求 API 生成二維碼展示給用戶后,用戶通過手機掃描二維碼進行支付,支付寶會將該筆訂單的變更信息,沿着商戶調用預下單請求時所傳入的異步通知地址 notify_url,通過 POST 請求的形式將支付結果作為參數通知到商戶系統。
異步回調地址狀態碼(http狀態碼) 為 200 時表示異步通知成功,返回碼為 404 或 500 時則表示服務器內部錯誤,需要商家自行排查。
商家如果因為其他原因沒有收到支付寶服務端返回的異步通知,開發者可以在開發者社區查看自查自查方案:
官方文檔:https://docs.open.alipay.com/194/103296
異步通知和同步一樣的地方在於,付款后帶一些參數返回到你服務器,讓你可以進行一下驗證,
異步通知只有掃碼才會有。
異步通知最重要的是要外網能訪問到你。
所以連接路由器的話,需要用內網穿透,讓外網能訪問到你;
我使用的是http://www.ngrok.cc/
測試的加開個免費的通道即可(關於開啟通道失敗問題,我家是移動寬帶,免費的服務器連接不了,出現連接失敗可以用手機開個網試試,不然直接買vip的)
開啟通道后:官網教程http://www.ngrok.cc/_book/
異步通知地址就可填寫:
#服務器異步通知頁面路徑
alipay.notify_url=http://paydemo.free.idcfengye.com/demo/notifyURL
后台控制器添加方法:
@RequestMapping(value = "/notifyURL",method = RequestMethod.POST) public void notifyURL(HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException { // 獲取支付寶POST過來反饋信息(官方案例) Map<String, String> params = new HashMap<String, String>(); Map<String, String[]> requestParams = request.getParameterMap(); 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); } // 調用SDK驗證簽名 boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); if (signVerified) {// 驗證成功 // 交易狀態 String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8"); if (trade_status.equals("TRADE_FINISHED")) { System.out.println("交易結束,不可退款"); } else if (trade_status.equals("TRADE_SUCCESS")) { System.out.println("交易支付成功"); } } else {// 驗證失敗 } }
用戶付款后會打印:
params集合里有着這次訂單的所有資料,具體有哪些參數請看官方文檔。