Java支付宝扫码支付[旧]

在实际开发过程中,首先采用当面付模式进行开发,但没有成功,个人认为当面付更适合线下模式
之后改用即时到账模式才成功接入支付宝扫码支付功能
以下内容是基于即时到账模式开发
要使用支付宝即时到账接口,首先需要签约即时到账产品, 申请地址
审核进度通常需要1个工作日,但实际几小时即可
之后需要获取和设置如下关键信息
pid:2088621150311111 合作伙伴身份ID
key:zrqyf6dfli7dvji4mmi4sw1111111111 MD5密钥
notify_url:http://127.0.0.1:8080/sop/order/notify/ali 异步通知回调地址
return_url:http://127.0.0.1:8080/sop/order/notify/ali/return 同步通知回调地址

更多精彩

模式

  1. 当面付 ,消费者扫描商户二维码完成支付。并不适用于Web网页端扫码支付
  2. 即时到账 ,用户在线向开发者的支付宝账号支付资金。Web网页端扫码支付首选

开发步骤

  1. 下载支付宝提供的即时到账 DEMO
  • 按下图所示将alipay下所有文件复制到项目开发目录,支付宝已将接口的调用、请求、验签等操作完成,我们只需要包装好数据后调用即可


    image
  1. 打开AlipayConfig.java,将其中关键信息替换成之前准备好的内容
  • 除了下述四个信息,其他内容均不需要修改
  • ** partner**即pid,key即md5密钥,都可以在 mapi网关产品密钥 获取
  • notify_url是支付宝异步通知链接,由支付宝主动调用,一旦用户完成支付,即调用该地址
  • 不可添加自定义参数
  • 必须保证外网可正常访问
  • 使用POST方式发送/接收数据
  • 没有时间限制
  • 如果是https,则必须安装ssl证书,并且需要正规的证书机构签发,自签名的无法识别
  • return_url,是支付宝同步跳转通知,用户完成支付后,在支付页面完成跳转
  • 不可添加自定义参数
  • 必须保证外网可正常访问,但可在本机测试
  • 使用GET方式发送/接收数据
  • 一分钟超时
// 合作身份者ID,签约账号,以2088开头由16位纯数字组成的字符串,查看地址:https://b.alipay.com/order/pidAndKey.htm
public static String partner = SOPConstants.ALI_PAY_PARTNER_ID;
 
// MD5密钥,安全检验码,由数字和字母组成的32位字符串,查看地址:https://b.alipay.com/order/pidAndKey.htm
public static String key = SOPConstants.ALI_PAY_MD5_KEY;

// 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = SOPConstants.ALI_PAY_NOTIFY_URL;

// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url = SOPConstants.ALI_PAY_RETURN_URL;
  1. 在订单支付页面创建一个空的div,用于接收调用即时到账接口返回的表单数据
$.ts.doAction("/api/order/pay/ali", {
    orderId: orderId
}, function () {
    // 提交表单内容
    $orderSubmitPanel.find("#returnAli").append(this.data);

   // 打开订单状态轮询页面,确保支付完成后,支付页面能自动跳转
    $.ts.openModalWindow("/api/order/pay/ali/tip/" + orderId, 350, 300);
}, "", "", "");
@RequestMapping(value = "/ali", method = RequestMethod.POST)
@ResponseBody
public ResponseData ali(HttpServletRequest request, @RequestParam("orderId") final String orderId) {
    SimpleActionHandler actionHandler = new SimpleActionHandler(request) {
        @Override
        protected void doHandle(ResponseData responseData) throws Exception {
            responseData.setData(orderPayService.aliPay(IdEncoder.decodeId(orderId)));
        }
    };

    return actionHandler.handle();
}
  1. 按照 请求参数说明 包装请求参数,并建立请求
  • 建立请求的操作支付宝已在AlipaySubmit.java中实现,只需要调用其中buildRequest()方法并传入参数即可
public String aliPay(Long orderId) {
    // 获取订单信息
    OrderDTO order = orderManageService.getOrder(orderId);

    // 包装请求参数
    Map<String, String> params = Maps.newHashMap();
    // 调用接口
    params.put("service", AlipayConfig.service);
    // 签约账号id
    params.put("partner", AlipayConfig.partner);
    // 收款支付账号,默认与签约账号相同
    params.put("seller_id", AlipayConfig.seller_id);
    // 编码格式,支付GBK和UTF-8
    params.put("_input_charset", AlipayConfig.input_charset);
    // 支付类型
    params.put("payment_type", AlipayConfig.payment_type);
    // 异步通知回调地址
    params.put("notify_url", AlipayConfig.notify_url);
    // 同步通知跳转地址
    params.put("return_url", AlipayConfig.return_url);
    // 防钓鱼时间戳
    params.put("anti_phishing_key", AlipayConfig.anti_phishing_key);
    // 客户端IP
    params.put("exter_invoke_ip", super.getClientIP());
    // 订单号
    params.put("out_trade_no", order.getCode());
    // 标题
    params.put("subject", "轻实训-" + order.getName());
    // 金额
    params.put("total_fee", String.valueOf(order.getPrice()));

    // 建立请求
    return AlipaySubmit.buildRequest(params, "get", "确认");
}
  • 由于调用即时到账接口返回的表单数据会自动提交,而接收表单数据的div是在订单提交页面,因此按照表单提交默认方式,会导致订单提交页面被替换成支付宝扫码页面
  • 所以需要修改AlipaySubmit.java中的buildRequest()方法,在其拼接的表单中加入target=“_blank”,让表单提交时在新页面打开
