作为开放式的B/S架构程序,无论所属电商,金融,机械制造,企业OA,ERP,CRM,CMS等等行业或系统中,第三方支付以及银联支付的业务一定是客户关心所在,也是保证客户系统盈利运营的一个重要保障。通常这种B2C或者C2C系统的开发,商户用户所关注的支付平台大多离不开“阿里支付宝,快钱,腾讯财付通,易宝支付这种第三方支付平台以及中国银联UnionPay....等等”这些方式。
最近某项目中涉及到支付的模块与涉及流程,在此和大家分享一下。
1,名词释义
商户网站:比如淘宝,聚美,唯品会这种B2C/C2C的网站及后台的管理系统,统称为商户网站;主要负责对买家订单数据的封装,加密,
及支付平台回调的订单处理。
支付平台:我们需要开发的支付平台,支付接口,支付模拟的Servlet,暴露出来的WebService接口url等;主要负责对买家请求来的
加密后的订单数据进行解密,构造请求的URL,拼接参数,对Sign进行加密,对支付机构异步(或同步)请求回调的数据
进行封装,解密回传给商户网站。
支付机构:比如阿里支付宝,快钱,腾讯财付通,易宝支付这种第三方支付平台等支付机构。
Sign:支付机构为商户分配的一把“**”与”合作者ID“同时分配,用做调用Base64,MD5等加密算法在加密解密时的一种私钥,通常
与此相关联的还有signType,就是加密方式。
回调:对上次请求端request中的url或指定的url进行http请求,或https请求
支付平台请求,响应,及回调流程图:

