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