JAVA实现的微信扫描二维码支付

吐槽一下

支付项目采用springMvc+Dubbo架构实现,只对外提供接口。

话说,为什么微信支付比支付宝来的晚了那么一点,一句话,那一阵挺忙的,然后就没有时间整理,最近做完支付宝支付,顺便也把微信支付的也整理一下。

这里再吐槽一下,微信支付的DEMO基本为零,很多代码都是从网上查找的(也可能我么有仔细找API)。

前期酝酿准备

扫码支付,目前来说个人是不可以申请的,包括现在支付宝的即时到帐个人相关业务也取消了。所以这里必须有一个微信支付商户平台,具体怎么申请的,我也不清楚,只是拿来用的。

商户平台是要配合绑定微信公众账号使用的,具体操作申请下来已经绑定了,这里你也只管用就是了。

什么是扫码支付?

场景介绍

用户扫描商户展示在各种场景的二维码进行支付。

步骤1:商户根据微信支付的规则,为不同商品生成不同的二维码(如图6.1),展示在各种场景,用于用户扫描购买。

步骤2:用户使用微信“扫一扫”(如图6.2)扫描二维码后,获取商品支付信息,引导用户完成支付(如图6.3)。

支付二维码 图6.1 支付二维码 打开微信扫一扫二维码 图6.2 打开微信扫一扫二维码 确认支付页面 图6.3 确认支付页面

步骤(3):用户确认支付,输入支付密码(如图6.4)。

步骤(4):支付完成后会提示用户支付成功(如图6.5),商户后台得到支付成功的通知,然后进行发货处理。

用户确认支付,输入密码 图6.4 用户确认支付,输入密码 支付成功提示 图6.5 支付成功提示

如何集成到项目中去?

ConfigUtil参数配置:

import java.util.Map;
import java.util.ResourceBundle;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 相关配置参数
 * 创建者    张志朋
 * 创建时间    2016年9月28日
 *
 */
public class ConfigUtil {
    /**
     * 服务号相关信息
     */
     public final static String APP_ID = "2016";//服务号的应用ID
     public final static String APP_SECRET = "2016";//服务号的应用密钥
     public final static String TOKEN = "weixinCourse";//服务号的配置token
     public final static String MCH_ID = "2016";//商户号
     public final static String API_KEY = "2016";//API密钥
     public final static String SIGN_TYPE = "MD5";//签名加密方式
     public final static String CERT_PATH = "apiclient_cert.p12";//微信支付证书存放路径地址
     static ResourceBundle resource = ResourceBundle.getBundle("config");
     //微信支付统一接口的回调action
     public final static String NOTIFY_URL = resource.getString("WEIXIN_NOTIFY_URL");
    /**
     * 微信基础接口地址
     */
     //获取token接口(GET)
     public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
     //oauth2授权接口(GET)
     public final static String OAUTH2_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
     //刷新access_token接口(GET)
     public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
    // 菜单创建接口(POST)
     public final static String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    // 菜单查询(GET)
     public final static String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
    // 菜单删除(GET)
    public final static String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
    /**
     * 微信支付接口地址
     */
    //微信支付统一接口(POST)
    public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    //微信退款接口(POST)
    public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    //订单查询接口(POST)
    public final static String CHECK_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
    //关闭订单接口(POST)
    public final static String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
    //退款查询接口(POST)
    public final static String CHECK_REFUND_URL = "https://api.mch.weixin.qq.com/pay/refundquery";
    //对账单接口(POST)
    public final static String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
    //短链接转换接口(POST)
    public final static String SHORT_URL = "https://api.mch.weixin.qq.com/tools/shorturl";
    //接口调用上报接口(POST)
    public final static String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report";
    
