因為項目需要對接支付寶,進行掃描支付,同時生成訂單進入系統(tǒng)處理具體的業(yè)務(wù)邏輯,所以就記錄一下開發(fā)過程。
一、配置沙箱環(huán)境
1、入駐支付寶開發(fā)平臺
訪問https://open.alipay.com,入駐支付寶開發(fā)平臺。

點擊立即入駐

信息填寫完成后,即可進入開發(fā)平臺。
2、進入沙箱環(huán)境
進入支付寶開發(fā)平臺>控制臺>開發(fā)服務(wù),點擊研發(fā)服務(wù)即可進入沙箱環(huán)境。
二、設(shè)置RSA2(SHA256)密鑰
注:本次開發(fā)主要是應(yīng)用了普通密鑰方式。


參考文檔:https://opendocs.alipay.com/open/291/105971
1、基本說明
支付寶開放平臺開發(fā)助手提供了一鍵生成密鑰功能,便于開發(fā)者生成一對 RSA 密鑰(應(yīng)用公鑰、應(yīng)用私鑰)以及公鑰證書申請 CSR 文件(在線申請應(yīng)用公鑰證書需要)。加密過程:使用公鑰(public key)為系統(tǒng)進行加密,并將密文發(fā)送給解密者,解密者使用私鑰(private key)解密將密文解碼為明文。公鑰證書模式中上傳的文件,無論是 CSR 文件或者開發(fā)者自己申請的公鑰證書文件,必須和用戶本地代碼中加密的應(yīng)用私鑰是匹配的,否則會導(dǎo)致支付寶開放平臺驗簽失敗。
支付寶開放平臺支持使用普通公鑰、公鑰證書兩種簽名方式。下面分別向您介紹兩種方式的工具操作步驟,包括如何使用密鑰生成工具生成應(yīng)用公鑰(public key)、應(yīng)用私鑰(private key)和公鑰證書申請 CSR 文件。
說明:
(1)應(yīng)用公鑰(public key)需提供給支付寶賬號管理者上傳到支付寶開放平臺。
(2)應(yīng)用私鑰(private key)由開發(fā)者自己保存,需填寫到代碼中供簽名時使用。
(3)生成的私鑰需妥善保管,避免遺失,不要泄露。
(4)密鑰和應(yīng)用(APPID)一一對應(yīng),即開發(fā)者需要為名下的每個應(yīng)用分別設(shè)置密鑰,且不同應(yīng)用的密鑰不能混用。
工具下載:
WINDOWS(windows 版本工具請不要安裝在含有空格的目錄路徑下,否則會導(dǎo)致公私鑰亂碼的問題)
MAC_OSX
普通公鑰與公鑰證書區(qū)別:
(1)企業(yè)開發(fā)者若涉及資金類支出接口接入,必須使用公鑰證書模式。
(2)個人開發(fā)者不涉及到資金類接口,建議使用公鑰方式進行加簽。
(3)在報文簽名場景下,報文接受方使用發(fā)送方的公鑰進行報文驗簽,該功能兩種簽名方式都可以實現(xiàn)。
(4)公鑰證書簽名方式引入了 CA 機構(gòu)對公鑰持有者進行身份識別,保證該證書所屬實體的真實性,以實現(xiàn)報文的抗抵賴。
(5)公鑰證書簽名方式下,開放平臺支持通過上傳 CSR 文件的方式給開發(fā)者在線簽發(fā)應(yīng)用公鑰證書,新的開放平臺 RSA 驗簽和簽名工具支持生成 CSR 文件。
前提條件:
(1)已完成開發(fā)者入駐以及實名認證。詳情請參見個人支付寶賬號實名認證指南、支付寶賬號實名認證指南。
(2)已下載密鑰生成工具(支付寶開放平臺開發(fā)助手)。詳情請參見簡介。
2、普通公鑰方式
下載相應(yīng)環(huán)境工具并安裝后即可使用,本步驟指引以 WINDOWS界面為例。

