聚合支付

第三章 支付宝(即时到账交易接口)



文章目录

  • 聚合支付
  • 前言
  • 一、开发前准备
  • 二、java实现
  • 1.pom中引入相关jar包
  • 2.支付相关工具类
  • 3.支付相关参数
  • 4.支付实现
  • 5.查询实现
  • 6.支付回调



前言

支付宝支付接口分两种,一种是“统一下单接口”在第一章已经介绍过了,第二种就是本章要介绍的内容“即时到账交易接口”,相关文档参考:https://opendocs.alipay.com/open/65/104807。相较于“统一下单接口”方式来说,“即时到账接口”目前应用相对来说较少,一般都是之前旧系统使用的,新开发的系统大都使用“统一下单接口”,另外由于支付宝开展“MD5密钥专项治理”,新用户申请不到MD5秘钥需要使用rsa或者rsa2秘钥进行相关数据交互签名和验签。


提示:以下是本篇文章正文内容,下面案例可供参考

一、开发前准备

对接支付接口前商户需要项支付宝申请获取一些必要的商户接入参数:支付宝账号、合作者id、MD5秘钥、接入地址等参数。

二、java实现

1.pom中引入相关jar包

<dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>

2.支付相关工具类

代码如下(示例):

public class MD5Util {
    /**
     * 支付宝消息验证地址
     */
    private static final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&";
    /**
     * 生成签名结果
     * @param sPara 要签名的数组
     * @return 签名结果字符串
     */
    public static String buildRequestMysign(Map<String, Object> sPara,String md5Key) {
        String prestr = createLinkString(sPara); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        String mysign = "";
        mysign = sign(prestr, md5Key, "UTF-8");
        return mysign;
    }
    /**
     * 签名字符串
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }
    /**
     * 签名字符串
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
        text = text + key;
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
        if(mysign.equals(sign)) {
            return true;
        }
        else {
            return false;
        }
    }
    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }

    /**
     * 除去数组中的空值和签名参数
     */
    public static Map<String, Object> paraFilter(Map<String, Object> sArray) {
        Map<String, Object> result = new HashMap<String, Object>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = (String) sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     */
    public static String createLinkString(Map<String, Object> params) {
        //去除空值
        params = paraFilter(params);
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = (String) params.get(key);
            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }

    /**
     * 根据反馈回来的信息,生成签名结果
     */
    public static boolean getSignVeryfy(Map<String, Object> Params, String sign,String md5Key) {
        //过滤空值、sign与sign_type参数
        Map<String, Object> sParaNew = paraFilter(Params);
        //获取待签名字符串
        String preSignStr = createLinkString(sParaNew);
        //获得签名验证结果
        boolean isSign = false;
        isSign = verify(preSignStr, sign, md5Key, "UTF-8");
        return isSign;
    }
    /**
     * 获取远程服务器ATN结果,验证返回URL
     */
    private static String verifyResponse(String notify_id,String partner) {
        //获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求
        String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "¬ify_id=" + notify_id;
        return checkUrl(veryfy_url);
    }
    /**
     * 获取远程服务器ATN结果
     */
    private static String checkUrl(String urlvalue) {
        String inputLine = "";
        try {
            URL url = new URL(urlvalue);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection
                    .getInputStream()));
            inputLine = in.readLine().toString();
        } catch (Exception e) {
            e.printStackTrace();
            inputLine = "";
        }
        return inputLine;
    }

    /**
     * 验证消息是否是支付宝发出的合法消息
     */
    public static boolean verify(Map<String, Object> params,String partner,String md5Key) {
        //判断responsetTxt是否为true,isSign是否为true
        //responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
        //isSign不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关
        String responseTxt = "true";
        if(params.get("notify_id") != null) {
            String notify_id = (String) params.get("notify_id");
            responseTxt = verifyResponse(notify_id,partner);
        }
        String sign = "";
        if(params.get("sign") != null) {sign = (String) params.get("sign");}
        boolean isSign = getSignVeryfy(params, sign,md5Key);
        if (isSign && responseTxt.equals("true")) {
            return true;
        } else {
            return false;
        }
    }
}

public class AlipayHttpUtil {
    /**
     * 支付宝提供给商户的服务接入网关URL(新)
     */
    private static final String ALIPAY_GATEWAY_NEW = "https://mapi.alipay.com/gateway.do?";

