此文檔寫于2017年3月,只能說明此時該文檔適用。使用前請查看以下接口支付寶是否提供。
- 批量付款到支付寶賬戶
- (鏈接如有發(fā)生變化,請在官方文檔中尋找此產(chǎn)品,一般情況下,產(chǎn)品名不會發(fā)生改變)
1. App支付產(chǎn)品
通俗上講就是在App中使用支付寶付款,流程上就是:App請求接口(服務(wù)端),哪一個用戶準(zhǔn)備要買什么產(chǎn)品或者是要充多少錢,然后服務(wù)端拼接一些必要的參數(shù)返回給它,App端通過集成支付寶的SDK,根據(jù)接口返回的值去喚醒支付寶進行支付;與此同時,支付寶會異步通知服務(wù)端,哪一筆訂單支付成功,服務(wù)端對充值后的邏輯做判斷。(代碼目前的做法)
-
見圖 alipay
看完讀可知,13,14步驟之前有10,圖中的做法是在App端喚醒支付寶支付完成后的回調(diào)里請求接口,然后接口去請求支付寶,校驗這筆訂單完成與否,13,14的步驟為了完成服務(wù)端的邏輯。
但實際上,支付寶是這樣說的:
支付寶sdk對商戶的請求支付數(shù)據(jù)處理完成后,會將結(jié)果同步反饋給商戶app端。
同步返回的數(shù)據(jù),商戶可以按照下文描述的方式在服務(wù)端驗證,驗證通過后,可以認為本次用戶付款成功。有些時候會出現(xiàn)商戶app在支付寶付款階段被關(guān)閉導(dǎo)致無法正確收到同步結(jié)果,此時支付結(jié)果可以完全依賴服務(wù)端的異步通知。
由于同步通知和異步通知都可以作為支付完成的憑證,且異步通知支付寶一定會確保發(fā)送給商戶服務(wù)端。為了簡化集成流程,商戶可以將同步結(jié)果僅僅作為一個支付結(jié)束的通知(忽略執(zhí)行校驗),實際支付是否成功,完全依賴服務(wù)端異步通知。
2. 批量付款到支付寶賬戶
- 即提現(xiàn);商戶(業(yè)主)用自己的支付寶轉(zhuǎn)給別人的支付寶賬戶。
- 很多應(yīng)用中涉及到提現(xiàn),支付寶提現(xiàn)是一種普遍做法(知聊、美麗約等),但是該接口屬于支付寶的歷史接口,在很長的時間內(nèi)沒有更新,曾經(jīng)問過客服,在不確定的某天,會將其重寫。
- 采用接口與后臺相配合實現(xiàn)提現(xiàn)的功能,App端提交提現(xiàn)申請,后臺(服務(wù)端)處理數(shù)據(jù),跳往支付寶的邏輯,支付寶處理完畢后,同樣給后臺反饋,只會給成功與失敗的反饋,這個地方有很多坑,具體請參見下面的描述。
強烈建議你(您),從官方的Api里尋求了解,獲取幫助,再結(jié)合鄙人粗糙的代碼進行完善或使用
3. App支付詳解
1. 前期業(yè)主申請操作:
在支付寶 開放平臺 注冊賬號:
- 該賬號必須為企業(yè)賬號,部分功能只針對企業(yè)用戶開放,有利于日后拓展;
- 請業(yè)主提前準(zhǔn)備:法定代表人信息、身份證照片、實際控制人信息、常用聯(lián)系手機號碼;企業(yè)證照(營業(yè)執(zhí)照、組織機構(gòu)代碼證)照片;企業(yè)銀行賬號信息;
登錄 開放平臺 并創(chuàng)建應(yīng)用:
- 選擇開發(fā)者中心—>概覽—>創(chuàng)建支付應(yīng)用—>創(chuàng)建—>填寫應(yīng)用名稱[App名稱] —>跳入完善信息頁面;
- 完善信息頁面的[功能列表]選擇我是商戶,功能只留一個APP支付即可,將其他的刪除[后期可根據(jù)需要實時添加,此處不必申請?zhí)?/em>];
- [開發(fā)配置]里的基礎(chǔ)環(huán)境無需配置,接口加簽方式的配置請參考同目錄下的[支付寶接口加簽方式說明];
- 配置完這一步可以獲得3個參數(shù):支付寶的公鑰、商戶私鑰、接口加簽方式[RSA或RSA2];
- 配置好后進行提交審核;這時返回應(yīng)用列表會看到剛剛創(chuàng)建的應(yīng)用,也有對應(yīng)的APPID;
應(yīng)用審核的同時,需要簽約APP支付產(chǎn)品:
- 在應(yīng)用列表里點開創(chuàng)建的應(yīng)用,在功能列表APP支付右上方有立即簽約字樣,或者登錄支付寶商家中心,單獨點擊簽約APP支付產(chǎn)品;
- 這里同微信支付類似,簽約需要提供APP的信息,支付寶需要一個文檔,把整個在APP中的支付流程說明。[需要特別注意自己的應(yīng)用是否涉及到了網(wǎng)絡(luò)文化經(jīng)營許可證的范疇,人工審核會判別這一點,如果是需要提供此證。如申請不下這個證,則會有一定的法律風(fēng)險,且不好通過]
其他說明:
- 但凡需要人工審核的地方都需要時間,請業(yè)主提早申請,避免延誤;
- 另外還有3個參數(shù),商戶支付寶賬戶[一般為郵箱,開放平臺的登錄賬號]、真實姓名[一般為xxx有限公司]、合作伙伴身份[開放平臺右上角用戶標(biāo)識下的賬號管理里的mapi網(wǎng)關(guān)產(chǎn)品密鑰的PID,以2088開頭的16位字符];
- 以上加粗標(biāo)識[除了標(biāo)題]的關(guān)鍵值均需業(yè)主提供給程序員,在開發(fā)中會用到;
2. 支付流程示例:
- 商戶APP-----向商戶服務(wù)端發(fā)起請求--1--商戶服務(wù)端--2—返回商戶APP;
- 商戶APP根據(jù)返回值調(diào)起支付寶支付,消費人支付成功;
- 支付寶服務(wù)端-----3-----向商戶服務(wù)端發(fā)起請求,商戶服務(wù)端完成自身業(yè)務(wù);
- 其他:數(shù)字標(biāo)志的含義,1代表處理自身業(yè)務(wù),2代表按照支付寶要求調(diào)用SDK拼接參數(shù)返回APP,3代表按支付寶要求驗證參數(shù),完成自己業(yè)務(wù)。
3.代碼結(jié)構(gòu)以及使用說明:
下載:
包含了微信支付和支付寶支付,密碼為phqa
結(jié)構(gòu):