(1)根據(jù)開發(fā)語言選擇密鑰格式和密鑰長度。
說明:
①新建應(yīng)用請務(wù)必使用 RSA2 密鑰長度 即 2048 位。詳情請參見開放平臺證書升級指南。
②目前已使用 RSA 密鑰長度即 1024 位密鑰長度的應(yīng)用仍然可以正常調(diào)用接口。
③接口中的 sign_type 參數(shù)應(yīng)與上傳密鑰的加簽方式一致。例如接口參數(shù)中 sign_type=RSA2,請求時就會使用此處設(shè)置的 RSA2(SHA256) 公鑰驗簽。
(2)點擊 生成密鑰 后,工具會自動生成商戶應(yīng)用公鑰(public key)和應(yīng)用私鑰(private key)。

(3)點擊工具界面下方的打開文件位置,在支付寶開放平臺開發(fā)助手文件夾下選擇RSA 密鑰,即可找到生成的公私鑰文件。

(4)復(fù)制上一步生成的公鑰,點擊保存設(shè)置,即可完成公鑰的設(shè)置。

3、公鑰證書方式
同普通公鑰方式一樣,下載相應(yīng)環(huán)境工具并安裝后即可使用,本步驟指引以 MAC_OSX 界面為例:
(1)點擊獲取CSR文件后的點擊獲取,生成應(yīng)用公鑰證書 CSR 申請文件。
說明:
① 密鑰長度選擇 RSA2
② 密鑰格式選擇 PKCS8(Java適用)

(2)在彈出的獲取CSR對話框中根據(jù)提示填寫相關(guān)信息,點擊生成 CSR 文件。
說明:
①組織/公司名稱一定要和開發(fā)者中心門戶賬號信息的公司名稱保持一致,否則會導(dǎo)致后續(xù)步驟中上傳 CSR 證書文件校驗失敗。
②沙箱環(huán)境下組織/公司名稱應(yīng)填寫為沙箱環(huán)境。

(3)在生成 CSR 文件后,點擊打開密鑰文件路徑,在對應(yīng)的文件夾里可以看到三個文件:應(yīng)用公鑰 key 串、應(yīng)用私鑰 key 串,以及 csr 格式的應(yīng)用公鑰證書文件。

(4)進入支付寶開放平臺并打開對應(yīng)的應(yīng)用,在應(yīng)用的開發(fā)配置頁面進行接口加簽方式設(shè)置。點擊設(shè)置后,輸入手機驗證碼。

(5)加簽?zāi)J竭x擇公鑰證書 ,上傳證書文件選擇上傳 CSR 文件在線生成證書或者上傳已申請證書,即可完成公鑰證書的設(shè)置。上傳證書文件。即可完成公鑰證書的設(shè)置。
①選擇上傳 CSR 文件在線生成證書并點擊 上傳 CSR 文件在線生成證書。

②選擇上傳已申請證書,點擊,選擇上一步驟生成的 .csr 文件上傳。上傳完成證書后,系統(tǒng)會自動識別證書的加密方式。證書必須由權(quán)威 CA 簽發(fā),詳情請參見 當(dāng)前支持的CA列表,且僅支持 X.509 格式的證書,詳情請參見 證書說明。

三、項目集成支付寶支付接口
1、集成并配置SDK
參考文檔:https://opendocs.alipay.com/open/54/103419