    /**
     * 生成要请求给支付宝的参数数
     */
    private static Map<String, Object> buildRequestPara(Map<String, Object> sParaTemp,String md5Key) {
        //除去数组中的空值和签名参数
        Map<String, Object> sPara = MD5Util.paraFilter(sParaTemp);
        //生成签名结果
        String mysign = MD5Util.buildRequestMysign(sPara,md5Key);
        //签名结果与签名方式加入请求提交参数组中
        sPara.put("sign", mysign);
        sPara.put("sign_type", "MD5");
        return sPara;
    }
    /**
     * 建立请求,以表单HTML形式构造(默认)
     */
    public static String buildRequest(Map<String, Object> sParaTemp, String strMethod, String strButtonName,String md5Key) {
        //待请求参数数组
        Map<String, Object> sPara = buildRequestPara(sParaTemp,md5Key);
        List<String> keys = new ArrayList<String>(sPara.keySet());
        StringBuffer sbHtml = new StringBuffer();
        sbHtml.append("<form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\"" + ALIPAY_GATEWAY_NEW
                + "_input_charset=" + "UTF-8" + "\" method=\"" + strMethod
                + "\">");
        for (int i = 0; i < keys.size(); i++) {
            String name = (String) keys.get(i);
            String value = (String) sPara.get(name);
            sbHtml.append("<input type=\"hidden\" name=\"" + name + "\" value=\"" + value + "\"/>");
        }
        //submit按钮控件请不要含有name属性
        sbHtml.append("<input type=\"submit\" value=\"" + strButtonName + "\" style=\"display:none;\"></form>");
        sbHtml.append("<script>document.forms['alipaysubmit'].submit();</script>");
        return sbHtml.toString();
    }

    /**
     * 建立请求,以表单HTML形式构造,带文件上传功能
     */
    public static String buildRequest(Map<String, Object> sParaTemp, String strMethod, String strButtonName, String strParaFileName,String md5Key) {
        //待请求参数数组
        Map<String, Object> sPara = buildRequestPara(sParaTemp,md5Key);
        List<String> keys = new ArrayList<String>(sPara.keySet());
        StringBuffer sbHtml = new StringBuffer();
        sbHtml.append("<form id=\"alipaysubmit\" name=\"alipaysubmit\"  enctype=\"multipart/form-data\" action=\"" + ALIPAY_GATEWAY_NEW
                + "_input_charset=" + "UTF-8" + "\" method=\"" + strMethod
                + "\">");
        for (int i = 0; i < keys.size(); i++) {
            String name = (String) keys.get(i);
            String value = (String) sPara.get(name);
            sbHtml.append("<input type=\"hidden\" name=\"" + name + "\" value=\"" + value + "\"/>");
        }
        sbHtml.append("<input type=\"file\" name=\"" + strParaFileName + "\" />");
        //submit按钮控件请不要含有name属性
        sbHtml.append("<input type=\"submit\" value=\"" + strButtonName + "\" style=\"display:none;\"></form>");
        return sbHtml.toString();
    }

    /**
     * 建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果
     * 如果接口中没有上传文件参数,那么strParaFileName与strFilePath设置为空值
     */
    public static String buildRequest(Map<String,Object> sParaTemp,String md5Key) throws Exception {
        //待请求参数数组
        Map<String, Object> sPara = buildRequestPara(sParaTemp,md5Key);
        HttpProtocolHandler httpProtocolHandler = HttpProtocolHandler.getInstance();
        HttpRequest request = new HttpRequest(HttpResultType.BYTES);
        //设置编码集
        request.setCharset("UTF-8");
        request.setParameters(generatNameValuePair(sPara));
        request.setUrl(ALIPAY_GATEWAY_NEW+"_input_charset="+"UTF-8");
        HttpResponse response = httpProtocolHandler.execute(request);
        if (response == null) {
            return null;
        }
        String strResult = response.getStringResult();
        return strResult;
    }

    /**
     * MAP类型数组转换成NameValuePair类型
     */
    private static NameValuePair[] generatNameValuePair(Map<String, Object> properties) {
        NameValuePair[] nameValuePair = new NameValuePair[properties.size()];
        int i = 0;
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            nameValuePair[i++] = new NameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
        }
        return nameValuePair;
    }
}

3.支付相关参数

public class  AliPayConfig {
    //这里用natapp内外网穿透,主要用于支付回调(用外网服务器更好)
    public static final String natUrl = "http://xiaotiancai.natapp1.cc";

    // 合作者身份ID
    public static partner="245625.......";

    // 支付宝账号
    public static seller_email="test.....@163.com";

    // md5秘钥
    public static String mchID="sdahsjdskjf....." ;

    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notifyUrl = natUrl + "/aliPay/AliPayNotify";
    public static String returnUrl = natUrl + "/aliPay/AliPayReturn";
    
    // 签名方式
    public static String signType = "MD5";

    // 字符编码格式
    public static String charset = "UTF-8";
    //支付请求地址
    public static String payUrl = "https://mapi.alipay.com/gateway.do";

    //支付接口名称
    public static String payService = "create_direct_pay_by_user";

    //查询接口名称
    public static String queryService = "single_trade_query";
}

4.支付实现

代码如下(示例):

