前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微信支付【代码篇】

微信支付【代码篇】

作者头像
简单的程序员
修改2020-05-23 10:56:59
2K0
修改2020-05-23 10:56:59
举报
文章被收录于专栏:奕仁专栏奕仁专栏

接上一篇,领导让我帮忙对接一下微信支付,接到文档之后我一脸懵逼,看了半天之后发现与银行对接大同小异,于是根据微信API要求进行了编码。

先贴上源码:微信支付Demo 本工程是用java8编写

注:必须要在微信小程序控制台申请APPID,KEY,商户号等

所用技术Maven 3.x,IDEA2017,Mysql5.7.x,SpringBoot 2.x,Lombok1.16.x

接下来贴一下maven代码:

代码语言:javascript
复制
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId> 
 </dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
</dependency> 
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.4</version>
</dependency> 
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency> 
<dependency>
	<groupId>dom4j</groupId>
	<artifactId>dom4j</artifactId>
</dependency>
<dependency>
	<groupId>xmlpull</groupId>
	<artifactId>xmlpull</artifactId>
	<version>1.1.3.1</version>
</dependency>
<dependency>
	<groupId>net.sf.kxml</groupId>
	<artifactId>kxml2</artifactId>
	<version>2.3.0</version>
</dependency>
<dependency>
	<groupId>com.thoughtworks.xstream</groupId>
	<artifactId>xstream</artifactId>
	<version>1.4.2</version>
</dependency> 
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.22</version>
</dependency>
<dependency>
	<groupId>commons-httpclient</groupId>
	<artifactId>commons-httpclient</artifactId>
	<version>3.1</version>
</dependency>
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.5.2</version>
</dependency> 

maven依赖加入完成之后,就开始进行项目的封装了

微信小程序支付API

先从统一下单开始:

在这里需要注意,生成的随机算法以及签名戳
这里我直接贴上代码:
代码语言:javascript
复制
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author ChoviWu
 * @date 2019/1/3
 * Description :
 */
public class OrderUtils {

	private static final AtomicInteger NUMBER = new AtomicInteger(1);

	private static final AtomicBoolean BOOLEAN = new AtomicBoolean(false);

	private static Random rnd = new Random();
	/**
	 * 随机串 生成签名
	 */
	public static final  String RANDOM_NONECE  = "abcdefghijklmnopqrstuvwxyz0123456789";
	/**
	 * 针对微信支付生成商户订单号,为了避免微信商户订单号重复(下单单位支付),
	 *
	 * @return
	 */
	public static String generateOrderSN() {
		StringBuffer orderSNBuffer = new StringBuffer();
		orderSNBuffer.append(System.currentTimeMillis());
		orderSNBuffer.append(getRandomStringByLength(7));
		return orderSNBuffer.toString();
	} 
	/**
	 * 生成随机数
	 * @param length
	 * @return
	 */
	public static String getRandomStringByLength(int length) {
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(RANDOM_NONECE.length());
			sb.append(RANDOM_NONECE.charAt(number));
		}
		return sb.toString();
	} 
}

’这个类为商户生成的32位随机串,这里就不介绍了

接下来是需要生成唯一的签名Sign(这里的算法我们用MD5加密),如果不严格进行加签,请求到微信会报签名错误

下面我来贴一下工具类: 因为要进行拼接,我利用反射对传入的字段进行取值并拼接,然后进行签名加密

代码语言:javascript
复制
import com.example.demo.core.annotation.Sign;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.*;

public class ReflectUtils {

	private static final Logger LOG = LoggerFactory.getLogger(ReflectUtils.class);
	