2、支付寶支付參數(shù)配置
public class AlipayConfig {
/**
* APPID即創(chuàng)建應(yīng)用后生成
*/
public static String APPID = "2021000118641985";
/**
* 開發(fā)者私鑰,由開發(fā)者自己生成
*/
public static String APP_PRIVATE_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXOnhEM7n606PpsE25ItCMCyLVIL4b6dYLzMl2rT7NT9n62xSnlQDjbQ22pHNBEgRqFXqT1TeBqi61MGBjCwC9Uyz4E4CcTyjhoToeWlrwXSc/GJ4Goww5rZKdnaqtf2fspdGDwHxib6D5bZBPOOMenMCdA+vRlaZyKxDwVRsUdFTLuZT1eLaXXOTA6f6LkZvXntIC9oHNwgpWiMzQwdnfJij18IGYhLMUjlQKJUIeGEanf1R6vzgPZZlmmHrVhDrx0LD9JJYYNlN0a+NFgrZfuLv0jiJoMtCsqOFFOJHBp3BtNJQt200yp8idefZu7IPaSLYmxy2yPh6s2be4455dAgMBAAECggEAXkt28g/Oxzdv3SaxT98Fak0HSx0/bOhBLtpiRD2CC0LfCCvSlSuzghtdkaS4uLojRoJeDA/GrHMQ7KldcGRL8cELKSP/7XbuZsHBG2v7iCSNdCpFXp4L4Wr5II5O/h+TDVvXZ+99n2M7XEuUz9EIzO2wrDbls6k8P7PavABVAksQiwiPftUG507xAUUyjAZparqKJF232DiprwFePqeJnp1vh3EmE+YA4f8W4mz6GiJpgtPV8ZGeqjCV7X3gauU9hiqPbiW+rR+VEeShiZA1G/O1XbweY5BF0v4xgUwbQaRSxXHeUvpjHaEvCsKqeeKVFDGMZzybjTgCOjE6cnFlgQKBgQDY4ChitPuSSiJISoqcJxoKx4PxWuQw5+q+r3k29IPSu1ApDaeOP/+GhDREwQV0so5/kxBbeuZGXobtdp66UYEWBASOk6cRmc4dy0r70NRQs28t2Qxo8sRRBQttPynRwolOfhyuwm8s6H1aQwLBR9VkW3Jdi7gygsSmUUc6nbsk1wKBgQCygpBtvsGqo7HwW5It9kC0PPxGgIedVwDH5VZ3/Xvr1mkOT4XPB3RnpwFL0WtmvCYfDaJWlcBilOixw1jjV6xJvm+VIGL5poMPD5uroHkUm2SCkkacMS4Wyd7HOMqcoLL0KmdCNPCy4hhPNKqYaClFpHvuM49DsahIiCe1nZb76wKBgFxJhuX5/dOSmGQK1FD+kqZjoFHkS5ZEGjBqmzo3cqEJ9GKD3Pk7YpDrURKw0JGIKfs/qYZEFhl7wA7smz7N0BB+RTImwsFKodsr1wyxIKf2syjfY9iE9eVEMEicyD7qeWNdZvc25fhGNpFiUpnM55F9GH2WJxvXabccfyMCW9ChAoGAOkU2gix/qYUP46bwm8JDstIpg5YXLrwkzBvH0xlSp1RxLLO2uTL0w5UXbjlpNrr6Mq7PrDXr/AIhx00+KdAHtHbOk75jsJyzMWpl5WtXuutSrvCyze+b3OJ+r0eRk/k9EUj6Nfl0DOCTEN/fRCrUNiCQN9xqyq0mgq63T6imjYsCgYBeiegO0YAs0hYwNRyUREsG5yHLix9sQT85SGHj8XPL+DnRwlMybPxleS6qU5lI6UN7Yt4RRfYYwb+a4EI5hf5DVcAWl0vanojymveqJRvyn40DiXdX3uR2tD3ujYSacm8ATdyaFqfvlwFlzwLGh6OwfmuxIGPMvBnU4BC1GguMEg==";
/**
* 支付寶公鑰,查看地址:https://openhome.alipay.com/platform/keyManage.htm 對應(yīng)APPID下的支付寶公鑰。
*/
public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2KKzch2l2g7Sx0TpZRtyk2+dQUxzKWMgK61p3qa5USF8WN50sSlCGRNy7FXbh7wp+KgQIUWc8IAMKqzpl0JfyB4axbalElB/jsijRwteiIQajPZAzAsxa82OMPVDty+XxCqiEmV1Dc0eAPPEsjd5zyqYxMhUGSnmWB6l8BYSsQCY5A92ep6+caDKQkvPUGB8Az7oP3aiuRjYfPe3YyhOxI0G02yej4jJ+jMOAuMCjLv2RnNcpu1I6XAEFIpCR2ru5ffx4ccFl+PR3w0yG4f2WYFXJYyFWmR9fdVq2rD5ZHVfcKlCAUhEVhqqZaxxSNJOTrxDP7x5U7jCerBHsqXFzQIDAQAB";
/**
* 服務(wù)器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問
*/
public static String NOTIFY_URL = "http://c68b2c1735f7.ngrok.io/supplier/notify_url";
/**
* 頁面跳轉(zhuǎn)同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問
*/
public static String RETURN_URL = "http://127.0.0.1:8081/supplier/return_url";
/**
* 商戶生成簽名字符串所使用的簽名算法類型,目前支持 RSA2 和 RSA,推薦使用 RSA2
*/
public static String SIGN_TYPE = "RSA2";
/**
* 編碼集,支持 GBK/UTF-8
*/
public static String CHARSET = "utf-8";
/**
* 支付寶網(wǎng)關(guān)(固定)
* 沙箱環(huán)境:https://openapi.alipaydev.com/gateway.do
* 生產(chǎn)環(huán)境:https://openapi.alipay.com/gateway.do
*/
public static String URL = "https://openapi.alipaydev.com/gateway.do";
/**
* 參數(shù)返回格式,只支持 json
*/
public static String FORMAT = "json";
}
關(guān)鍵參數(shù)說明:
① URL:支付寶支付網(wǎng)關(guān)(固定)
沙箱環(huán)境:https://openapi.alipaydev.com/gateway.do
生產(chǎn)環(huán)境:https://openapi.alipay.com/gateway.do
② APPID:APPID 即創(chuàng)建應(yīng)用后生成,沙箱環(huán)境進入沙箱應(yīng)用中查看
③ APP_PRIVATE_KEY:開發(fā)者私鑰,由開發(fā)者自己生成,從這里進行查看,從本地支付寶開發(fā)平臺開發(fā)助手生成的應(yīng)用私鑰獲取。