使用說明:
- 操作者只需要填寫Config所需的信息,根據(jù)自身業(yè)務(wù)抒寫Controller、Service層即可,其他結(jié)構(gòu)無需修改;在Service里調(diào)用ALiPayCore進行支付寶必要的步驟即可。
- ALiPayConfig配置參數(shù)概覽:均為字符串
| 參數(shù)名 | 釋義或固定值 | 取得方式或固定值 |
|---|---|---|
| partner | 合作身份者ID以2088開頭由16位純數(shù)字組成的字符串 | 參考前期業(yè)主申請操作 |
| appid | 應(yīng)用id | 同上 |
| seller_email | 商戶支付寶賬號 | 同上 |
| account_name | 商戶真實姓名 | 同上 |
| private_key | 商戶的私鑰RSA | 同上 |
| ali_public_key | 支付寶的公鑰 RSA | 同上 |
| pay_sign_type | 簽名方式 | 現(xiàn)在支付寶支持2個值RSA或RSA2,該值須和私鑰公鑰生成方式一致。 |
| notify_url | 支付寶服務(wù)器主動通知商戶服務(wù)器里指定的頁面http/https路徑 | 參考外網(wǎng)映射的配置方式https://www.ngrok.cc/ |
| subject | 商品的標(biāo)題/交易標(biāo)題/訂單標(biāo)題/訂單關(guān)鍵字等 | 栗子:索尼-相機購買 |
| 以下值為固定值 | --- | --- |
| method | 接口名稱 | alipay.trade.app.pay |
| version | version | 1.0 |
| product_code | 銷售產(chǎn)品碼,商家和支付寶簽約的產(chǎn)品碼 | QUICK_MSECURITY_PAY |
| input_charset | 字符編碼格式 目前支持 gbk 或 utf-8 | utf-8 |
在Controller層抒寫兩個業(yè)務(wù),處理APP調(diào)起支付和支付寶的回調(diào);調(diào)起App的接口可以自定義參數(shù),接收支付寶的回調(diào)參數(shù)無需修改。在Service里抒寫自己的具體業(yè)務(wù),然后調(diào)用固定方法進行處理。
eg:A用戶要購買100元的衣服,提供給App的接口需要接收用戶的id,衣服的id,在Service層里處理訂單的業(yè)務(wù)之后,調(diào)用ALiPayCore.createAliPayStr (out_trade_no,total_fee)方法,傳入商戶內(nèi)部訂單號和用戶所要支付的金額,并將返回值返給前端即可;
前端接收到返回值調(diào)起支付寶SDK,用戶支付完畢;
支付寶會異步通知在Config里配置的回調(diào)地址,在回調(diào)的Service層里,調(diào)用ALiPayCore.checkAliPayParam(request)進行驗證,判斷是否是支付寶發(fā)送來的通知以及是否支付成功,返回值不為NULL即可進行商品發(fā)貨或者填寫快遞訂單號等業(yè)務(wù)。兩個核心方法使用介紹:
1.String orderString = ALiPayCore.createAliPayStr (String out_trade_no,String total_fee);
a) 拼接支付寶支付必要參數(shù),加簽,并返回字符串;
b) out_trade_no 傳入商戶內(nèi)部訂單號,必須唯一;total_fee 所要支付的金額,格式必須為0.00,單位為元;
c) 返回App端調(diào)起支付所需要的參數(shù)。
2.Map<String, String> aliParam = ALiPayCore.checkAliPayParam (request);
a) 支付成功的回調(diào):檢測支付寶異步反饋是否真實,不為null為真實,null為校驗失敗。
b) 返回的map中可以取得支付寶返回的參數(shù),一般只取out_trade_no(商戶內(nèi)部訂單號)、total_amount(充值的金額,單位為元)、trade_no(支付寶訂單號)。
c) 判斷aliParam是否不為NULL,不為NULL拿商戶內(nèi)部訂單號去查出充值的訂單數(shù)據(jù),完成自己的業(yè)務(wù)。
- 特別注意:在處理的回調(diào)里,處理成功后需要向支付寶反饋成功:
Eg:out.print(“success”); *
如果處理失敗則無需反饋;支付寶如果接收不到正確反饋,則會按照一定策略重復(fù)通知,代碼中應(yīng)該有去重處理[判斷該訂單是否已經(jīng)處理過*]
注意事項:
ALiPayCore類里的方法均不需要修改,和業(yè)務(wù)無關(guān),直接調(diào)用即可。
引用jar:
集成支付寶接口需要引入的文件是:
- alipay-sdk-java*.jar
- commons-logging-1.1.1.jar
若進一步了解代碼實現(xiàn)請引入文件:
- alipay-sdk-java*-source.jar
- commons-logging-1.1.1-sources.jar
4.備注以及拓展:
文檔的第一步驟可以截取發(fā)給業(yè)主參考申請,讓業(yè)主提供加粗的參數(shù)信息;
- 本著程序員應(yīng)全身心在開發(fā)上,避免不必要的時間浪費。
對ALiPayCore.createAliPayStr()方法的解釋:
- 方法的作用就是按照支付寶的要求返回字符串,APP端根據(jù)此返回值調(diào)起支付寶;
- 字符串里包含著訂單信息,[支付寶要扣多少錢、支付寶服務(wù)端給哪個地址發(fā)送支付成功的通知];
- 不論是支付寶還是微信都有自己的一套規(guī)則,但大體上都很相似:
1.將參數(shù)的key值按照ascii碼的順序進行排序,并轉(zhuǎn)化成key=value&key=value的形式;
2.然后支付寶會讓調(diào)用各自語言的SHA加密算法,利用自己的私鑰對剛剛的key=value進行加密得到sign參數(shù),再將sign當(dāng)作key,拼入字符串中;
3.為了防止中文value亂碼,需要對所有的value進行URLEncoder.encode。 - 支付寶以上的3步是為了確保這個通知是商戶的自身發(fā)送出來的,拿私鑰加簽實際上是采用了非對稱性加密的方式,確保唯一性。[如果支付寶不這么做,訂單信息被別人篡改了,對雙方都將是最壞的結(jié)果]。
對ALiPayCore.checkAliPayParam()方法的解釋:
- 方法的作用就是檢測是否是支付寶發(fā)送來的通知,根據(jù)此通知進行支付成功的業(yè)務(wù);
- 支付寶是不會發(fā)送支付失敗的通知的;
- 驗證也有相應(yīng)的規(guī)則:
1.將支付寶的參數(shù)都取出[這里有個拓展,requesr.getgetParameterMap()就可以取出Map形式的參數(shù)];
2.剔除sign和sign_type字段,變成按照ascii碼排序的key=value&形式;
3.調(diào)用各自語言對應(yīng)的驗簽方法,這一步拿支付寶公鑰進行驗證返回布爾值;
4.以上的步驟其實也是非對稱性的驗證方式。 - 同理,支付寶需要這樣的驗證步驟,也是為了確保該通知是支付寶發(fā)出的,以防別人冒發(fā)送請求;
- 支付寶防止意外,還讓對訂單號、商戶的基本信息做驗證。
拓展:
- 關(guān)于數(shù)字簽名這里有一篇文章介紹的很全面。
- 也推薦關(guān)注該文章的翻譯者:阮一峰。
新發(fā)現(xiàn):
重新修改此文時,發(fā)現(xiàn)支付寶更新了SDK的介紹頁面,在這個鏈接里,支付寶也介紹了如何通過調(diào)用SDK完成服務(wù)端的工作。
4. 批量付款到支付寶賬戶詳解
1. 前期業(yè)主申請操作:
-
找文檔
這個藏得很深的,因為在支付寶的明面上看不到它,不像App支付,在類似產(chǎn)品大全的頁面能找到;首先進入支付寶的 文檔中心 在左側(cè)找到 歷史接口 ,但愿你看到的還是這個樣子,如果在那一欄底下也標(biāo)注了新的接口,那么你就重寫它吧(笑)。