	public static <T> Map<String,Object> reflectBean(T bean){
		List<Field> list = ClassUtils.getClassFields(bean);
		if(CollectionUtils.isEmpty(list)){
			return null;
		}
		Map map = new HashMap(16);
		for (Field field : list){
			field.setAccessible(true);
			try {
				Object value = field.get(bean);
				map.put(field.getName(),value);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
				LOG.error("error map : {}",e);
				continue;
			}
		}
		LOG.info("获取Bean : {}",map);
		return map;
	}
	public static <T> T generatorBean(T bean) {
		List<Field> list = ClassUtils.getClassFields(bean);
		if (CollectionUtils.isEmpty(list)) {
			return null;
		}
		//ASCII字母排序C
		list.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
		//必有一个签名字段
		String type = Constants.MD5;
		//签名字段
		Field signField = null;
		//拼接的StringBuilder
		StringBuilder sb = new StringBuilder("");
		for (int i = 0; i < list.size(); i++) {
			Field field = list.get(i);
			field.setAccessible(true);
			try {
				Object value = field.get(bean);
				if (field.isAnnotationPresent(Sign.class)) {
     				//这里可能还需要定义一个忽略的字段,用来标记,但不传输
					Sign sign = field.getAnnotation(Sign.class);
					type = sign.type();
					signField = field;
				}
				// 签名类型
				if(Constants.SIGN_TYPE.equals(field.getName())){
					field.set(bean,type);
				}

				if (StringUtils.isEmpty(sb)) {
					sb.append(field.getName()).append("=").append(value);
				} else {
					//如果是拼接到最后一个元素,进行签名
					if (i == list.size() - 1) {
						try {
							if (!StringUtils.isEmpty(value)) {
								sb.append("&").append(field.getName()).append("=").append(value);
							}
							signField.set(bean, toSign(sb, type));
							break;
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						}
					}
					//过滤为null的value,不参与拼接
					if (StringUtils.isEmpty(value)) {
						continue;
					}
					sb.append("&").append(field.getName()).append("=").append(value);
				}
			} catch (IllegalAccessException e) {
				e.printStackTrace();
				LOG.error("error map : {}", e);
				continue;
			}

		}
		return bean;
}