public void oldAliPayUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
             SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmssSSS");
           //商户订单号,商户网站订单系统中唯一订单号,必填(可以用一些重要的数加上时间戳组装,回调时可通过订单号截取使用)
            String orderNo = sdf.format(new Date());
            Map sParaTemp = new HashMap();
            sParaTemp.put("service", AliPayConfig.payService);
            sParaTemp.put("partner", AliPayConfig.partner);//合作者身份Id
            sParaTemp.put("seller_email", AliPayConfig.seller_email);//
            sParaTemp.put("_input_charset", "UTF-8");
            sParaTemp.put("payment_type", "1");//支付类型,只支持值为1
            sParaTemp.put("notify_url", AliPayConfig.notifyUrl);
            sParaTemp.put("return_url", AliPayConfig.returnUrl);
            sParaTemp.put("out_trade_no", orderNo);
            sParaTemp.put("subject", "乌龙茶");
            sParaTemp.put("total_fee", payAmount);
            sParaTemp.put("body","商品");
            sParaTemp.put("paymethod", "bankPay");
            sign = MD5Util.buildRequestMysign(sParaTemp,AliPayConfig.md5Key);
            String subject = URLEncoder.encode("商品","UTF-8");
            String body = URLEncoder.encode("乌龙茶","UTF-8");
            String notifyUrl = URLEncoder.encode(notify_url,"UTF-8");
            String returnUrl = URLEncoder.encode(return_url,"UTF-8");
            String seller_email = URLEncoder.encode(seller_email,"UTF-8");
            payData = rdcNetpayPbank.getNetpayUrl();
            payData += "?service="+AliPayConfig.payService+
                    "&partner="+AliPayConfig.partner+
                    "&seller_email="+seller_email+
                    "&_input_charset="+"UTF-8"+
                    "&payment_type="+"1"+
                    "¬ify_url="+notifyUrl+
                    "&return_url="+returnUrl+
                    "&out_trade_no="+orderNo+
                    "&subject="+subject+
                    "&sign="+sign+
                    "&sign_type="+AliPayConfig.signType+
                    "&total_fee="+payAmount+
                    "&body="+body+
                    "&paymethod="+"bankPay";
 }

5.查询实现

代码如下(示例):

public String aliPayOrderQuery(String orderNo) throws Exception {
        Map sParaTemp = new HashMap();
        sParaTemp.put("service", AliPayConfig.queryService);
        sParaTemp.put("partner", AliPayConfig.partner);
        sParaTemp.put("_input_charset", AliPayConfig.charset);
        sParaTemp.put("out_trade_no", orderNo);
        String resultStr = AlipayHttpUtil.buildRequest(sParaTemp, AliPayConfig.md5Key);
        System.out.println("result++++++:" + resultStr);
        Alipay alipay = AlipayXmlUtil.xmlToObject(resultStr, Alipay.class);
        if ("T".equals(alipay.getIsSuccess())){
            AlipayRequest alipayRequest = alipay.getRequest();
            String alipaySign = alipay.getSign();
            AlipayResponse alipayResponse = alipay.getResponse();
            AlipayTrade alipayTrade = alipayResponse.getTrade();
            JSONObject tradeJson = (JSONObject) JSON.toJSON(alipayTrade);
            Map<String, Object> tradeMap = (Map<String, Object>) tradeJson;
            boolean issign = MD5Util.getSignVeryfy(tradeMap, alipaySign, AliPayConfig.md5Key);
            if(issign){
                if ("TRADE_SUCCESS".equals(alipayTrade.getTrade_status()) || "TRADE_FINISHED".equals(alipayTrade.getTrade_status())){
                    /*
                    *支付成功
                    */
                }else{
                   /*
                   *支付失败,或未支付
                   */
                }
            }
        }
    }

6.支付回调

代码如下(示例):

@RequestMapping(value = "/aliPay/AliPayNotify")
    public void AliPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取支付宝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] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用
			//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        //验证签名
        boolean signVerified = MD5Util.verify(params,AliPayConfig.partner,AliPayConfig.md5Key);//验证签名
        if (signVerified) {//验证成功
            //商户订单号
            String out_trade_no = params.get("out_trade_no");
            //支付宝交易号
            String trade_no = params.get("trade_no");
            //付款金额
            String total_amount = params.get("total_amount");
            //交易状态
            String total_amount = params.get("trade_status");
            if (trade_status.equals("TRADE_FINISHED") || trade_status.equals("TRADE_SUCCESS")) {
                /*
                 * 支付成功
                 * 业务规则实现
                 */
                 //回复支付公司
                 try {
                      response.getWriter().print("SUCCESS");
                    } catch (IOException e) {
                        e.printStackTrace();
                   }
            }
            
        } else {//验证失败
            /*
             * 支付失败
             */
        }
    }