-
業(yè)主簽約
由于屬于歷史接口,支付寶的考量可能是不建議新商家接入,所以簽約的方式比較繁雜,需要App支付產(chǎn)品簽約成功后,再找人工客服簽約。
人工客服可以通過登錄支付寶開放平臺,如圖1所示的地方喚醒;喚醒的是一只機器人,和它聊三句以上的話,會提醒接入人工,如圖2。然后就可以和人工說我需要接入批量付款到支付寶賬戶這個有密接口,跟著她的節(jié)奏來進行了。這一步也是需要業(yè)主完成的,因為有業(yè)主需要考量的東西,程序員做不了主。


人工客服大概會說,該接口每筆會收取0.5%的手續(xù)費,不滿1元按1元算,最高不會超過25元,您同意嗎?,同意就簽約了。一般這個費用是轉(zhuǎn)嫁給用戶的,看業(yè)主如何考量了。
2.程序員進行配置、抒寫:
-
獲取PID、MD5Key
pid就不說了,以2088開頭的數(shù)字;
配置密鑰, 參照文檔 上指引的地方,把MD5那個key保存下來,用于請求數(shù)據(jù)的簽名和支付寶返回數(shù)據(jù)的驗簽(后期才注意到,在“提現(xiàn)”這一模塊,簽名是用MD5簽名的)。這里就不放圖了,支付寶的網(wǎng)頁跳來跳去,而且經(jīng)常改動,無法確切它的位置,文檔里倒是更新開很快,這點比某信要強多了。
-
簡單綜述一下:
用戶,在App中填寫好自己的支付寶真實姓名以及對應(yīng)的支付寶賬戶,然后在提現(xiàn)那個地方,輸上提現(xiàn)的金額,點擊提交,接口就接收了這個數(shù)據(jù),做一定的處理(比如轉(zhuǎn)嫁手續(xù)費什么的),在提現(xiàn)表里有一個狀態(tài)標(biāo)識是提現(xiàn)申請、處理中、成功、失??;后臺處理申請的提現(xiàn),拼接一些參數(shù)給支付寶,處理成功后,支付寶給回饋,成功的成功,失敗的失敗。成功,失敗拿系統(tǒng)消息作為提示,失敗之后返回給用戶金錢,可以重新提交申請。失敗的原因可能是用戶支付寶填的不正確,正常情況下是這樣的。但是有很多意外情況,也就帶來了很多坑,下面會拿粗體涉及。
-
App接口處理方面
對簡單的參數(shù)做驗證后,查看用戶是否綁定了支付寶賬戶信息,以及用戶的提現(xiàn)的錢是否符合夠用(項目里是最低標(biāo)準(zhǔn)是5元)。
/**
* 提現(xiàn)申請
* @return
*/
@ResponseBody
@RequestMapping("applicationWithdraw")
public Object applicationWithdraw(Integer pid, BigDecimal money){
//驗證參數(shù)
if(ObjectUtil.isPassInteger(pid) && money != null && money.compareTo(new BigDecimal(5)) != -1){
if(aliacount_dao.selectByPersionId(pid).size()!=0){//用戶已綁定支付寶
Persion persion = persion_dao.queryPersionByIdForIM(pid);
if(money.compareTo(new BigDecimal(persion.getCapitalBalance())) != 1){
// 申請?zhí)岈F(xiàn)業(yè)務(wù)
boolean flag = wiService.insertApplication(persion, money);
return flag==true?"success":"error";
}else{
return "error";//用戶金額不足
}
}else{//用戶未綁定支付寶
return "fail";
}
}else{
return "error";
}
}
下面這塊給拼接了一個流水號,在后面會有介紹;然后轉(zhuǎn)嫁手續(xù)費到用戶身上,插記錄,扣錢;以事務(wù)的方式進行控制。關(guān)于用戶手續(xù)費方面,其實還可以更加靈活一些,無非就是多提少收,少提多收;這個也得業(yè)主考量了。
**
* 申請?zhí)岈F(xiàn)業(yè)務(wù)
* @return
*/
@Transactional
public boolean insertApplication(Persion persion,BigDecimal money){
String onceId = UUIDHashCode.getOrderIdByUUId();//獲得一個隨機唯一標(biāo)識
//扣除支付寶的手續(xù)費 0.5% 最高不會超過25元 最低不會超過1元
BigDecimal platform_cost = money.multiply(new BigDecimal(0.005)).setScale(2, BigDecimal.ROUND_HALF_UP);
if(platform_cost.compareTo(new BigDecimal(25)) == 1){
platform_cost = new BigDecimal(25);
}else if(platform_cost.compareTo(new BigDecimal(1)) == -1){
platform_cost = new BigDecimal(1);
}
BigDecimal getMoney = money.subtract(platform_cost);//扣除手續(xù)費之后的金額
int num = 0 ;
if(getMoney.compareTo(new BigDecimal(0)) == 1){
// 新增一條提現(xiàn)申請記錄
num = withMa.insertSelective(new Withdraw(onceId,persion.getId(),getMoney,1,platform_cost));
}
if(num == 1){
// 修改該用戶信息中可提現(xiàn)金額信息
persion.setCapitalBalance(money.doubleValue());
Persion queryPersion = perMa.queryPersionByIdForIM(persion.getId());
if(queryPersion != null){
persion.setVersion(queryPersion.getVersion());
int withdr = perMa.cutPersionCap(persion);
return withdr>0?true:false;
}else{
num = 1/0;
return false;
}
}else{
num = 1/0;
return false;
}
}
-
后臺進行處理
因為這個提現(xiàn)還是需要人工操作的,比如輸支付密碼什么的,支付寶的考量也可能是為了安全吧,所以需要有后臺的一系列邏輯。像App支付一樣,先從基本信息配置入手。
AlipayConfig 這里關(guān)鍵在于私鑰和公鑰,可以看一看 阮一峰的數(shù)字簽名是什么 支付寶App支付的公鑰和這里提現(xiàn)所用的公鑰是不一致的,對應(yīng)的RSA的與RSA2的公鑰也是不同的。之前不了解公鑰的意義,這里的公鑰和App支付的共用了,也算是無知的坑吧。商戶安全校驗碼就是上面讓保存的那個MD5的key值。
public class AlipayConfig {
//↓↓↓↓↓↓↓↓↓↓請在這里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 合作身份者ID,以2088開頭由16位純數(shù)字組成的字符串
public static String partner = "2088xxxxxxx";
//商戶支付寶賬號
public static String seller_email = "";
//商戶真實姓名
public static String account_name = " XXXXX科技有限公司";
// 支付寶用于提現(xiàn)的公鑰,一般情況下無需修改該值 (提現(xiàn)),也可與支付寶map網(wǎng)關(guān)產(chǎn)品密鑰的支付寶公鑰做對比,應(yīng)該是一致的。
public static String ali_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB";
//商戶安全校驗碼
public static String key = "MD5key";
//支付寶異步通知地址 需http://格式的完整路徑,不允許加?id=123這類自定義參數(shù)
public static String notify_url = PathUtil.GetDemain() + "/WoBanAdmin/ALiPay/TransNotify";
//public static String notify_url = "http://xiaofanfight.viphk.ngrok.org/WoBanAdmin/ALiPay/TransNotify";
//↑↑↑↑↑↑↑↑↑↑請在這里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// 調(diào)試用,創(chuàng)建TXT日志文件夾路徑
public static String log_path = "D:\\";
// 字符編碼格式 目前支持 gbk 或 utf-8
public static String input_charset = "utf-8";
// 簽名方式 不需修改(退款簽名方式)
public static String sign_type = "MD5";
}
代碼里有很多是運用支付寶提供的Demo,支付寶并沒有對這個接口提供jar包,公司的前輩也對此進行了開創(chuàng)性的研究,而我只是完善了一下,并寫成了拙劣的文檔。
后臺的流程及頁面展示:
頁面是位于pages - recharge 里的。