	/**
	 * 生成签名
	 * MD5
	 *
	 * @param str
	 * @return
	 */
	public static String toSign(StringBuilder str, String signType) {
		StringBuilder sb = str.append("&key=").append(WxUtils.KEY);
		String sign = null;
		sign = new String(sb.toString());
		LOG.info("生成对象成功: {}", sign);
		if (Constants.MD5.equals(signType)) {
			return MD5Utils.MD5Encode(sign).toUpperCase();
		}
		return ShA256Utils.sha256_HMAC(sign, WxUtils.KEY).toUpperCase();
	}
}
`

上面的加签过程依赖了两个工具类,这里贴过来:

import com.yixuan.domain.wxpay.BaseRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class ClassUtils {

	/**
	 * 继承 反射获取字段
	 * @param bean
	 * @param <T>
	 * @return
	 */
	public static <T> List<Field> getClassFields(T bean){
		return sortField(bean.getClass(),new ArrayList<>());
	} 
	/**
	 * 递归获取字段
	 * @param tClass 轮询到的当前类
	 * @param list   字段list
	 * @return  返回字段的List
	 */
	private static <T> List<Field> sortField(Class<T> tClass,List<Field> list){
		if(StringUtils.isEmpty(tClass)){
			return list;
		}
		Field[] fields = tClass.getDeclaredFields();
		for (Field field : fields){
			list.add(field);
		}
		Class clazz = tClass.getSuperclass();
		if(clazz==null){
			return list;
		}
		return sortField(clazz.getSuperclass(),list);
	}
}

以及MD5加密的(网上有)

在这里注意,我在调试过程中总是出现签名错误,经调试,Md5加密与微信支付的加密后的不对,这里可以在微信官方进行调试微信公众平台支付接口调试工具

**注:在SHA256加密算法中,如果你的参数里有了中文等字符时,需要在加密前转为UTF-8,否则会 报 “签名错误” **

代码语言:javascript
复制
<xml>
	<return_code><![CDATA[FAIL]]></return_code>
	<return_msg><![CDATA[签名错误]]></return_msg>
</xml>

在这我先贴上SHA256工具类:

代码语言:javascript
复制
package com.yixuan.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class ShA256Utils {

    /**
     * 将加密后的字节数组转换成字符串
     *
     * @param b 字节数组
     * @return 字符串
     */
    private static String byteArrayToHexString(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; b!=null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }
    /**
     * sha256_HMAC加密
     * @param message 消息
     * @param secret  秘钥
     * @return 加密后字符串
     */
    public static String sha256_HMAC(String message, String secret) {
        String hash = "";
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
			//重点  : *这里需要进行UTF-8转码,为了防止中文字符匹配签名失败**
            byte[] bytes = sha256_HMAC.doFinal(message.getBytes("UTF-8"));
            hash = byteArrayToHexString(bytes);
            System.out.println(hash);
        } catch (Exception e) {
            System.out.println("Error HmacSHA256 ===========" + e.getMessage());
        }
        System.out.println("Sha256 生成结果:" + hash);
        return hash;
    }
}

import java.security.MessageDigest;

/**
 * @author ChoviWu
 * @date 2019/1/4
 * Description :
 */
public class MD5Utils {
	private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
			"8", "9", "a", "b", "c", "d", "e", "f"};

	/**
	 * 转换字节数组为16进制字串
	 * @param b 字节数组
	 * @return 16进制字串
	 */
	public static String byteArrayToHexString(byte[] b) {
		StringBuilder resultSb = new StringBuilder();
		for (byte aB : b) {
			resultSb.append(byteToHexString(aB));
		}
		return resultSb.toString();
	}

	/**
	 * 转换byte到16进制
	 * @param b 要转换的byte
	 * @return 16进制格式
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}

	/**
	 * MD5编码
	 * @param origin 原始字符串
	 * @return 经过MD5加密之后的结果
	 */
	public static String MD5Encode(String origin) {
		String resultString = null;
		try {
			resultString = origin;
			MessageDigest md = MessageDigest.getInstance("MD5");
			md.update(resultString.getBytes("UTF-8"));
			resultString = byteArrayToHexString(md.digest());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultString;
	}
}

到这一步,我们传入的对象基本已经可以拼接好并生成签名串了,由于微信所需要的是发送xml格式的,所以,我们需要对bean进行格式转化: 这里我就不贴代码了

在bean类加入注解是不够的,还需要在请求微信接口之前进行xml格式转化,这里我封装了一个工具类,如下:
代码语言:javascript
复制
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;

public class XmlUtils {

	private static final Logger LOG = LoggerFactory.getLogger(XmlUtils.class);

	public static String beanToXml(Object obj){
		XStream xstream = new XStream(new DomDriver("UTF-8"));
		// 识别obj类中的注解
		xstream.processAnnotations(obj.getClass());
		// 以格式化的方式输出XML
		String xml = xstream.toXML(obj);
		//在这里,xstream会把bean的单下划线转化为双下划綫,这里要进行替换字符串
		xml = xml.replace("__","_");
		LOG.info("请求参数 : \n" + xml);
		return xml;
	}
	//微信返回我们的数据,转化为bean类
	public static <T> T toBean(String xmlStr, Class<T> cls) {
		T t = null;
		try {
			JAXBContext context = JAXBContext.newInstance(cls);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			t = (T)unmarshaller.unmarshal(new StringReader(xmlStr));
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		return t;
	}

}

至此,一切都准备就绪了,我们可以请求微信的下单接口了:

代码语言:javascript
复制
 /**
     * 下单
     * @param unifiedOrder  自己封装的下单的bean类,与微信方保持一致
     * @return
     */
    public static UnifiedOrderReturn unifiedOrder(UnifiedOrder  unifiedOrder){
        System.out.println(JsonUtil.toJson(unifiedOrder));
		//http请求微信接口,这里的参数是上面我们封装的反射调用和转化xml的类
        String response = HttpUtil.sendHttpPostXml(UNIFIEDORDER,XmlUtils.beanToXml(ReflectUtils.appendToString(unifiedOrder)));
		//微信返回xml  我们对其进行转化
        UnifiedOrderReturn unifiedOrderReturn =XmlUtils.toBean(response,UnifiedOrderReturn.class);
		//处理逻辑
        if(Constants.FAIL.equals(unifiedOrderReturn.getReturn_code())){
            //TODO 失败
            System.out.println(JsonUtils.toJson(unifiedOrderReturn));
        }
        return unifiedOrderReturn;
    }
逻辑业务部分,我就不写了,一般都是现在本地插入记录,然后请求微信方,下单成功之后,下一步前端会调键盘唤醒支付,用户支付完成则微信端会异步到商户的系统(可见上篇的流程图)成功接口,商户进行修改订单状态。

至此,本篇文章结束,该部分只以下单为例,其余的也适用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 接下来贴一下maven代码:
    • 在这里需要注意,生成的随机算法以及签名戳
      • 这里我直接贴上代码:
        • 在bean类加入注解是不够的,还需要在请求微信接口之前进行xml格式转化,这里我封装了一个工具类,如下:
          • 逻辑业务部分,我就不写了,一般都是现在本地插入记录,然后请求微信方,下单成功之后,下一步前端会调键盘唤醒支付,用户支付完成则微信端会异步到商户的系统(可见上篇的流程图)成功接口,商户进行修改订单状态。
          相关产品与服务
          云开发 CloudBase
          云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档