首页 > 上网技巧 > 电脑小技巧 > Java网站微信扫码支付功能实现以及回调教程

Java网站微信扫码支付功能实现以及回调教程

时间:2018-07-10 09:26 作者:QQ地带 我要评论

最近在项目中需要实现支付功能,所以在需要在网站中接入微信和支付宝。趁着周末的时间,记录一下本周的收获。
 
我使用的微信支付是V3的版本
扫码支付使用的是模式二
项目使用的是spring+spring mvc + mybatis
 
微信支付流程
网站通过调用微信接口,获取到所要支付商品的二维码的链接code_url,然后输出到网页端显示
 
提示用户打开手机端微信进行扫码
 
扫码完成进入支付页面,当用户付款后,微信会回调服务器接口,并带着支付状态信息等
 
通过返回的信息,就可以判断用户是否支付成功,并执行自己的业务逻辑
 
微信业务流程时序图
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。
 
具体开发
微信支付需要在微信公众平台以及微信商户平台等申请到相应的权限,因为我这块不是我弄的,所以就不具体的说了。
 
接入前准备
在接入接口之前,我们还需要在微信公众平台中得到自己的账号信息,也就是请求时身份验证。
 
邮件中参数 API参数名 详细说明
APPID appid appid是微信公众账号或开放平台APP的唯一标识,在公众平台申请公众账号或者在开放平台申请APP账号后,微信会自动分配对应的appid,用于标识该应用。可在微信公众平台–>开发者中心查看,商户的微信支付审核通过邮件中也会包含该字段值。
微信支付商户号 mch_id 商户申请微信支付后,由微信支付分配的商户收款账号。
API密钥 key 交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按一下路径设置:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
Appsecret secret AppSecret是APPID对应的接口密码,用于获取接口调用凭证access_token时使用。在微信支付中,先通过OAuth2.0接口获取用户openid,此openid用于微信内网页支付模式下单接口使用。在开发模式中获取AppSecret(成为开发者且帐号没有异常状态)。
同时,我们还需要在微信公众平台设置测试授权目录
 
到这里,我们就可以开始准备接入微信接口了。
 
接入微信接口
获取微信支付二维码:
 
/**
 * 微信扫码支付-模式二
 * @author Homiss
 * @version 1.0, 2015/12/16
 */
 
@RequestMapping(value = "/config/weixinPay_notify")
    public static ModelAndView wechatQrcodePay(HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
        // 账号信息
        String appid = "wx135*********4f0d";  // appid
        String appsecret = "1350cf*******************040dac"; // appsecret
        String mch_id = "12******01"; // 商业号
        String key = "1350c********************0040dac"; // key
        String currTime = TenpayUtil.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = TenpayUtil.buildRandom(4) + "";
        String nonce_str = strTime + strRandom;
        String order_price = "1"; // 价格   注意:价格的单位是分!!!不能为分数!!!!
        String body = "Homiss";   // 商品名称
        String out_trade_no = "9527"; // 订单号
        // 获取发起电脑 ip
        String spbill_create_ip = request.getRemoteAddr();
        // spbill_create_ip = "可能因为我的是内网,获取不到ip地址,所以我自己写的";
        
        // 回调接口 
        String notify_url = DOMAIN_TEST + "/config/weixinPay_result";
        String trade_type = "NATIVE";
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid", appid);
        packageParams.put("mch_id", mch_id);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", body);
        packageParams.put("out_trade_no", out_trade_no);
        packageParams.put("total_fee", order_price);
        packageParams.put("spbill_create_ip", spbill_create_ip);
        packageParams.put("notify_url", notify_url);
        packageParams.put("trade_type", trade_type);
        RequestHandler requestHandler = new RequestHandler(request, response);
        requestHandler.init(appid, appsecret, key);
        String sign = requestHandler.createSign(packageParams);
        String xml="<xml>"+
                "<appid>"+appid+"</appid>"+
                "<mch_id>"+mch_id+"</mch_id>"+
                "<nonce_str>"+nonce_str+"</nonce_str>"+
                "<sign>"+sign+"</sign>"+
                "<body><![CDATA["+body+"]]></body>"+
                "<out_trade_no>"+out_trade_no+"</out_trade_no>"+
                "<total_fee>"+order_price+"</total_fee>"+
                "<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
                "<notify_url>"+notify_url+"</notify_url>"+
                "<trade_type>"+trade_type+"</trade_type>"+
                "</xml>";
        String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String code_url ;
        // 获取返回的code_url值
        code_url = new GetWxOrderno().getUrlCode(createOrderURL, xml);
        if(code_url.equals("")){
            logger.debug("code_url error");
        }
        ModelAndView model = new ModelAndView("/wldm/common/pay/wechat");
        
        model.addObject("code_url", code_url);
        return model;
    }
然后我们将获取到的code_url在页面上展示出来:
 
 
<img class="qcode" src="qr_code.img?code_url=${code_url}"/>
 
import com.zghm.wldm.util.QRCodeUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
/**
 * @author Homiss
 * @version 1.0, 2015/12/18
 */
@Controller
public class QrCodeImage {
    @RequestMapping("/qr_code.img")
    @ResponseBody
    public void getQrCode(String code_url, HttpServletResponse response) throws Exception {
        // String qrCode = QRCodeUtil.encode("qrCode", qrCodePath);
        ServletOutputStream sos = response.getOutputStream();
        QRCodeUtil.encode(code_url, sos);
    }
}
不出意外的话,这里我们就可以使用自己手机端扫码支付了。
 
回调
支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
 
特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
 
/**
 * 微信回调接口
 * @author Homiss
 * @version 1.0, 2015/12/16
 */