這里每進一次頁面會有提示,因為很重要,所以每次都提醒;提示框的藍色按鈕可以跳往支付寶對證書的提示,它會說,目前只支持IE 32位部分瀏覽器以及UC瀏覽器,Mac下只支持safari;但我實際測試的時候,只支持火狐......支付寶需要證書是為了后面你輸入支付寶支付密碼時候的安全。

一個的時候展示一個的詳情,多人的時候展示多人的信息,點擊結(jié)算后的頁面是以彈出層展示的

這里的話其實不加驗證也可以的,畢竟支付寶那邊還要驗證支付密碼,或者這里也可以結(jié)合一下管理員的手機號,進行處理。
輸入密碼,點擊確定后,就走Controller了,從控制層里拼接一些參數(shù),跳往支付寶頁面。下面詳述ALiPayController
實際上,在上一張圖我們可知,能傳過來的數(shù)據(jù)無非就幾個:
1.哪個管理員操作的;
2.哪些人申請的提現(xiàn)。
因為像申請的錢什么的是依賴表的而不是頁面上的數(shù)據(jù)。
參數(shù)ids實際上控制著三個選擇,全部處理、按照選擇處理、單個處理;然后一些基礎(chǔ)信息的取得,計算提現(xiàn)的總額(依靠SQL),最為關(guān)鍵就是拼接參數(shù),按照文檔進行,參數(shù)中最為關(guān)鍵的就是 付款詳細數(shù)據(jù) 以及 批次號和 流水號,批次號每次都是隨機生成的,因為支付寶將相同的批次號視作同一筆申請,而流水號在App端提交的時候就做了處理;這里不需要關(guān)注一些細節(jié),在支付寶提供的Demo里有涉及,有興趣可以點進去看一看。
/**
*
* <b>Method ALiPayTrans</b>
* <dd>方法作用:請求批量轉(zhuǎn)賬前一步
* <dd>適用條件:提現(xiàn)
* <dd>使用方法:請參照最新的支付寶批量轉(zhuǎn)賬有密接口文檔
* @param aid 管理員id
* @param ids 提現(xiàn)申請人id的集合
* @param out 用于給支付寶回饋(大概吧)
* @since Met 2.0
*/
@RequestMapping(value = "ALiPayTrans", method = RequestMethod.GET)
public void ALiPayTrans(Integer aid, String ids,PrintWriter out) {
DecimalFormat df = new DecimalFormat(".00");//將double類型的數(shù)據(jù)保留兩位數(shù)
List<Withdraw> wlist = new ArrayList<Withdraw>();
if(ids!=null && !ids.equals("")){
if(ids.equals("all")){//全部處理
wlist = withdrawMapper.selectWithdraw(new Withdraw());
}else{
if(ids.contains(",")){
String Ids=ids.trim().substring(0, ids.length() - 1);
Withdraw withdraw = new Withdraw();
withdraw.setId(0);
withdraw.setBatchNo(Ids);
wlist = withdrawMapper.selectWithdraw(withdraw);
}else{//單個流水
wlist = withdrawMapper.selectWithdraw(new Withdraw(Integer.parseInt(ids)));
}
}
//服務(wù)器異步通知頁面路徑 通知提現(xiàn)成功與失敗
String notify_url = AlipayConfig.notify_url;
//付款賬號
String email = AlipayConfig.seller_email;
//付款賬戶名
String account_name = AlipayConfig.account_name;
//必填,個人支付寶賬號是真實姓名公司支付寶賬號是公司名稱
//付款當(dāng)天日期
String pay_date = DateUtil.getDays();
//必填,格式:年[4位]月[2位]日[2位],如:20100801
//批次號
String batch_no = DateUtil.getDays() + DateUtil.getThree() + DateUtil.getThree();
//必填,格式:當(dāng)天日期[8位]+序列號[3至16位],如:201008010000001
//付款總金額
//計算總額
BigDecimal summoney = withdrawMapper.querySumByGet(wlist);
if(summoney != null && summoney.compareTo(new BigDecimal(10000000)) == -1){
String batch_fee = df.format(summoney).toString();
//必填,即參數(shù)detail_data的值中所有金額的總和
//付款筆數(shù)
Integer num = wlist.size();
String batch_num = num.toString();
//必填,即參數(shù)detail_data的值中,“|”字符出現(xiàn)的數(shù)量加1,最大支持1000筆(即“|”字符出現(xiàn)的數(shù)量999個)
/*
* 下列付款詳細數(shù)據(jù)說明以及示例
* String detail_data = batch_no + "^" + "zhangsan@qq.com" + "^" + "張三" + "^" + batch_fee + "^備注說明";
* 解釋:其中batch_no為上面生成的轉(zhuǎn)賬批次號;zhangsan@qq.com為需要轉(zhuǎn)賬的支付寶賬戶;張三為轉(zhuǎn)賬支付寶賬戶的真實姓名;
* batch_fee為轉(zhuǎn)賬金額,最后的參數(shù)為附加參數(shù),可以對本次轉(zhuǎn)賬備注說明,只要是字符串就可以,但長度不宜過長。請根據(jù)需要以此替換
*/
//必填,即參數(shù)detail_data的值中,“|”字符出現(xiàn)的數(shù)量加1,最大支持1000筆(即“|”字符出現(xiàn)的數(shù)量999個)
//付款詳細數(shù)據(jù)
String detail_data = "";
for(int i=0;i<wlist.size();i++){
detail_data += wlist.get(i).getOnceid() + "^" + wlist.get(i).getAccount() + "^" + wlist.get(i).getRealname() + "^" + wlist.get(i).getMoney() + "^平臺手續(xù)費"+wlist.get(i).getPlatform_cost()+"元"+"|";
}
detail_data = detail_data.substring(0,detail_data.length()-1);
//batch_no + "^" + "收款方賬號" + "^" + "收款方真實姓名" + "^" + batch_fee + "^備注說明";
//必填,格式:流水號1^收款方帳號1^真實姓名^付款金額1^備注說明1|流水號2^收款方帳號2^真實姓名^付款金額2^備注說明2....
//修改提現(xiàn)信息
for(int j=0;j<wlist.size();j++){
Withdraw mapper = new Withdraw(wlist.get(j).getId(), batch_no, aid);
mapper.setState(2);
withdrawMapper.updateWithdraw(mapper);
}
//把請求參數(shù)打包成數(shù)組
Map<String, String> sParaTemp = new HashMap<String, String>();
sParaTemp.put("service", "batch_trans_notify");
sParaTemp.put("partner", AlipayConfig.partner);
sParaTemp.put("_input_charset", AlipayConfig.input_charset);
sParaTemp.put("notify_url", notify_url);
sParaTemp.put("email", email);
sParaTemp.put("account_name", account_name);
sParaTemp.put("pay_date", pay_date);
sParaTemp.put("batch_no", batch_no);
sParaTemp.put("batch_fee", batch_fee);
sParaTemp.put("batch_num", batch_num);
sParaTemp.put("detail_data", detail_data);
//建立請求
//
Log log = new Log(aid,"處理提現(xiàn)申請操作,處理額度為" + summoney + "元");
logMa.insert(log);
String sHtmlText = AlipaySubmit.buildRequest(sParaTemp, "post", "確認");
out.println(sHtmlText);
}else{
out.print("<script>alert('處理批次額度超限。最高不超過1000萬元。');</script>");
}
}
}
順利就跳到了以下的頁面,值得注意的是,在這個Controller內(nèi),將這筆提現(xiàn)申請改成了處理中,避免其他人會重復(fù)處理。請注意這個狀態(tài)。