    public static void commonParams(SortedMap<Object, Object> packageParams){
        // 账号信息
        String appid = ConfigUtil.APP_ID; // appid
        String mch_id = ConfigUtil.MCH_ID; // 商业号
        // 生成随机字符串
        String currTime = PayCommonUtil.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = PayCommonUtil.buildRandom(4) + "";
        String nonce_str = strTime + strRandom;

        packageParams.put("appid", appid);// 公众账号ID
        packageParams.put("mch_id", mch_id);// 商户号
        packageParams.put("nonce_str", nonce_str);// 随机字符串
    }
    /**
     * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),减小二维码数据量,提升扫描速度和精确度。
     * @Author    张志朋
     * @param urlCode  void
     * @Date    2016年10月26日
     * 更新日志
     * 2016年10月26日 张志朋  首次创建
     *
     */
    @SuppressWarnings("rawtypes")
    public static void shorturl(String urlCode){
        try {
            String key = ConfigUtil.API_KEY; // key
            SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
            ConfigUtil.commonParams(packageParams);
            packageParams.put("long_url", urlCode);// URL链接
            String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
            packageParams.put("sign", sign);// 签名
            String requestXML = PayCommonUtil.getRequestXml(packageParams);
            String resXml = HttpUtil.postData(ConfigUtil.SHORT_URL, requestXML);
            Map map = XMLUtil.doXMLParse(resXml);
            String returnCode = (String) map.get("return_code");
            if("SUCCESS".equals(returnCode)){
                String resultCode = (String) map.get("return_code");
                if("SUCCESS".equals(resultCode)){
                    urlCode = (String) map.get("short_url");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

参数必填项 APP_ID 和APP_SECRET 是从微信公众号里面获取的,而MCH_ID和API_KEY是从商户平台获取的。CERT_PATH 证书可选,但是如果做退款接口必须要使用证书。NOTIFY_URL 为回调地址,自定义路径,但是一定要微信平台可以调用到你的url。

如何生成二维码订单?

API文档地址

文档有详细的参数说明,具体生成需要xml解析,这里就不放了,好多的说,有需要的可以留言。

支付回调:

/**
 * 二维码支付
 * 创建者    张志朋
 * 创建时间    2016年10月31日
 *
 */
@Controller
@RequestMapping(value = "weixin")
public class WeixinPayController {
    
    @Autowired
    private IWeixinPayService weixinpayBack;
    /**
     * 微信支付回调
     * @Author    张志朋
     * @param request
     * @param response
     * @throws Exception  void
     * @Date    2016年9月28日
     * 更新日志
     * 2016年9月28日 张志朋  首次创建
     *
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @RequestMapping(value = "pay")
    public void weixin_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        LogUtil.info("支付成功回调");
        // 读取参数
        InputStream inputStream = request.getInputStream();
        StringBuffer sb = new StringBuffer();
        String s;
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        while ((s = in.readLine()) != null) {
            sb.append(s);
        }
        in.close();
        inputStream.close();

        // 解析xml成map
        Map<String, String> m = new HashMap<String, String>();
        m = XMLUtil.doXMLParse(sb.toString());

        // 过滤空 设置 TreeMap
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        Iterator it = m.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = m.get(parameter);

            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        // 账号信息
        String key = ConfigUtil.API_KEY; // key
        // 判断签名是否正确
        if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
            // ------------------------------
            // 处理业务开始
            // ------------------------------
            String resXml = "";
            if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
                // 这里是支付成功
                String orderNo = (String) packageParams.get("out_trade_no");
                String attach = (String) packageParams.get("attach");
                //回调K12
                LogUtil.info(attach+"(订单号:"+orderNo+"付款成功)");
                // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                weixinpayBack.updateAccOrder(orderNo);
            } else {
                LogUtil.info("支付失败,错误信息:" + packageParams.get("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 {
            LogUtil.info("通知签名验证失败");
        }

    }
}

大体就这个样子,后续的可能就是安全优化了。涉及到钱可不是小问题。 相关阅读:

JAVA实现的支付宝扫描二维码支付 https://blog.52itstyle.com/archives/169/

支付宝扫码支付和微信扫码支付业务场景及问题记录 https://blog.52itstyle.com/archives/263/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android群英传

Google I/O 之 Android App Bundles 是个啥

47920
来自专栏实战docker

Java实战操作MongoDB集群(副本集)

Spring提供了MongoDB操作的工具:MongoTemplate,使得在Spring环境下对MongoDB的操作更为便利,本章我们就来学一下如何用Mong...

36290
来自专栏开源优测

RFC821 简单邮件传输协议(SMTP)

15230
来自专栏xdecode

开发安全规约

 所有可以通过web端访问到的页面, 都需要考虑是否加上权限控制. 包括各类service & action. 1 long userID = ...

23690
来自专栏阿杜的世界

Spring的容器内部事件发布自定义事件机制Spring 的容器内事件发布类结构应用场景

EventListener接口的作用仅仅在于“标记”,具体要提供哪些功能需要开发者自己定义,而且,还需要为自己定义的接口提供一个默认的实现类——只有接口的话什么...

10720
来自专栏菩提树下的杨过

Flash/Flex学习笔记(53):利用FMS快速创建一个文本聊天室

先来看客户端fla的构成: 第一帧:登录界面 ? 第一帧的代码: import flash.events.MouseEvent; import com.adob...

21190
来自专栏散尽浮华

Linux系统下的用户密码设定梳理

随着linux使用的普遍,对于linux用户以及系统的安全要求越来越高,而用户密码复杂程度是系统安全性高低的首要体现。因此如何对linux下用户的密码进行规则限...

33490
来自专栏*坤的Blog

公司web安全等级提升

公司的一个web数据展示系统,本来是内网的,而且是一个单独的主机,不存在远程控制的问题,所以之前并没有考虑一些安全相关的测试.但是国调安全检查的需要添加这样子的...

22540
来自专栏Java3y

从零开始写项目第二篇【登陆注册、聊天、收藏夹模块】

登陆模块目标 我要将其弄成类似的登陆,功能是要比较完善的。 ? 本来我是想做一步写一步的,但是发现这样文章就会太乱,因为要改的地方太多了。前面写过的,后边就被修...

1.2K80
来自专栏小樱的经验随笔

sqlmap注入分类

注入分法不同,种类不同,来个简单的分类: 1.get型:sqlmap -u “http://xxx.xx.xxx/xx.xxx?xx=xxx”  2.pos...

37250

扫码关注云+社区

领取腾讯云代金券