④FORMAT:參數(shù)返回格式,只支持 json
⑤CHARSET:編碼集,支持 GBK/UTF-8
⑥ALIPAY_PUBLIC_KEY:支付寶公鑰,由支付寶生成

⑦SIGN_TYPE:商戶生成簽名字符串所使用的簽名算法類型,目前支持 RSA2 和 RSA,推薦使用 RSA2
⑧RETURN_URL:頁面跳轉(zhuǎn)同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問
⑨NOTIFY_URL:服務(wù)器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問。
3、支付寶支付工具類
public class AlipayUtil {
//獲得初始化的AlipayClient
private final static AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID,
AlipayConfig.APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.SIGN_TYPE);
/**
* @annotation: alipay.trade.page.pay(統(tǒng)一收單下單并支付頁面接口)
*/
public static AlipayTradePagePayResponse createOrder(String outTradeNo, String totalAmount, String subject, String body, HttpServletResponse rep) {
//設(shè)置請求參數(shù)
//實例化具體API對應(yīng)的request類,類名稱和接口名稱對應(yīng),當(dāng)前調(diào)用接口名稱:alipay.trade.page.pay
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.RETURN_URL);
alipayRequest.setNotifyUrl(AlipayConfig.NOTIFY_URL);
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);//訂單編號 商戶訂單號,64個字符以內(nèi)、可包含字母、數(shù)字、下劃線;需保證在商戶端不重復(fù)
model.setProductCode("FAST_INSTANT_TRADE_PAY"); //銷售產(chǎn)品碼,與支付寶簽約的產(chǎn)品碼名稱。 注:目前僅支持FAST_INSTANT_TRADE_PAY
model.setTotalAmount(totalAmount);//訂單總金額,單位為元,精確到小數(shù)點后兩位,取值范圍[0.01,100000000]。
model.setSubject(subject);//訂單標(biāo)題
model.setBody(body);//訂單描述
alipayRequest.setBizModel(model);
//請求
String result = null;
AlipayTradePagePayResponse alipayTradePagePayResponse = null;
try {
alipayTradePagePayResponse = alipayClient.pageExecute(alipayRequest);
if (alipayTradePagePayResponse.isSuccess()){
System.out.println("創(chuàng)建訂單成功!");
}else {
System.out.println("創(chuàng)建訂單失敗!");
}
result = alipayTradePagePayResponse.getBody();
rep.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
rep.getWriter().write(result);//直接將完整的表單html輸出到頁面
rep.getWriter().flush();
rep.getWriter().close();
} catch (AlipayApiException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return alipayTradePagePayResponse;
}
/**
* @annotation: 調(diào)用統(tǒng)一收單線下交易查詢接口 alipay.trade.query
*/
public static String findOrder(String outTradeNo) throws AlipayApiException {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()){
System.out.println( "交易查詢調(diào)用成功" );
} else {
System.out.println("交易查詢調(diào)用失敗");
}
return response.getBody();
}
}
4、實際業(yè)務(wù)處理
@Controller
@RequestMapping("/supplier")
public class SupplierController extends BaseController {
/**
* @annotation: 獲得訂單編號
*/
public static synchronized String getOrderNumber() {
Date date = new Date();
return Long.valueOf(date.getTime()).toString();
}
/**
* @annotation: 支付寶支付
*/
@RequestMapping("/alipay")
@ResponseBody
public void alipay(Long supplierId, String loginName, String companyName, String expiredTime, String projectSign, HttpServletResponse rep) {
BidProject bidProject = new BidProject();
bidProject.setProjectSign(projectSign);
bidProject.setDelFlag(ProjectConstants.DEL_FLAG_NO);
List<BidProject> bidProjects = bidProjectService.selectBidProjectList(bidProject);
BigDecimal totalAmount = bidProjects.get(0).getUkPrice();
String subject = "";
if (ProjectConstants.PROJECT_SIGN_TN.equals(projectSign)) {
subject = "支付寶掃碼支付測試";
}
String body = supplierId + "-" + loginName + "-" + companyName;
String outTradeNo = getOrderNumber();
//創(chuàng)建訂單
AliPayOrder aliPayOrder = new AliPayOrder();
aliPayOrder.setTradeNo(outTradeNo);
aliPayOrder.setSubject(subject);
aliPayOrder.setBody(body);
aliPayOrder.setTotalAmount(totalAmount);
aliPayOrder.setProjectSign(projectSign);
aliPayOrder.setSupplierId(supplierId);
aliPayOrder.setOldExpiredTime(expiredTime);
//續(xù)費年限先默認一年
aliPayOrder.setRenewYear(1);
AlipayTradePagePayResponse tradePagePayResponse = AlipayUtil.createOrder(outTradeNo, totalAmount.toString(), subject, body, rep);
aliPayOrder.setCode(tradePagePayResponse.getCode());
aliPayOrder.setMsg(tradePagePayResponse.getMsg());
aliPayOrder.setSubCode(tradePagePayResponse.getSubCode());
aliPayOrder.setSubMsg(tradePagePayResponse.getSubMsg());
if (tradePagePayResponse.isSuccess()) {//成功
aliPayOrder.setTradeNo(tradePagePayResponse.getTradeNo());
aliPayOrder.setSellerId(tradePagePayResponse.getSellerId());
aliPayOrder.setMerchantOrderNo(tradePagePayResponse.getMerchantOrderNo());
}
//保存訂單信息
aliPayService.saveAliPayOrder(aliPayOrder);
}
/**
* @annotation: 支付寶頁面跳轉(zhuǎn)同步通知頁面路徑
*/
@GetMapping("/return_url")
public String return_url(HttpServletRequest request, ModelMap modelMap) throws UnsupportedEncodingException, AlipayApiException {
//獲取支付寶GET過來反饋信息
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] + ",";
}
//亂碼解決,這段代碼在出現(xiàn)亂碼時使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
String outTradeNo = params.get("out_trade_no");
AliPayOrder aliPayOrder = aliPayService.selectAliPayOrderByOutTradeNo(outTradeNo);
modelMap.put("aliPayOrder", aliPayOrder);
return "supplier/alipay/return_url";
}
/**
* @annotation: 服務(wù)器異步通知頁面路徑
*/
@RequestMapping("/notify_url")
@ResponseBody
public void notify_url(HttpServletRequest request, HttpServletResponse response) throws IOException, AlipayApiException, ParseException {
//獲取支付寶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] + ",";
}
//亂碼解決,這段代碼在出現(xiàn)亂碼時使用
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGN_TYPE); //調(diào)用SDK驗證簽名
if (signVerified) {//驗證成功
/**簽名驗證成功后,需要進行通知數(shù)據(jù)的驗證。
1、商戶需要驗證該通知數(shù)據(jù)中的 out_trade_no 是否為商戶系統(tǒng)中創(chuàng)建的訂單號;
2、判斷 total_amount 是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額);
3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據(jù)的對應(yīng)的操作方(有的時候,一個商戶可能有多個seller_id/seller_email)
4、驗證app_id是否為該商戶本身。
上述 1、2、3、4 有任何一個驗證不通過,則表明本次通知是異常通知,務(wù)必忽略。
*/
//商戶訂單號
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
AliPayOrder aliPayOrder = aliPayService.selectAliPayOrderByOutTradeNo(out_trade_no);
if (aliPayOrder == null) {
return;
}
//訂單的實際金額
String totalAmount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");
if (!aliPayOrder.getTotalAmount().equals(new BigDecimal(totalAmount))) {
return;
}
//商品賣方id
String sellerIid = new String(request.getParameter("seller_id").getBytes("ISO-8859-1"), "UTF-8");
if (!aliPayOrder.getSellerId().equals(sellerIid)) {
return;
}
//appid
String app_id = new String(request.getParameter("app_id").getBytes("ISO-8859-1"), "UTF-8");
if (!AlipayConfig.APPID.equals(app_id)) {
return;
}
//buyer_id
String buyer_id = new String(request.getParameter("buyer_id").getBytes("ISO-8859-1"), "UTF-8");
aliPayOrder.setBuyerId(buyer_id);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//交易創(chuàng)建時間
String gmt_create = new String(request.getParameter("gmt_create").getBytes("ISO-8859-1"), "UTF-8");
if (gmt_create != null) {
Date gmtCreate = simpleDateFormat.parse(gmt_create);
aliPayOrder.setGmtCreate(gmtCreate);
}
//交易付款時間
String gmt_payment = new String(request.getParameter("gmt_create").getBytes("ISO-8859-1"), "UTF-8");
if (gmt_payment != null) {
Date gmtPayment = simpleDateFormat.parse(gmt_payment);
aliPayOrder.setGmtPayment(gmtPayment);
}
//交易結(jié)束時間
String gmt_close = new String(request.getParameter("gmt_close").getBytes("ISO-8859-1"), "UTF-8");
if (gmt_close != null) {
Date gmtClose = simpleDateFormat.parse(gmt_close);
aliPayOrder.setGmtClose(gmtClose);
}
//交易狀態(tài)
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
aliPayOrder.setTradeStatus(trade_status);
aliPayService.updateAliPayOrder(aliPayOrder);
if (trade_status.equals("TRADE_SUCCESS")) { //默認開啟
//調(diào)用各個系統(tǒng)的接口
String url = "";
if (ProjectConstants.PROJECT_SIGN_TN.equals(aliPayOrder.getProjectSign())) {//泰能熱電電子招投標(biāo)系統(tǒng)
url = DictUtils.getDictValue("tn_project_interface_address", "supplier_ukey_renew");
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("supplierId", aliPayOrder.getSellerId());
jsonObject.put("expiredTime", aliPayOrder.getNowExpiredTime());
HttpUtils.sendPost(url, JSONObject.toJSONString(jsonObject));
}
response.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
response.getWriter().write("success");
response.getWriter().flush();
response.getWriter().close();
} else {//驗證失敗
response.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
response.getWriter().write("fail");
response.getWriter().flush();
response.getWriter().close();
}
}
}
四、接口調(diào)用
1、支付
電腦網(wǎng)站支付的支付接口alipay.trade.page.pay調(diào)用時序圖如下:

