微信支付(app支付--apicloud)

微信支付(app支付--apicloud)

编写原因

最近做一个APP充值的功能 ,用到了微信支付,然后就研究了一下,遇到了好多坑,解决也是浪费了我好长时间,在这吐槽下微信的官方文档,哎,真正理解 最重要, 希望这篇文章能够帮助到用到微信支付的伙伴们!

首先附上微信的官方文档,虽然说写的不是很好,但是也不得不看,而且要仔细的看。
微信官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

具体实现步骤

  1. 我是用的框架是MVC的框架,springboot也是相同的道理,只是穿的衣服不通,人都是一样的,也就是道理都是一样的,介绍不是特别详细,但是重要的每一行代码上都有相应的注释,这是程序员必备的好习惯。

  2. 把一些固定的配置最好存放在配置文件中,到时候发生更改,直接修改配置文件就OK了,上代码,哈哈

#微信APP:appId
wx.appIdApp=appid
#APP 商户号
wx.mchIdApp=申请的商户号
#支付签名
wx.paySignKeyApp=商户平台有
#交易类型
wx.tradeTypeApp=APP
  1. 读取配置文件
package com.platform.utils;

import java.io.UnsupportedEncodingException;
import java.util.ResourceBundle;

/**
 * 名称:ResourceUtil <br>
 * 描述:参数工具类<br>
 *
 */
public class ResourceUtil {
    private static ResourceUtil RESOURCE_UTIL = null;

    private static ResourceBundle BUNDLE = java.util.ResourceBundle.getBundle("platform");

    private ResourceUtil() {

    }

    /**
     * 工厂实现配置文件读取
     *
     * @param properties 参数
     * @return ResourceUtil 工具类
     */
    public static ResourceUtil getInstance(String properties) {
        if (RESOURCE_UTIL == null) {
            RESOURCE_UTIL = new ResourceUtil();
        }
        if (properties != null) {
            BUNDLE = java.util.ResourceBundle.getBundle(properties);
        }
        return RESOURCE_UTIL;
    }

    /**
     * 工厂实现配置文件读取
     *
     * @return ResourceUtil
     */
    public static ResourceUtil getInstance() {
        if (RESOURCE_UTIL == null) {
            RESOURCE_UTIL = new ResourceUtil();
        }
        return RESOURCE_UTIL;
    }