支付寶提示當(dāng)前操作環(huán)境不支持支付寶控件,因為我是谷歌瀏覽器打開的,這種情況支付寶不會給異步通知,因為它不知道是這種問題,它會通知的情況只有兩種:
1.支付成功;
2.支付失敗,給轉(zhuǎn)賬的支付寶用戶信息不正確。
所以處理中的作用就是存放意外情況下的申請,管理員可以在合適的環(huán)境下,將處理中的申請再次轉(zhuǎn)化為申請中,再次提交。


以下為出現(xiàn)意外的情況截圖:



正常情況下:

下面這個圖就說明了,支付寶會給打款成功的回調(diào)。


剛剛問了半天客服,客服也無法說清楚,建議是在IE8 32位瀏覽器下進行的,可是IE8會和layui會有沖突,這就有點尷尬了。以上的截圖是在火狐下進行的??头膊恢每煞?。
完成了支付寶付款的邏輯,就剩下接收回調(diào)的處理了,也在Controller層,對于成功走成功邏輯,失敗走失敗邏輯。
這里關(guān)注一下支付寶的說明即可:如果成功的信息為空,證明都失敗了,反之;如果兩者都不為空,就需要各自走各自的邏輯了,根據(jù)流水號查出提現(xiàn)的詳情,成功推送信息,失敗返回資金并推送。