調(diào)用順序如下:
1. 商戶系統(tǒng)請求支付寶接口alipay.trade.page.pay,支付寶對商戶請求參數(shù)進行校驗,而后重新定向至用戶登錄頁面。
2. 用戶確認支付后,支付寶通過 get 請求 returnUrl(商戶入?yún)魅耄祷赝椒祷貐?shù)。
3. 交易成功后,支付寶通過 post 請求 notifyUrl(商戶入?yún)魅耄?,返回異步通知參?shù)。
4. 若由于網(wǎng)絡(luò)等問題異步通知沒有到達,商戶可自行調(diào)用交易查詢接口 alipay.trade.query進行查詢,根據(jù)查詢接口獲取交易以及支付信息(商戶也可以直接調(diào)用查詢接口,不需要依賴異步通知)。
注意:
①由于同步返回的不可靠性,支付結(jié)果必須以異步通知或查詢接口返回為準(zhǔn),不能依賴同步跳轉(zhuǎn)。
②商戶系統(tǒng)接收到異步通知以后,必須通過驗簽(驗證通知中的 sign 參數(shù))來確保支付通知是由支付寶發(fā)送的。詳細驗簽規(guī)則參考異步通知驗簽。
③接收到異步通知并驗簽通過后,一定要檢查通知內(nèi)容,包括通知中的 app_id、out_trade_no、total_amount 是否與請求中的一致,并根據(jù) trade_status 進行后續(xù)業(yè)務(wù)處理。
④在支付寶端,partnerId 與 out_trade_no 唯一對應(yīng)一筆單據(jù),商戶端保證不同次支付 out_trade_no 不可重復(fù);若重復(fù),支付寶會關(guān)聯(lián)到原單據(jù),基本信息一致的情況下會以原單據(jù)為準(zhǔn)進行支付。
五、支付結(jié)果異步通知
對于 PC 網(wǎng)站支付的交易,在用戶支付完成之后,支付寶會根據(jù) API 中商戶傳入的 notify_url,通過 POST 請求的形式將支付結(jié)果作為參數(shù)通知到商戶系統(tǒng)。
1、異步通知參數(shù)
公共參數(shù)