    /**
     * 主要功能:获取配置文件参数
     * 注意事项:无
     *
     * @param name 参数名称
     * @return 参数名称对应值
     */
    public static String getConfigByName(String name) {
        String value = "";
        try {
            value = new String(BUNDLE.getString(name).getBytes("iso8859-1"), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * 主要功能:取得分隔符
     * 注意事项:无
     *
     * @return 分隔符
     */
    public static String getSeparator() {
        return System.getProperty("file.separator");
    }

}

  1. 微信统一下单,这是最坑的,我要分步详细介绍,希望可以帮助到小伙伴们!
  • 将微信官方文档中所需的所有必填参数存到map中;
           Map<Object, Object> parame = new TreeMap<Object, Object>();
           //自定义的订单编号(保证不发生重复就可以)
           String orderId=generating(merFlowStatement.getMobile());
           //随机字符串(32位)
           String nonceStr = CharUtil.getRandomString(32);
           //appid
           parame.put("appid", ResourceUtil.getConfigByName("wx.appIdApp"));
            // 商家账号
            parame.put("mch_id", ResourceUtil.getConfigByName("wx.mchIdApp"));
            String randomStr = CharUtil.getRandomNum(18).toUpperCase();
            // 商品描述
            parame.put("body", "商户-充值");
            // 随机字符串
            parame.put("nonce_str", randomStr);
            // 商户订单编号
            parame.put("out_trade_no", orderId);
            //支付金额
            parame.put("total_fee",merFlowStatement.getCashMoney().multiply(new BigDecimal(100)).intValue());
            //交易类型APP
            parame.put("trade_type", ResourceUtil.getConfigByName("wx.tradeTypeApp"));
            parame.put("spbill_create_ip", getClientIp());
            // 回调地址
            //parame.put("notify_url", ResourceUtil.getConfigByName("wx.notifyUrl"));
            parame.put("notify_url", "https://platform.ln1788.com/platform/api/merflowstatement/weChatCallback");
            String sign = WechatUtil.arraySign(parame, ResourceUtil.getConfigByName("wx.paySignKeyApp"));
            // 数字签证
            parame.put("sign", sign);

  • 附上生成订单编号的代码
 public String generating(String str) {
 	   //当前时间
 	   SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
 		Date date = new Date();
 		String time = sdf.format(date);
 		//手机号后四位
 		String phone=str.substring(str.length()-4, str.length());
 		//四位随机数
 		int num=(int) (Math.random()*9000+1000);
 		String number="CZO"+time+phone+num;
     	return number;
     }

-向微信发送请求(统一下单接口)

   //将map的类型转化成xml(微信需要的就是xml的参数)
   String xml = MapUtils.convertMap2Xml(parame);
   logger.info("xml:" + xml);
   //发送微信统一下单请求
   Map<String, Object> resultUn =XmlUtil.xmlStrToMap(WechatUtil.requestOnce(ResourceUtil.getConfigByName("wx.uniformorder"), xml));
   // 响应报文
   String return_code = MapUtils.getString("return_code", resultUn);
   String return_msg = MapUtils.getString("return_msg", resultUn);
  • 判断预支付成功与失败,并将与支付订单号返回前端,前端进行支付操作。需注意非常坑的地方来了,一定办微信支付所需的参数写对写全,要不前端就会一直报错。这块一定注意!
    if (return_code.equalsIgnoreCase("FAIL")) {
            	resultObj= toResponsFail("支付失败," + return_msg);
            } else if (return_code.equalsIgnoreCase("SUCCESS")) {
                // 返回数据
                String result_code = MapUtils.getString("result_code", resultUn);
                String err_code_des = MapUtils.getString("err_code_des", resultUn);
                if (result_code.equalsIgnoreCase("FAIL")) {
                	resultObj=toResponsFail("支付失败," + err_code_des);
                } else if (result_code.equalsIgnoreCase("SUCCESS")) {
                 //成功执行数据返回前端,以下详细介绍
                 }
  • 千万记住下图中这些参数一定不能错,一定不能落下,这坑我一天;还有就是签名,这些一定要都参与进签名;

一定看好官方文档,并不是下图中这几个参数,人家是举例,一定要把上图中所有参数参与签名;
在这里插入图片描述

  • 列出预下单之后发起支付的签名参数,及签名,接上述代码;
       String prepay_id = MapUtils.getString("prepay_id", resultUn);
                 // 先生成paySign 参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
                    resultObjOne.put("appid", ResourceUtil.getConfigByName("wx.appIdApp"));
                    resultObjOne.put("prepayid", prepay_id);
                    resultObjOne.put("partnerid", MapUtils.getString("mch_id", resultUn));
                    resultObjOne.put("noncestr", nonceStr);
                    String timestamp = String.valueOf(new Date().getTime()/1000); 
                    resultObjOne.put("timestamp", Integer.valueOf(timestamp));
                    resultObjOne.put("package", "Sign=WXPay");
                    String paySign = WechatUtil.arraySign(resultObjOne, ResourceUtil.getConfigByName("wx.paySignKeyApp"));
                    resultObjOne.put("sign",paySign);
                    /**
                    *操作自己的数据库生成订单流水
                    */
                     //微信订单号
                    merFlowStatement.setReserveOne(prepay_id);
                     //类型为充值
                    merFlowStatement.setType(1);
                    SysMacroEntity entity=macroDao.queryObject(20);
                    BigDecimal bigDecimal=new BigDecimal( entity.getValue());
                    BigDecimal money=(merFlowStatement.getCashMoney()).multiply(bigDecimal);
                    merFlowStatement.setCashMoney(money);
                     //付款状态为审核中
                    merFlowStatement.setCashStatus(1);
                    merFlowStatement.setOrderNumber(orderId);
                    merFlowStatementMapper.save(merFlowStatement);
                    resultObj=toResponsObject(0, "微信统一订单下单成功", resultObjOne);
  • 签名工具类
package com.platform.util.wechat;

import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.platform.utils.CharUtil;
import com.platform.utils.MapUtils;
import com.platform.utils.ResourceUtil;
import com.platform.utils.XmlUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * <p>Title: 微信工具类</p>
 * <p>Description: 微信工具类,通过充值客户端的不同初始化不同的工具类,得到相应微信退款相关的appid和muchid</p>
 *
 * @author xubo
 * @date 2017年6月6日  下午5:05:03
 */
public class WechatUtil {
    private static Log logger = LogFactory.getLog(WechatUtil.class);
    /**
     * 充值客户端类型--微信公众号
     */
    public static Integer CLIENTTYPE_WX = 2;
    /**
     * 充值客户端类型--app
     */
    public static Integer CLIENTTYPE_APP = 1;
   
   
    /**
     * 方法描述:根据签名加密请求参数
     * 创建时间:2017年6月8日  上午11:28:52
     * 作者: xubo
     *
     * @param
     * @return
     */
    public static String arraySign(Map<Object, Object> params, String paySignKey) {
        boolean encode = false;
        Set<Object> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuffer temp = new StringBuffer();
        boolean first = true;
        for (Object key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            Object value = params.get(key);
            String valueString = "";
            if (null != value) {
                valueString = value.toString();
            }
            if (encode) {
                try {
                    temp.append(URLEncoder.encode(valueString, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            } else {
                temp.append(valueString);
            }
        }
        temp.append("&key=");
        temp.append(paySignKey);
        System.out.println(temp.toString());
        String packageSign = MD5.getMessageDigest(temp.toString());
        return packageSign;
    }

    /**
     * 请求,只请求一次,不做重试
     *
     * @param url
     * @param data
     * @return
     * @throws Exception
     */
    public static String requestOnce(final String url, String data) throws Exception {
        BasicHttpClientConnectionManager connManager;
        connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
                        .build(),
                null,
                null,
                null
        );

        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(5000)
                .setConnectTimeout(5000)
                .setConnectionRequestTimeout(10000).build();

        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(data, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + ResourceUtil.getConfigByName("wx.mchId"));
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        String reusltObj = EntityUtils.toString(httpEntity, "UTF-8");
        logger.info("请求结果:" + reusltObj);
        return reusltObj;

    }
}
  • xml工具类
package com.platform.utils;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * xml相关的工具类
 *
 * @author yang.y
 */
@SuppressWarnings("unchecked")
public class XmlUtil {

    /**
     * xml字符串转换成bean对象
     *
     * @param xmlStr xml字符串
     * @param clazz  待转换的class
     * @return 转换后的对象
     */
    public static Object xmlStrToBean(String xmlStr, Class clazz) {
        Object obj = null;
        try {
            // 将xml格式的数据转换成Map对象
            Map<String, Object> map = xmlStrToMap(xmlStr);
            // 将map对象的数据转换成Bean对象
            obj = mapToBean(map, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 将xml格式的字符串转换成Map对象
     *
     * @param xmlStr xml格式的字符串
     * @return Map对象
     * @throws Exception 异常
     */
    public static Map<String, Object> xmlStrToMap(String xmlStr) throws Exception {
        if (StringUtils.isNullOrEmpty(xmlStr)) {
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        // 将xml格式的字符串转换成Document对象
        Document doc = DocumentHelper.parseText(xmlStr);
        // 获取根节点
        Element root = doc.getRootElement();
        // 获取根节点下的所有元素
        List children = root.elements();
        // 循环所有子元素
        if (children != null && children.size() > 0) {
            for (int i = 0; i < children.size(); i++) {
                Element child = (Element) children.get(i);
                map.put(child.getName(), child.getTextTrim());
            }
        }
        return map;
    }

    /**
     * 将xml格式字符串转换成Bean对象
     * 多级子节点递归遍历
     *
     * @param xmlStr
     * @param clazz
     * @return
     * @throws Exception
     */
    public static Object xmlStrToJavaBean(String xmlStr, Class clazz) {
        if (StringUtils.isNullOrEmpty(xmlStr)) {
            return null;
        }
        Object obj = null;
        Map<String, Object> map = new HashMap<String, Object>();
        // 将xml格式的字符串转换成Document对象
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);

            // 获取根节点
            Element root = doc.getRootElement();
            map = elementToMap(root, map);
            // 将map对象的数据转换成Bean对象
            obj = mapToBean(map, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 递归遍历xml子节点,转换Map
     *
     * @param element
     * @param map
     * @return
     */
    public static Map<String, Object> elementToMap(Element element, Map<String, Object> map) {
        if (element == null || map == null)
            return null;
        List children = element.elements();
        if (children != null && children.size() > 0) {
            for (int i = 0; i < children.size(); i++) {
                Element child = (Element) children.get(i);
                if (child.elements() != null && child.elements().size() > 0)
                    elementToMap(child, map);
                else
                    map.put(child.getName(), child.getTextTrim());
            }
        }
        return map;
    }

    /**
     * 将Map对象通过反射机制转换成Bean对象
     *
     * @param map   存放数据的map对象
     * @param clazz 待转换的class
     * @return 转换后的Bean对象
     * @throws Exception 异常
     */
    public static Object mapToBean(Map<String, Object> map, Class clazz) throws Exception {
        Object obj = clazz.newInstance();
        if (map != null && map.size() > 0) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String propertyName = entry.getKey();
                Object value = entry.getValue();
                String setMethodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                Field field = getClassField(clazz, propertyName);
                if (field != null) {
                    Class fieldTypeClass = field.getType();
                    value = convertValType(value, fieldTypeClass);
                    clazz.getMethod(setMethodName, field.getType()).invoke(obj, value);
                }
            }
        }
        return obj;
    }

    /**
     * 将Object类型的值,转换成bean对象属性里对应的类型值
     *
     * @param value          Object对象值
     * @param fieldTypeClass 属性的类型
     * @return 转换后的值
     */
    private static Object convertValType(Object value, Class fieldTypeClass) {
        Object retVal = null;
        if (Long.class.getName().equals(fieldTypeClass.getName())
                || long.class.getName().equals(fieldTypeClass.getName())) {
            retVal = Long.parseLong(value.toString());
        } else if (Integer.class.getName().equals(fieldTypeClass.getName())
                || int.class.getName().equals(fieldTypeClass.getName())) {
            retVal = Integer.parseInt(value.toString());
        } else if (Float.class.getName().equals(fieldTypeClass.getName())
                || float.class.getName().equals(fieldTypeClass.getName())) {
            retVal = Float.parseFloat(value.toString());
        } else if (Double.class.getName().equals(fieldTypeClass.getName())
                || double.class.getName().equals(fieldTypeClass.getName())) {
            retVal = Double.parseDouble(value.toString());
        } else {
            retVal = value;
        }
        return retVal;
    }

    /**
     * 获取指定字段名称查找在class中的对应的Field对象(包括查找父类)
     *
     * @param clazz     指定的class
     * @param fieldName 字段名称
     * @return Field对象
     */
    private static Field getClassField(Class clazz, String fieldName) {
        if (Object.class.getName().equals(clazz.getName())) {
            return null;
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getName().equals(fieldName)) {
                return field;
            }
        }

        Class superClass = clazz.getSuperclass();
        if (superClass != null) {// 简单的递归一下
            return getClassField(superClass, fieldName);
        }
        return null;
    }
}

  • 微信回调接口
 @IgnoreAuth
    @ApiOperation(value = "微信订单回调接口")
    @RequestMapping(value = "/weChatCallback")
    @ResponseBody
    public void weChatCallback(HttpServletRequest request, HttpServletResponse response) throws IOException {
       try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            response.setHeader("Access-Control-Allow-Origin", "*");
            InputStream in = request.getInputStream();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            out.close();
            in.close();
            //xml数据
            String reponseXml = new String(out.toByteArray(), "utf-8");
            System.out.println(reponseXml);
            WechatRefundApiResult result = (WechatRefundApiResult) XmlUtil.xmlStrToBean(reponseXml, WechatRefundApiResult.class);
            String result_code = result.getResult_code();
            if (result_code.equalsIgnoreCase("FAIL")) {
                //订单编号
                String out_trade_no = result.getOut_trade_no();
                logger.error("订单" + out_trade_no + "充值失败");
                ApiMerFlowStatementEntity entity=new ApiMerFlowStatementEntity();
                entity.setOrderNumber(out_trade_no);
                ApiMerFlowStatementEntity merFlowStatementEntity=apiMerFlowStatementService.queryObjectByOrderNumber(entity);
                //查询账户信息
                ApiMerAccountEntity accountEntity= accountService.queryObject(merFlowStatementEntity.getAccountId());
                /**
                   * 充值失败逻辑
                 */
               
                response.getWriter().write(setXml("SUCCESS", "OK"));
            } else if (result_code.equalsIgnoreCase("SUCCESS")) {
                //订单编号
                String out_trade_no = result.getOut_trade_no();
                logger.error("订单" + out_trade_no + "充值成功");
                ApiMerFlowStatementEntity entity=new ApiMerFlowStatementEntity();
                entity.setOrderNumber(out_trade_no);
                ApiMerFlowStatementEntity merFlowStatementEntity=apiMerFlowStatementService.queryObjectByOrderNumber(entity);
                //查询账户信息
                ApiMerAccountEntity accountEntity= accountService.queryObject(merFlowStatementEntity.getAccountId());
                /**
                 * 充值成功逻辑
                 */
              
                }
                response.getWriter().write(setXml("SUCCESS", "OK"));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        
    }

大功告成了

如果还有问题的宝宝们,可以和我进行交流哦,对于apicloud前端的代码暂时没有,我负责后台开发的,如果你遇到问题也可以和我沟通,我们一起解决,希望正在研究微信支付和将来会研究微信支付的小伙伴,能够帮助到你们,加油哦!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值