2,业务流设计(本文只介绍alipay的即时到账接口:"create_direct_pay_by_user")
2.1 商户网站对数据封装加密,调用支付接口:
2.1.1)商户网站后台对买家的订单进行封装,插入商户网站db中的订单表(比如:xxx_order);
PayReturnVovo
= new PayReturnVo();
vo.setOrderId("kuaiqian00232");
vo.setOrderAmount("20");
vo.setOrderTime("20140504121020");
vo.setProductName("3M网线,送水晶头");
vo.setProductId("2213229319378");
vo.setProductNum("2");
vo.setPayType("00");*/
// 把模拟的表单数据转成Json
StringorderJson= PaymentJsonUtil.beanToJson(vo);
// 通过db获取商家key**
Stringkey
= dao.getKeyByUserId(userId);
// 根据key使用base64加密算法对订单信息进行加密
StringSignedJson = CryptUtil.encryptBase64Des(orderJson,
key);
2.1.2)于此同时调用dao层查询买家用户平台账户余额,并进行锁表:在SQL的select后加入 forupdate
wait n(最好
为1-5秒,此处的 数值为httpclient请求超时时长)为防止订单被多用户修改。
2.2 支付平台响应请求及解密,调用支付机构接口:
2.2.1)支付平台响应请求,对数据进行解密;
//获取输入参数
InputStreamis =
request.getInputStream();
//把接收的加密流转成String类型
StringpayMsgJson
= IOUtils.toString(is, "utf-8");
//base64进行解密
byte[]byteJson
= CryptUtil.decryptBASE64payMsgJson
StringstrJson
= new String(byteJson,"UTF-8");
//把解密后的json转换成实体vo
try{
pVo
= (BankPayVo)PaymentJsonUtil.jsonToBean(strJson,BankPayVo.class);
}catch (Exception e) {
e.printStackTrace();
throw(e);
}
2.2.2)从db查询商户协议信息,构造不同方式的支付机构所需请求的url;
publicString
CreateUrl(PayBankEntity payBankEntity) throws BankpayException,SQLException{
StringwebPartentId
= payBankEntity.getWebPartentId();
//通过DB获取阿里支付Config信息
AliPayAccountDaoImplaccount
= new AliPayAccountDaoImpl();
AliPayAccountVoaccVo
= account.getAccountInfo(webPartentId);
//根据订单号区别b2a和b2c对partner参数设置
StringstrOrderNo
= payBankEntity.getOrderNo();
//阿里支付合作伙伴ID
Stringpartner
= accVo.getPaPartner();
//阿里支付key
Stringkey=
accVo.getPaKey();
//阿里支付接口
Stringpaygateway
= accVo.getPaPayGateWay();
//阿里支付服务名
Stringservice
= accVo.getPaService();
//阿里支付签名Sign加密方式
Stringsign_type
= accVo.getPaSignType();
//卖家账号,邮箱
Stringseller_email
= accVo.getPaSellerEmail();
//######
Form Web ###### 商户网站订单
Stringout_trade_no
= payBankEntity.getOrderNo();
//######
Form Web ###### 交易总额
Stringtotal_fee
= payBankEntity.getMoney();
//######
Form Web ###### 商品名称
String
subject= payBankEntity.getProductId();
//######
Form Web ###### 商品展示地址
StringinputCharset
= accVo.getPaInputCharset();
//######
Form Web ###### 支付类型
Stringpayment_type
= payBankEntity.getPaymentType();
//超时时长
Stringit_b_pay
= accVo.getPaItBBay();
//!!! 在此修改参数为异步notify_url但是vo和db中显示为return_url
Stringreturn_url
= accVo.getPaReturnUrl();
StringItemUrl="";
2.2.2.temp) PS: 下行代码的CreateUrl()是根据请求参数首字母降序排列,把参数重新构造成新的url。
ItemUrl=
Payment.CreateUrl(paygateway,service,sign_type,inputCharset,payment_type,
partner,key,out_trade_no,total_fee,return_url,seller_email,subject,it_b_pay);
System.out.println("异步通知返回agbpay地址:"+
return_url);
returnItemUrl;
}
2.2.3)StringBuffer绘制跳转请求的html
dom元素,把参数请求到支付机构;
publicString
getBankHtml(PayBankEntity payBankEntity) throws BankpayException {
StringBuffer
sbHtml = new StringBuffer();
try
{
sbHtml.append("");
sbHtml.append("
支付网关");
sbHtml.append("
sbHtml.append("
");
}catch
(Exception e) {
throw
new BankpayException("系统异常,错误描述:"
+ e.getMessage());
}
return
sbHtml.toString();
}
2.2.4)切记不要忘记设置支付机构回调支付平台的回调url,大多数支付机构的参数为同步和异步两种,设置支付机构的
回调url目的在于它进行了我们的请求。处理之后对订单数据及订单等状态的回写,进而支付平台可以封装,
加密成json串,继续调用商户网站,对这次支付的信息进行更改,执行具体业务。
下面是阿里的api,同步和异步回调路径不能同时为空。
notify_url 服务器异步通知页面路径 String(160) 支付宝服务器主动通知商户网站里指定的页面Http路径 可空
returl_url 服务器同步通知页面路径 String(160) 支付宝完成处理后当前页面自动跳转到商户网站的Http路径 可空
下面是快钱的api,同步和异步回调路径不能同时为空。
pageUrl 接受支付结果的页面地址 String(256) 需要是绝对地址,与bgUrl不能同时为空,当bgUrl为空时,生效 可空
bgUrl 接受支付结果后台代码地址 String(256) 需要是绝对地址,与pageUrl不能同时为空,当pageUrl为空时,生效 可空
2.3 支付平台响应支付机构回调:被支付机构接收的订单支付成功或失败之后,回调我们支付平台的接口。
1)把支付宝的请求输入流转成我们需要的vo对象,调用2)中的performTask()。
//获取输入参数
InputStreamis
= request.getInputStream();
//转成String类型
String
payMsgJson =IOUtils.toString(is, "utf-8");
PayReturnVovos
= PaymentJsonUtil.jsonToBean(payMsgJson, PayReturnVo.class);
request.setAttribute("returnStr",vos);
newAliPayReturnBo().performTask(request,
response);
2)把支付宝的请求输入流转成我们需要的vo对象,调用2)中的performTask()。
@SuppressWarnings("unused")
publicstatic
String performTask(HttpServletRequest request,
HttpServletResponseresponse)
throws IOException, ServletException {
StringreturnStr
= "";
StringwebPartentId
= "";
try{
Stringsign
= request.getParameter("sign");
//支付状态:TRADE_FINISHED(普通即时到账的交易成功状态)||TRADE_SUCCESS(开通
了高级即时到账或机票分销产品后的交易成功状态)
StringtradeStatus
= request.getParameter("trade_status");
//订单编号
StringorderNo
= request.getParameter("out_trade_no");
//通知類型
Stringnotify_type
= request.getParameter("notify_type");
//支付宝交易流水号
Stringtrade_no
= "";
//订单总价
Stringamount
= request.getParameter("total_fee");
if(request.getParameter("trade_no")
!= null) {
trade_no=
request.getParameter("trade_no");
}
StringalipayNotifyURL
= "http://notify.alipay.com/trade/notify_query.do?"
+"partner="
+partner
+"¬ify_id="
+request.getParameter("notify_id");
//获取支付宝ATN返回结果,true是正确的订单信息,false 是无效的
//StringresponseTxt
= CheckURL.check(alipayNotifyURL);
Mapparams
= new HashMap();
//获得POST 过来参数设置到新的params中
for(Iterator
iter = requestParams.keySet().iterator(); iter
.hasNext();){
Stringname
= (String) iter.next();
String[]values
= (String[]) requestParams.get(name);
StringvalueStr
= "";
for(int
i = 0; i < values.length; i++) {
valueStr=
(i == values.length - 1) ? valueStr + values[i] :valueStr + values[i] + ",";
}
params.put(name,valueStr);
}
//2、校验支付结果
StringpayStatus
= "1";
Stringmysign
= com.alipay.util.SignatureHelper.sign(params,privateKey);
//验证
booleanverifySuccess
= mysign.equalsIgnoreCase(sign);
//获取支付交易状态
booleantradeFinished
= tradeStatus
.equalsIgnoreCase("TRADE_SUCCESS")
||tradeStatus.equalsIgnoreCase("TRADE_FINISHED");
if(verifySuccess&&
tradeFinished)
{
//TODO 调用agbweb接口告知支付结果
PayReturnVovos
= (PayReturnVo) request.getAttribute("returnStr");
StringwebPartengId
= vos.getWebPartentId();
//通过DB获取阿里支付Config信息
AliPayAccountDaoImplaccount
= new AliPayAccountDaoImpl();
AliPayAccountVoaccVo
= account.getAccountInfo(webPartengId);
Stringkey
= accVo.getWebKey();
vos.setOutTradeNo(vos.getBillNo());
vos.setTotal_free(vos.getTotal_free());
vos.setPrivate_key(key);
StringnotifyType
= vos.getNotifyType();
StringpayStatuss
= vos.getPay_status();
// 支付银行
if(notifyType.equals("trade_status_sync"))
{
vos.setBankName("ALIPAY");
}else
vos.setBankName("QUICKMONEY");
// 支付结果
if(payStatuss.equals("TEADE_SUCCESS")||
payStatuss.equals("TEADE_FINISHED")){
// 阿里-支付成功
vos.setTradeFlag("ALIPAY_T");
}
returnStr=
PaymentJsonUtil.beanToJson(vos);
// 原封Json+key
StringreturnStrWithKey
= key + returnStr;
// MD5加密
StringbyteMD5
= MD5Util.MD5Encode(returnStrWithKey);
returnMsg(request,response,
returnStr , byteMD5);
}else
if (!verifySuccess) { // "AliPay返回的结果信息认证没有通过"
//}else
if (false) { // "AliPay返回的结果信息认证没有通过"
thrownew
BankpayException("Alipay支付返回失败");
}else
{ // AliPay返回没有TRADE_FINISHED
thrownew
BankpayException("Alipay支付返回失败");
}
}catch
(Exception e) {
e.printStackTrace();
}
return
returnStr;
}
3)回调商户网站的接口,告知支付状态以及回调的订单信息。
publicstatic
void returnMsg(HttpServletRequest request,
HttpServletResponseresponse,
String strMsg , String strMD5)
try{
URLurl
= new URL(
"http://10.1.126.10:8080/agb/payResponse.servlet?str="+
strMsg + "&strMD5=" + strMD5);
HttpURLConnectionhttp
= (HttpURLConnection) url.openConnection();
http.setRequestMethod("POST");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout","30000");// 连接超时30秒
System.setProperty("sun.net.client.defaultReadTimeout","30000");
// 读取超时30秒
http.connect();
//TODO 把数据回写到agbweb
OutputStreamos
= http.getOutputStream();
//os.write(strMsg.getBytes("UTF-8"));//传入参数
os.flush();
os.close();
InputStreamis
= http.getInputStream();
}catch
(IOException e) {
e.printStackTrace();
throw(e);
}
}
4)被支付机构接收的订单有可能存在回调失败等情况,虽然这种情况是百万分之一的机会,但为了防止交易过程没有
进行回调,也可以通过Spring的定时任务注解:@Scheduled注解进行“对账接口”的定时对账,在此不进行详细
介绍,接口名为“Sign_trade_query”。
2.4 商户网站响应支付平台回调:
1)流获取,转换String
UTF-8;
2)解密,Json转化为Vo;
3)执行某个Service/Bo;
4)更新DB,订单表等;
5)回写页面,告知用户支付结果。
本篇日志仅大致描述了支付宝交易的一次请求流程:
1)商户网站(订单加密)
2)订单解密)支付平台(构造url)
3)阿里接口
4)封装订单vo
-- 支付平台 -- 订单加密,模拟请求
5)商户网站(db操作订单)的操作流程。
其中包括其中的4次加密以及2次回调和两次模拟的http请求。其他第三方或银联支付平台与此结构大致一样,只是API中的参数或构造URL的方式,加密算法有个别差异。
以上。