2、服務(wù)器異步通知頁面特性
①必須保證服務(wù)器異步通知頁面(notify_url)上無任何字符,如空格、HTML 標(biāo)簽、開發(fā)系統(tǒng)自帶拋出的異常提示信息等;
②支付寶是用 POST 方式發(fā)送通知信息,因此該頁面中獲取參數(shù)的方式,如:request.Form(“out_trade_no”)、$_POST[‘out_trade_no’];
③支付寶主動發(fā)起通知,該方式才會被啟用;
④只有在支付寶的交易管理中存在該筆交易,且發(fā)生了交易狀態(tài)的改變,支付寶才會通過該方式發(fā)起服務(wù)器通知(即時到賬交易狀態(tài)為“等待買家付款”的狀態(tài)默認是不會發(fā)送通知的);
⑤服務(wù)器間的交互,不像頁面跳轉(zhuǎn)同步通知可以在頁面上顯示出來,這種交互方式是不可見的;
⑥第一次交易狀態(tài)改變(即時到賬中此時交易狀態(tài)是交易完成)時,不僅會返回同步處理結(jié)果,而且服務(wù)器異步通知頁面也會收到支付寶發(fā)來的處理結(jié)果通知;
⑦程序執(zhí)行完后必須打印輸出“success”(不包含引號)。如果商戶反饋給支付寶的字符不是 success 這7個字符,支付寶服務(wù)器會不斷重發(fā)通知,直到超過24小時22分鐘。一般情況下,25小時以內(nèi)完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h);
⑧程序執(zhí)行完成后,該頁面不能執(zhí)行頁面跳轉(zhuǎn)。如果執(zhí)行頁面跳轉(zhuǎn),支付寶會收不到 success 字符,會被支付寶服務(wù)器判定為該頁面程序運行出現(xiàn)異常,而重發(fā)處理結(jié)果通知;
⑨cookies、session 等在此頁面會失效,即無法獲取這些數(shù)據(jù);
⑩該方式的調(diào)試與運行必須在服務(wù)器上,即互聯(lián)網(wǎng)上能訪問;
該方式的作用主要防止訂單丟失,即頁面跳轉(zhuǎn)同步通知沒有處理訂單更新,它則去處理;
當(dāng)商戶收到服務(wù)器異步通知并打印出 success 時,服務(wù)器異步通知參數(shù) notify_id 才會失效。也就是說在支付寶發(fā)送同一條異步通知時(包含商戶并未成功打印出 success 導(dǎo)致支付寶重發(fā)數(shù)次通知),服務(wù)器異步通知參數(shù) notify_id 是不變的。
3、異步返回結(jié)果的驗簽
某商戶設(shè)置的通知地址為 https://商家網(wǎng)站通知地址,對應(yīng)接收到通知的示例如下:
https://商家網(wǎng)站通知地址?voucher_detail_list=[{"amount":"0.20","merchantContribute":"0.00","name":"5折券","otherContribute":"0.20","type":"ALIPAY_DISCOUNT_VOUCHER","voucherId":"2016101200073002586200003BQ4"}]&fund_bill_list=[{"amount":"0.80","fundChannel":"ALIPAYACCOUNT"},{"amount":"0.20","fundChannel":"MDISCOUNT"}]&subject=PC網(wǎng)站支付交易&trade_no=2016101221001004580200203978&gmt_create=2016-10-12 21:36:12¬ify_type=trade_status_sync&total_amount=1.00&out_trade_no=mobile_rdm862016-10-12213600&invoice_amount=0.80&seller_id=2088201909970555¬ify_time=2016-10-12 21:41:23&trade_status=TRADE_SUCCESS&gmt_payment=2016-10-12 21:37:19&receipt_amount=0.80&passback_params=passback_params123&buyer_id=2088102114562585&app_id=2016092101248425¬ify_id=7676a2e1e4e737cff30015c4b7b55e3kh6& sign_type=RSA2&buyer_pay_amount=0.80&sign=***&point_amount=0.00
第一步: 在通知返回參數(shù)列表中,除去 sign、sign_type 兩個參數(shù)外,凡是通知返回回來的參數(shù)皆是待驗簽的參數(shù)。
第二步: 將剩下參數(shù)進行 url_decode,然后進行字典排序,組成字符串,得到待簽名字符串:
app_id=2016092101248425&buyer_id=2088102114562585&buyer_pay_amount=0.80&fund_bill_list=[{"amount":"0.80","fundChannel":"ALIPAYACCOUNT"},{"amount":"0.20","fundChannel":"MDISCOUNT"}]&gmt_create=2016-10-12 21:36:12&gmt_payment=2016-10-12 21:37:19&invoice_amount=0.80¬ify_id=7676a2e1e4e737cff30015c4b7b55e3kh6¬ify_time=2016-10-12 21:41:23¬ify_type=trade_status_sync&out_trade_no=mobile_rdm862016-10-12213600&passback_params=passback_params123&point_amount=0.00&receipt_amount=0.80&seller_id=2088201909970555&subject=PC網(wǎng)站支付交易&total_amount=1.00&trade_no=2016101221001004580200203978&trade_status=TRADE_SUCCESS&voucher_detail_list=[{"amount":"0.20","merchantContribute":"0.00","name":"5折券","otherContribute":"0.20","type":"ALIPAY_DISCOUNT_VOUCHER","voucherId":"2016101200073002586200003BQ4"}]
第三步: 將簽名參數(shù)(sign)使用 base64 解碼為字節(jié)碼串。
第四步: 使用 RSA 的驗簽方法,通過簽名字符串、簽名參數(shù)(經(jīng)過 base64 解碼)及支付寶公鑰驗證簽名。
第五步:需要嚴格按照如下描述校驗通知數(shù)據(jù)的正確性:
1、商戶需要驗證該通知數(shù)據(jù)中的 out_trade_no 是否為商戶系統(tǒng)中創(chuàng)建的訂單號;
2、判斷 total_amount 是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額);
3、校驗通知中的 seller_id(或者 seller_email) 是否為 out_trade_no 這筆單據(jù)的對應(yīng)的操作方(有的時候,一個商戶可能有多個 seller_id/seller_email);
4、驗證 app_id 是否為該商戶本身。
上述 1、2、3、4 有任何一個驗證不通過,則表明本次通知是異常通知,務(wù)必忽略。 在上述驗證通過后商戶必須根據(jù)支付寶不同類型的業(yè)務(wù)通知,正確的進行不同的業(yè)務(wù)處理,并且過濾重復(fù)的通知結(jié)果數(shù)據(jù)。在支付寶的業(yè)務(wù)通知中,只有交易通知狀態(tài)為 TRADE_SUCCESS 或 TRADE_FINISHED 時,支付寶才會認定為買家付款成功。
注意:
1、狀態(tài) TRADE_SUCCESS 的通知觸發(fā)條件是商戶簽約的產(chǎn)品支持退款功能的前提下,買家付款成功;
2、交易狀態(tài) TRADE_FINISHED 的通知觸發(fā)條件是商戶簽約的產(chǎn)品不支持退款功能的前提下,買家付款成功;或者,商戶簽約的產(chǎn)品支持退款功能的前提下,交易已經(jīng)成功并且已經(jīng)超過可退款期限。
異步通知驗簽:
Map<String, String> paramsMap = ... //將異步通知中收到的所有參數(shù)都存放到map中
boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET, SIGN_TYPE) //調(diào)用SDK驗證簽名
if(signVerfied){
// TODO 驗簽成功后,按照支付結(jié)果異步通知中的描述,對支付結(jié)果中的業(yè)務(wù)內(nèi)容進行二次校驗,校驗成功后在response中返回success并繼續(xù)商戶自身業(yè)務(wù)處理,校驗失敗返回failure
}else{
// TODO 驗簽失敗則記錄異常日志,并在response中返回failure.
}
六、將內(nèi)網(wǎng)ip映射成公網(wǎng)ip
使用ngrok工具
參考文檔:http://www.itdecent.cn/p/571fdbc98d25