public static String buildRequest(Map<String, String> sParaTemp, String strMethod, String strButtonName) {
    //待请求参数数组
    Map<String, String> sPara = buildRequestPara(sParaTemp);
    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=" + AlipayConfig.input_charset + "\" method=\"" + strMethod
                  + "\" target=\"_blank\">");

    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();
}
  1. 用户通过支付宝扫描二维码完成支付后,支付宝会主动调用notify_url进行回执
  • 由于回执时并没有携带用户信息,所以如果使用了诸如shiro等安全框架的,需要给予该回执地址一个访问许可,否则会被安全框架屏蔽
@RequestMapping(value = "/ali", method = RequestMethod.POST)
@ResponseBody
public String aliNotify(HttpServletRequest request) {
    return orderPayService.aliNotify(request);
}
  • 接收并处理支付回执后,必须通知支付宝回执接收成功,否则支付宝会认为回执发送失败,并发送数次通知
  • 支付宝发送异步通知后,如果没有获取到成功回执(返回success),则会按一定规律重发(4m,10m,10m,1h,2h,6h,15h)
  • 接收支付宝的回执信息时,必须进行验签,改操作支付宝已实现,我们只需要调用AlipayNotify.java中的verify()方法即可
  • 验签成功后需要判断操作状态,从回执参数中获取trade_status,参数值为TRADE_SUCCESS即为支付成功
  • 还有一个订单状态是TRADE_FINISHED,该状态表示订单已完成,即超过三个月的退款期限,一旦订单到达这个状态,支付宝会再次调用notify_url进行回执,若业务系统没有退款流程,则无需处理该状态
public String aliNotify(HttpServletRequest request) {
    // 订单号
    String orderCode = request.getParameter("out_trade_no");
    // 交易状态
    String tradeStatus = request.getParameter("trade_status");

    // 验签
    if (AlipayNotify.verify(getNotifyData(request))) {
        // 交易成功
        if (tradeStatus.equals("TRADE_SUCCESS")) {
            // 更新订单信息
            updateOrderInfo(orderCode, OrderPay.aliPay.getCode());
        }

        return "success";
    } else {
        return "fail";
    }
}
  • 处理回执内容,用于验签
private Map<String, String> getNotifyData(HttpServletRequest request, String method) throws Exception {
    // 获取回执内容
    Map requestParams = request.getParameterMap();

    // 过滤回执内容
    Map<String, String> params = Maps.newHashMap();
    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] + ",";
        }

        if (method.equals("notify")) {
            params.put(name, valueStr);
        } else {
            params.put(name, new String(valueStr.getBytes("ISO-8859-1"), "utf-8"));
        }
    }

    return params;
}
  1. 用户通过支付宝扫描二维码完成支付后,在新打开的支付宝窗口会执行return_url进行跳转
  • 由于跳转时并没有携带用户信息,所以如果使用了诸如shiro等安全框架的,需要给予该跳转地址一个访问许可,否则会被安全框架屏蔽
public String aliReturn(HttpServletResponse response, HttpServletRequest request) throws Exception {
    orderPayService.aliReturn(response, request);
        
   // 跳转地址会通过输出流的方式关闭新窗口,所以无需返回任何内容
    return null;
}
  • 接收跳转通知时,同样需要验签,以确保内容的安全性
  • 为防止网络等不可预测原因导致异步回执没能成功接收,所以在接收跳转通知并验签通过以及状态判断成功后,同样需要对订单状态进行更新
  • 如果用户完成支付后立即关闭页面,会导致同步跳转通知无法执行,所以不能完全依赖该通知确认订单状态
  • 如果用户在支付页面扫码生成预下单订单,但并未直接付款,而是前往支付宝订单页重新付款,即使网页端支付页面未关闭,也无法执行该同步跳转通知
  • 通过同步跳转通知更新订单状态,只是一种辅助措施,主要手段还是通过接受异步回执来处理订单状态
  • 由于支付宝的扫码页面是在新窗口打开,在支付完成后并没有存在的必要,即可以通过输出流的方式关闭该窗口
public void aliReturn(HttpServletResponse response, HttpServletRequest request) throws IOException {
    // 订单号
    String orderCode = request.getParameter("out_trade_no");
    // 交易状态
    String tradeStatus = request.getParameter("trade_status");

    // 验签
    if (AlipayNotify.verify(getNotifyData(request))) {
        if (tradeStatus.equals("TRADE_FINISHED") || tradeStatus.equals("TRADE_SUCCESS")) {
            // 更新订单信息
            updateOrderInfo(orderCode, OrderPay.aliPay.getCode());

            // 页面跳转
            response.setContentType("text/html;charset=gb2312");

     // 通过输出流关闭窗口
            PrintWriter writer = response.getWriter();
            writer.println("<html><head><title> CLOSE </title></head>");
            writer.println("<body>");
            writer.println("<script type=\"text/javascript\">window.close();</script>");
            writer.println("</body></html>");
        }
    }
}
  1. 轮询订单状态,实现支付完成后页面自动跳转
  • 由于支付回执是异步的,所以即使捕获到异步回执也无法实现支付页面的自动跳转
  • 所以需要在支付页面打开时设置一个ajax轮询订单状态,一旦订单状态更新,则进行页面跳转
// 页面关闭
$(“.modal-header button.close:last”).click(function () {
    // 停止轮询
    clearInterval(checkTimer);
});

// 轮询订单状态
checkTimer = setInterval(function () {
    // 窗口是否打开
    if ($(“.order-pay-panel”).length <= 0) {
        clearInterval(checkTimer);
    }

    // 获取订单状态
    $.ts.doAction(“/api/order/review/check/“, {
        orderId: orderId
    }, function () {
        // 订单已支付
        if (!this.data) {
            $.ts.closeWindow();

            g_index.loadMainContentWithState(“/order/manage”);

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

推荐阅读更多精彩内容