@RequestMapping(value = "TransNotify", method = RequestMethod.POST)
/**
* 批量付款數(shù)據(jù)中轉(zhuǎn)賬成功的詳細信息 String success_details
* 批量付款數(shù)據(jù)中轉(zhuǎn)賬失敗的詳細信息 String fail_details
* 批量付款數(shù)據(jù)中轉(zhuǎn)賬批次號 String batch_no
*/
public void TransNotify(HttpServletRequest request, String success_details, String batch_no,
String fail_details, PrintWriter out) {
//獲取支付寶POST過來反饋信息
Map<String, String> params = GetInfoFromALiPay(request);
boolean flag = true;
if(AlipayNotify.verify(params)){
//驗證成功
//判斷是否在商戶網(wǎng)站中已經(jīng)做過了這次通知返回的處理;如果沒有做過處理,那么執(zhí)行商戶的業(yè)務(wù)程序;如果有做過處理,那么不執(zhí)行商戶的業(yè)務(wù)程序
//可以判斷success_details是否為null來標(biāo)識轉(zhuǎn)賬是否成功,支付寶方面明確說明如果轉(zhuǎn)賬成功success_details不為null,fail_details則
//為null;若轉(zhuǎn)賬失敗success_details為null而fail_details不為null,同樣根據(jù)batch_no來查詢轉(zhuǎn)賬對象并更新轉(zhuǎn)賬狀態(tài)
if(fail_details == null){
//提現(xiàn)全部成功 處理相關(guān)業(yè)務(wù) 看是否已經(jīng)處理過了 改狀態(tài) 推送
withdrawService.getMoneySuccess(batch_no);
}else if(success_details == null){
//提現(xiàn)全部失敗
//返還未提現(xiàn)用戶的金幣 推送
String info = withdrawService.getMoneyError(batch_no);
if("error".equals(info)){
flag = false;//自己sql出錯,請求支付寶再次發(fā)送驗證
}
}else{
//轉(zhuǎn)賬部分成功/部分失敗
String info = withdrawService.getMoneySuccessOrError(batch_no, success_details, fail_details);
if("error".equals(info)){
flag = false;//自己sql出錯,請求支付寶再次發(fā)送驗證
}
}
if(flag){
out.print("success");//請勿修改該值!
}else{
out.print("fail");//自己sql出錯,請求支付寶再次發(fā)送驗證
}
}else{
//驗證失敗
//程序執(zhí)行完后必須打印輸出“success”(不包含引號)。如果商戶反饋給支付寶的字符不是success這7個字符,支付寶服務(wù)器會不斷重發(fā)通知,直到超過24小時22分鐘
out.print("fail");
}
}
@SuppressWarnings("unchecked")
public Map<String, String> GetInfoFromALiPay(HttpServletRequest request) {
Map<String, String> params = new HashMap<String, String>();
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]
+ ",";
}
//亂碼解決,這段代碼在出現(xiàn)亂碼時使用。如果mysign和sign不相等也可以使用這段代碼轉(zhuǎn)化
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
params.put(name, valueStr);
}
return params;
}
5. 結(jié)束
第一次總結(jié),文字還是很冗余的。在這里還是需要感謝一下ydm公司的前輩。寫于2017-03-17。