@RequestMapping(value = "/config/weixinPay_result")
public void wechatOrderBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //---------------------------------------------------------
    //财付通支付通知(后台通知)
    //---------------------------------------------------------
    //商户号
    String partner = "需要自己填写";
    //密钥
    String key = "需要自己填写";
    //创建支付应答对象
    ResponseHandler resHandler = new ResponseHandler(request, response);
    resHandler.setKey(key);
    //判断签名是否正确
    if(resHandler.isTenpaySign()) {
        //------------------------------
        //处理业务开始
        //------------------------------
        String resXml = "";
        if("SUCCESS".equals(resHandler.getParameter("result_code"))){
            // 这里是支付成功
            //////////执行自己的业务逻辑////////////////
            
            
            //////////执行自己的业务逻辑////////////////
            // 同步返回给微信参数
            // resHandler.sendToCFT("SUCCESS");
            //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
            resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                    + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
        } else {
            System.out.println("支付失败,错误信息:" + resHandler.getParameter("err_code"));
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        //------------------------------
        //处理业务完毕
        //------------------------------
        BufferedOutputStream out = new BufferedOutputStream(
                response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    } else{
        System.out.println("通知签名验证失败");
    }
}
在做签名认证时,Google和百度了很多的教程,但是都没有发现有谁做了签名认证,只能参考其他地方的代码, 囧~
 
签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
 
/**
 * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
 * @return boolean
 */
public boolean isTenpaySign() {
StringBuffer sb = new StringBuffer();
Set es = this.parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + this.getKey());
//算出摘要
String enc = TenpayUtil.getCharacterEncoding(this.request, this.response);
String sign = MD5Util.MD5Encode(sb.toString(), enc).toLowerCase();
String tenpaySign = this.getParameter("sign").toLowerCase();
//debug信息
this.setDebugInfo(sb.toString() + " => sign:" + sign +
" tenpaySign:" + tenpaySign);
System.out.println(tenpaySign + "    " + sign);
return tenpaySign.equals(sign);
}
退款
当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
注意:
1、交易时间超过一年的订单无法提交退款;
2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交,要采用原来的退款单号。总退款金额不能超过用户实际支付金额。
 
是否需要证书
请求需要双向证书。(需要在微信平台上下载
 
 
import com.zghm.wldm.entity.ResultEntity;
import com.zghm.wldm.third.wechat.utils.GetWxOrderno;
import com.zghm.wldm.third.wechat.utils.RequestHandler;
import com.zghm.wldm.third.wechat.utils.TenpayUtil;
import org.apache.log4j.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
 * wldm
 * 微信退款
 *
 * @author Homiss
 * @version 1.0, 2015/12/21
 */
public class WechatRefundController {
    private static final Logger logger = Logger.getLogger(WechatRefundController.class);
    public static ResultEntity wechatRefund(HttpServletResponse response, HttpServletRequest request) throws UnsupportedEncodingException {
        // 返回信息
        ResultEntity result = new ResultEntity();
        // 账号信息
        String appid = "wx13*******0d";  // appid
        String appsecret = "1350c************0040dac"; // appsecret
        String mch_id = "1******201"; // 商业号
        String key = "1350c************90040dac"; // key
        String currTime = TenpayUtil.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = TenpayUtil.buildRandom(4) + "";
        String nonce_str = strTime + strRandom;
        SortedMap<String, String> parameters = new TreeMap<String, String>();
        parameters.put("appid", appid);
        parameters.put("mch_id", mch_id);
        parameters.put("nonce_str", nonce_str);
        // 在notify_url中解析微信返回的信息获取到 transaction_id,此项不是必填,详细请看上图文档
        // parameters.put("transaction_id", "微信支付订单中调用统一接口后微信返回的 transaction_id");
        parameters.put("out_trade_no", "9527");
        parameters.put("out_refund_no", "9527");                              //我们自己设定的退款申请号,约束为UK
        parameters.put("total_fee", "1");          //单位为分
        parameters.put("refund_fee", "1");              //单位为分
        // 操作员帐号, 默认为商户号
        parameters.put("op_user_id", mch_id);
        RequestHandler requestHandler = new RequestHandler(request, response);
        requestHandler.init(appid, appsecret, key);
        String sign = requestHandler.createSign(parameters);
        String createOrderURL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        String xml = "<xml>"
                + "<appid><![CDATA[" + appid + "]]></appid>"
                + "<mch_id><![CDATA[" + mch_id +"]]></mch_id>"
                + "<nonce_str><![CDATA[" + nonce_str + "]]></nonce_str>"
                + "<out_trade_no><![CDATA[" + out_trade_no + "]]></out_trade_no>"
                + "<out_refund_no><![CDATA[" + out_refund_no + "]]></out_refund_no>"
                + "<total_fee><![CDATA[" + total_fee + "]]></total_fee>"
                + "<refund_fee><![CDATA[" + refund_fee + "]]></refund_fee>"
                + "<op_user_id><![CDATA[" + mch_id + "]]></op_user_id>"
                + "<sign>" + sign + "</sign>"
                + "</xml>";
        try {
            Map map = GetWxOrderno.forRefund(createOrderURL, xml);
            if(map != null){
                String return_code = (String) map.get("return_code");
                String result_code = (String) map.get("result_code");
                if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
                    // 退款成功
                    result.setMsg("退款成功");
                    result.setFlag(1);
                } else {
                    result.setMsg("退款失败");
                }
            } else {
                result.setMsg("退款失败");
            }
        } catch (Exception e) {
            System.out.print("退款失败");
            e.printStackTrace();
        }
        return result;
    }
}
现在只做了这几个功能,以后有机会的话会将剩下的补上。

标签: 微信支付
顶一下
(0)
0%
踩一下
(0)
0%

Google提供的广告