8Kpay稳定支付口1

请点击此处输入图片描述

​作为开放式的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的方式,加密算法有个别差异。

仅供参考,个人觉得bo中的业务逻辑处理得还不够细致,欢迎大家提出最宝贵的意见,一起探讨学习。

以上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容