微信支付之扫码支付与小程序支付

前言:最近的需求中,频繁出现微信支付功能的开发,于是研读了微信官方开发文档以及相关代码做了以下总结,并记录在此,以备不时之需。如有不足之处,欢迎批评指正。
微信官方开发文档

扫码支付

模式二:本文着重介绍扫码支付的模式二,其他情况以此类推,主要区别在统一下单前的步骤。

  1. 商户后台系统先调用微信支付的统一下单接口。
  2. 微信后台系统返回链接参数code_url。
  3. 商户后台系统将code_url值生成二维码图片。
  4. 用户使用微信客户端扫码后发起支付。

注意:code_url有效期为2小时,过期后扫码不能再发起支付

附上微信官方序列图,个人觉得还是挺通俗易懂的~

相关实体

WeChatPayInfo (微信支付实体)

代码示例

/**
* 微信支付信息实体类
*/
@Data
public class WeChatPayInfo {// 应用ID
private String appid;
// 商户号
private String mch_id;
// 终端设备号(门店号或收银设备ID),默认请传"WEB"
private String device_info = "WEB";
// 随机字符串
private String nonce_str;
// 签名,信息填充完整后使用工具类设置签名
private String sign;
// 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5
private String sign_type = "MD5";
/*** 商品描述交易字段格式根据不同的应用场景按照以下格式: APP——需传入应用市场上的APP名字-实际商品名称*/
private String body;
// 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
private String attach;
// 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一
private String out_trade_no;
// 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见
private String fee_type = "CNY";
// 订单总金额,单位为分
private int total_fee;
//订单详情,用于单个商品的优惠,设置成对象的json
private String detail;
// 用户端实际ip
private String spbill_create_ip;
// 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
private String notify_url;
// 交易类型
private String trade_type;
// 该字段用于统一下单时上报场景信息,目前支持上报实际门店信息,设置成对象的json
private String scene_info;
//微信公众号支付必填
private String openid;
//限制使用信用卡支付
private String limit_pay;
//二维码有效时间
private String time_expire;/*** 设置限制使用信用卡*/
public void configureLimitPay() {this.limit_pay = "no_credit";
}/*** 设置必填的自定义参数*/
public WeChatPayInfo(String body, String out_trade_no, String suffix,int total_fee, String trade_type, String spbill_create_ip) throws IOException {this.body = WeChatPayConfigurations.getAppName() + "-" + body;this.out_trade_no = out_trade_no;this.notify_url = WeChatPayConfigurations.getNotifyUrl(suffix);this.trade_type = trade_type;this.spbill_create_ip = spbill_create_ip;if (!WeChatPayConfigurations.getPayEnvironment()) {this.total_fee = 1;} else {this.total_fee = total_fee;}
}/***构造函数1- 设置必填的自定义参数*/
public WeChatPayInfo(String body, String out_trade_no,int total_fee, String notify_url,String trade_type, String spbill_create_ip) throws IOException {this.body = WeChatPayConfigurations.getAppName() + "-" + body;this.out_trade_no = out_trade_no;this.notify_url = notify_url;this.trade_type = trade_type;this.spbill_create_ip = spbill_create_ip;if (!WeChatPayConfigurations.getPayEnvironment()) {this.total_fee = 1;} else {this.total_fee = total_fee;}
}/***构造函数2- 设置必填的自定义参数*/
public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url,String trade_type, String spbill_create_ip, String openid) {this.body = body;this.out_trade_no = out_trade_no;if (!WeChatPayConfigurations.getPayEnvironment()) {this.total_fee = 1;} else {this.total_fee = total_fee;}this.spbill_create_ip = spbill_create_ip;this.notify_url = notify_url;this.trade_type = trade_type;this.openid = openid;
}/***构造函数3- 设置必填的自定义参数*/
public WeChatPayInfo(String body, String out_trade_no, int total_fee, String notify_url,String trade_type, String spbill_create_ip, String openid,String appid,String mch_id) {this.body = body;this.out_trade_no = out_trade_no;if (!WeChatPayConfigurations.getPayEnvironment()) {this.total_fee = 1;} else {this.total_fee = total_fee;}this.spbill_create_ip = spbill_create_ip;this.notify_url = notify_url;this.trade_type = trade_type;this.openid = openid;this.appid = appid;this.mch_id = mch_id;
}/*** 设置单品优惠信息*/
public void configDetail(OrderInfos orderInfos) {this.detail = JSON.toJSONString(orderInfos);
}/*** 设置实际门店信息*/
public void configScene_info(SceneInfo sceneInfo) {this.scene_info = JSON.toJSONString(sceneInfo);
}}

WeChatPreOrderInfo (微信预支付实体)

代码示例


/**
* 微信预支付订单返回信息
*/
@Data
public class WeChatPreOrderInfo {//返回状态码
private String return_code;
//返回信息
private String return_msg;
//应用APPID
private String appid;
//商户号
private String mch_id;
//设备号
private String device_info;
//随机字符串
private String nonce_str;
//签名
private String sign;
//业务结果
private String result_code;
//错误代码
private String err_code;
//错误代码描述
private String err_code_des;
//交易类型
private String trade_type;
//预支付交易会话标识
private String prepay_id;
//扫码支付返回字段,用于生成二维码
private String code_url;
//二维码有效时间
private String time_expire;/*** 连接是否成功*/
public boolean isContact() {return "SUCCESS".equals(this.return_code);
}/*** 业务是否成功*/
public boolean isSuccess() {if (isContact()) {return "SUCCESS".equals(this.result_code);}return false;
}/*** 固定字段*/
public String getPackage() {return "Sign=WXPay";
}/*** 时间戳*/
public Long getTimestamp() {return System.currentTimeMillis() / 1000;
}}

WeChatPayRet (微信支付返回结果实体)

代码示例

/**
* 微信支付返回信息类
*/
@Data
public class WeChatPayRet {//返回状态码
private String return_code;
//返回信息
private String return_msg;
//应用ID
private String appid;
//商户号
private String mch_id;
//设备号
private String device_info;
//随机字符串
private String nonce_str;
//签名
private String sign;
//业务结果
private String result_code;
//错误代码
private String err_code;
//错误代码描述
private String err_des;
//用户标识
private String openid;
//是否关注公众账号
private String is_subscribe;
//交易类型
private String trade_type;
//付款银行
private String bank_type;
//总金额
private Integer total_fee;
//货币种类
private String fee_type;
//现金支付金额
private Integer cash_fee;
//现金支付货币类型
private String cash_fee_type;
//代金券金额
private Integer coupon_fee;
//代金券使用数量
private Integer coupon_count;
//微信支付订单号
private String transaction_id;
//商户订单号
private String out_trade_no;
//商家数据包
private String attach;
//加密方式
private String sign_type;
//支付完成时间//支付完成时间
private String time_end;/*** 连接是否成功*/
public boolean isContact() {return "SUCCESS".equals(this.return_code);
}/*** 业务是否成功*/
public boolean isSuccess() {if (isContact()) {return "SUCCESS".equals(this.result_code);}return false;
}@Override
public String toString() {return "WeChatPayRet{" +"return_code='" + return_code + '\'' +", return_msg='" + return_msg + '\'' +", appid='" + appid + '\'' +", mch_id='" + mch_id + '\'' +", device_info='" + device_info + '\'' +", nonce_str='" + nonce_str + '\'' +", sign='" + sign + '\'' +", result_code='" + result_code + '\'' +", err_code='" + err_code + '\'' +", err_des='" + err_des + '\'' +", openid='" + openid + '\'' +", is_subscribe='" + is_subscribe + '\'' +", trade_type='" + trade_type + '\'' +", bank_type='" + bank_type + '\'' +", total_fee=" + total_fee +", fee_type='" + fee_type + '\'' +", cash_fee=" + cash_fee +", cash_fee_type='" + cash_fee_type + '\'' +", coupon_fee=" + coupon_fee +", coupon_count=" + coupon_count +", transaction_id='" + transaction_id + '\'' +", out_trade_no='" + out_trade_no + '\'' +", attach='" + attach + '\'' +", sign_type='" + sign_type + '\'' +", time_end='" + time_end + '\'' +'}';
}
}

注意:随着微信版本的更新微信官方会扩展出新的字段,届时注意接收实体的局部变动

相关工具类

IpUtil(ip获取工具类)

代码示例


import org.apache.commons.lang.StringUtils;import javax.servlet.http.HttpServletRequest;/**
* 获取用户侧ip地址
*/
public class IpUtil {public static String getIp(HttpServletRequest request) {String ip = request.getHeader("X-Real-IP");if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) {ip = request.getHeader("remote-host");}if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip.trim())) {ip = request.getRemoteAddr();}if (StringUtils.isNotBlank(ip)) {if (ip.startsWith("10.")) {String tip = request.getParameter("ip");if (StringUtils.isNotBlank(tip)) {ip = tip;}}}return ip;
}
}

SnGenerator(随机字符串生成器)

代码示例


/*** 随机字符串生成工具*/
public class SnGenerator {private final static char[] NUMS = "123456789".toCharArray();
private final static char[] LETTERS = "QWERTYUIPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm".toCharArray();
private final static char[] MIX_LETTERS_AND_NUM = "QWERTYUIPASDFGHJKLZXCVBNMqwertyuipasdfghjklzxcvbnm01234567890".toCharArray();public final static int MODE_NUM = 0;
public final static int MODE_LOWER_STR = 1;
public final static int MODE_UPPER_STR = 2;
public final static int MODE_STR = 3;
public final static int MODE_MIX = 4;/*** 生成带前缀的字符串,如果前缀+日期字符串+随机字符串的长度超过count,将会在保留前缀的情况下压缩其余部分*/
public static String generateFormatWithPrefix(String prefix, int count, int mode) {return generateFormat(prefix, null, count, mode);
}/*** 生成带前缀的字符串,如果日期字符串+随机字符串+后缀的长度超过count,将会在保留后缀的情况下压缩其余部分*/
public static String generateFormatWithSuffix(String suffix, int count, int mode) {return generateFormat(null, suffix, count, mode);
}/*** 生成不带前后缀的字符串,格式为yyyyMMddHHmmssSSS+随机字符串,长度为count*/
public static String generateFormat(int count, int mode) {return generateFormat(null, null, count, mode);
}/*** 生成格式化的字符串,格式为前缀+日期字符串(17位)+中间随机字符串+后缀。如果前缀+后缀+中间字符串的长度超过count, 将会压缩中间字符串的长度来满足count** @param prefix 字符串前缀* @param suffix 字符串后缀* @param count 生成字符串的长度* @param mode 模式*/
public static String generateFormat(String prefix, String suffix, int count, int mode) {if (count <= 17) {count = 18;}int prefixLen = 0;int suffixLen = 0;StringBuilder sb = new StringBuilder();if (prefix != null && (!"".equals(prefix))) {prefixLen = prefix.length();sb.append(prefix);}if (suffix != null && (!"".equals(suffix))) {suffixLen = suffix.length();}SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");String date = sdf.format(new Date());int len = count - prefixLen - suffixLen - date.length();if (len > 0) {switch (mode) {case MODE_NUM:date = date + randomNums(len);break;case MODE_LOWER_STR:date = date + randomLowerStr(len);break;case MODE_UPPER_STR:date = date + randomUpperStr(len);break;case MODE_STR:date = date + randomStr(len);break;case MODE_MIX:date = date + randomMix(len);break;default:date = date + randomNums(len);break;}}sb.append(date.substring(0, count - prefixLen - suffixLen));if (suffixLen > 0) {sb.append(suffix);}return sb.toString();
}/*** 生成写字母和数字随机字符串*/
public static String randomMix(int count) {return generator(count, MIX_LETTERS_AND_NUM);
}/*** 生成大小写混合字母随机字符串*/
public static String randomStr(int count) {return generator(count, LETTERS);
}/*** 生成纯大写字母随机字符串*/
public static String randomUpperStr(int count) {return generator(count, LETTERS).toUpperCase();
}/*** 生成纯数字随机字符串*/
public static String randomNums(int count) {return generator(count, NUMS);
}/*** 生成纯小写字母随机字符串*/
public static String randomLowerStr(int count) {return generator(count, LETTERS).toLowerCase();
}/*** 生成器*/
private static String generator(int count, char[] arr) {if (count <= 0) {count = 6;}StringBuilder sb = new StringBuilder();for (int i = 0; i < count; i++) {double d = Math.random();int index = (int) Math.floor(d * arr.length);sb.append(arr[index]);}return sb.toString();
}}

XMLUtils(xml转化工具类)

代码示例


/**
* xml转化工具类
*/
public class XMLUtils {/*** 从request读取xml*/
public static String readXmlFromRequest(HttpServletRequest request) {StringBuilder xmlSb = new StringBuilder();try (ServletInputStream in = request.getInputStream();InputStreamReader inputStream = new InputStreamReader(in);BufferedReader buffer = new BufferedReader(inputStream);) {String line;while ((line = buffer.readLine()) != null) {xmlSb.append(line);}} catch (Exception e) {e.printStackTrace();}return xmlSb.toString();
}/*** 将获取的Map转换成xml*/
public static Document convertMap2Xml(Map<String, Object> map) {Document doc = DocumentHelper.createDocument();try {Element root = doc.addElement("xml");Set<String> keys = map.keySet();for (String key : keys) {Element ele = root.addElement(key);ele.addCDATA(map.get(key).toString());}} catch (Exception e) {e.printStackTrace();}return doc;
}/*** xml文档Document转对象*/
@SuppressWarnings("unchecked")
public static Object convertXml2Bean(Document document, Class<?> clazz) {Map<String, String> map = new HashMap<>();// 获取根节点Element root = document.getRootElement();try {List<Element> properties = root.elements();for (Element pro : properties) {String propName = pro.getName();String propValue = pro.getText();map.put(propName, propValue);}} catch (Exception e) {e.printStackTrace();}//处理map里的JSON字符串字段,防止解析错误Map<String, Object> objMap = new TreeMap<>();Set<String> keys = map.keySet();for (String key : keys) {String str = map.get(key);try {//如果是JSON字符串,则转换成对象,再添加到objMap中objMap.put(key, JSON.parse(str));} catch (JSONException e) {//如果不是JSON字符串,则直接添加到objMap中objMap.put(key, str);} catch (Exception e) {//其余错误抛出e.printStackTrace();}}return JSON.parseObject(JSON.toJSONString(map), clazz);
}/*** xml字符串转对象*/
public static Object convertXml2Bean(String xmlString, Class<?> clazz) {Document document;try {document = DocumentHelper.parseText(xmlString);} catch (DocumentException e) {throw new RuntimeException("获取Document异常" + xmlString);} // 获取根节点return convertXml2Bean(document, clazz);
}/*** 对象转xml文件*/
public static Document convertBean2Xml(Object b) {Document document = DocumentHelper.createDocument();try {// 创建根节点元素Element root = document.addElement(b.getClass().getSimpleName());// 获取实体类b的所有属性,返回Field数组Field[] field = b.getClass().getDeclaredFields();// 遍历所有有属性for (Field aField : field) {String name = aField.getName(); // 获取属属性的名字if (!name.equals("serialVersionUID")) {// 去除串行化序列属性name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写,方便构造get,set方法Method m = b.getClass().getMethod("get" + name);String propValue = (String) m.invoke(b);// 获取属性值Element propertie = root.addElement(name);propertie.setText(propValue);}}} catch (Exception e) {e.printStackTrace();}return document;
}/*** 对象转xml格式的字符串*/
public static String getXmlString(Object b) {return convertBean2Xml(b).asXML();
}
}

WeChatPayAssistant (微信支付助手)

代码示例


public class WeChatPayAssistant {private static final Logger logger = LoggerFactory.getLogger(WeChatPayAssistant.class);/*** 解析付款回调请求*/
public static WeChatPayRet parsePayNotifyRequest(HttpServletRequest request) {String xml = XMLUtils.readXmlFromRequest(request);logger.info("wechatXml is:           "+xml);return (WeChatPayRet) XMLUtils.convertXml2Bean(xml, WeChatPayRet.class);
}/*** 解析退款回调请求*/
public static WeChatRefundNotifyRet parseRefundNotifyRequest(HttpServletRequest request) {String xml = XMLUtils.readXmlFromRequest(request);return (WeChatRefundNotifyRet) XMLUtils.convertXml2Bean(xml, WeChatRefundNotifyRet.class);
}/*** 应答微信回调*/
public static void echo(HttpServletResponse response) throws Exception {response.setContentType("application/xml");ServletOutputStream os = response.getOutputStream();os.print(echo());
}/*** 异步回调应答*/
public static String echo() {return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}/*** 微信公众号或移动支付退款*/
public static WeChatRefundRet refund(WeChatRefundInfo refundInfo, String tradeType)throws Exception {refundInfo.setNotify_url(WeChatPayConfigurations.getNotifyUrl("refund"));refundInfo.setNonce_str(SnGenerator.randomMix(32));String xml = "";refundInfo.setAppid(WeChatPayConfigurations.getAppId());refundInfo.setMch_id(WeChatPayConfigurations.getMchId());refundInfo.setSign(generateSign(refundInfo));TreeMap<String, Object> map = getSignMap(refundInfo, WeChatRefundInfo.class);Document doc = XMLUtils.convertMap2Xml(map);URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/secapi/pay/refund").build();xml = HttpClientUtils.connectWithXMLAndSSLByPost(uri, doc,WeChatPayConfigurations.getRefundCertificatePath(),WeChatPayConfigurations.getRefundCertificatePassword());WeChatRefundRet refundRet = (WeChatRefundRet) XMLUtils.convertXml2Bean(xml, WeChatRefundRet.class);if (!refundRet.isContact()) {String msg = refundRet.getReturn_code() + ":" + refundRet.getReturn_msg();msg = new String(msg.getBytes("iso-8859-1"), "utf-8");throw new RuntimeException(msg);}if (!refundRet.isSuccess()) {String msg = refundRet.getResult_code() + ":" + refundRet.getErr_code() + " - " + refundRet.getErr_code_des();throw new RuntimeException(msg);}return refundRet;
}/*** 微信预支付订单*/
public static WeChatPreOrderInfo preOrder(WeChatPayInfo payInfo) throws Exception {if (payInfo.getTrade_type().equals(WeChatPayConst.TRADE_TYPE_OFFICIAL_ACCOUNT) && StringUtils.isEmpty(payInfo.getOpenid())) {throw new RuntimeException("公众号支付openid不能为空,请填入正确的openid");}payInfo.setAppid(WeChatPayConfigurations.getAppId());payInfo.setMch_id(WeChatPayConfigurations.getMchId());payInfo.setNonce_str(SnGenerator.randomMix(32).toUpperCase());payInfo.setTime_expire(getOrderExpireTime(2*60*60*1000L));payInfo.setSign(generateSign(payInfo));Document doc = XMLUtils.convertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class));URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/pay/unifiedorder").build();String xml = HttpClientUtils.connectWithXMLByPost(uri, doc);WeChatPreOrderInfo info = (WeChatPreOrderInfo) XMLUtils.convertXml2Bean(xml, WeChatPreOrderInfo.class);if (!info.isContact()) {String msg = info.getReturn_code() + ":" + info.getReturn_msg();msg = new String(msg.getBytes("iso-8859-1"), "utf-8");throw new RuntimeException(msg);}if (!info.isSuccess()) {String msg = info.getResult_code() + ":" + WeChatPayErrorUtil.getErrorMsg(info.getErr_code());throw new RuntimeException(msg);}return info;
}/***  设置微信二维码失效时间,并返回具体失效的时间点*  expire 二维码的有效时间,单位是毫秒*/
public static String getOrderExpireTime(Long expire){SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");Date now = new Date();Date afterDate = new Date(now.getTime() + expire);return sdf.format(afterDate );
}/*** 设置签名*/
public static String generateSign(Object obj) throws Exception {TreeMap<String, Object> map = getSignMap(obj, obj.getClass());return signMap(map);
}/*** 验证签名*/
public static boolean isSignValid(Object obj) throws Exception {TreeMap<String, Object> map = getSignMap(obj, obj.getClass());String signFromAPIResponse = map.get("sign").toString();if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {logger.warn("The data signature data returned by the API does not exist and may be tampered with by a third party!!!");return false;}logger.info("服务器回包里面的签名是: {}", signFromAPIResponse);//清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名map.remove("sign");logger.info("sign map: {}", new JSONObject(map));String signForAPIResponse = signMap(map);if (!signForAPIResponse.equals(signFromAPIResponse)) {//签名验不过,表示这个API返回的数据有可能已经被篡改了logger.warn("The data signature verification returned by the API fails, and may be tampered with by a third party!!! signForAPIResponse The resulting signature is" + signForAPIResponse);return false;}return true;
}
/*** TreeMap加签*/
private static String signMap(TreeMap<String, Object> map) {Set<String> keys = map.keySet();StringBuilder sb = new StringBuilder();for (String key : keys) {sb.append(key).append("=").append(map.get(key)).append("&");}sb.append("key").append("=").append(WeChatPayConfigurations.getPayKey());return DigestUtils.md5Hex(sb.toString()).toUpperCase();
}/*** 获取按顺序整理好的非空值字段*/
@SuppressWarnings("unchecked")
private static TreeMap<String, Object> getSignMap(Object obj, Class<?> clz) throws Exception {if (obj == null) {throw new RuntimeException("支付对象不能为空");}// 使用treeMap将字段按要求排序TreeMap<String, Object> map = new TreeMap<>();if (obj instanceof Map){Map mapObj = (Map)obj;Set<Map.Entry<Object,Object>> set = mapObj.entrySet();for (Map.Entry<Object,Object> entry:set){map.put(entry.getKey().toString(),entry.getValue());}}else {Field[] fields = clz.getDeclaredFields();for (Field field : fields) {map.put(field.getName(), clz.getMethod("get" + upperFirst(field.getName())).invoke(obj));}}// 使用fastjson过滤掉null的字段String json = JSON.toJSONString(map);map = JSON.parseObject(json, TreeMap.class);return map;
}/*** 首字母大写*/
private static String upperFirst(String name) {return name.substring(0, 1).toUpperCase() + name.substring(1);
}/*** APP支付二次加签*/
public static Map<String, Object> sign4App(WeChatPreOrderInfo preOrderInfo) {TreeMap<String, Object> map = new TreeMap<>();map.put("appid", preOrderInfo.getAppid());map.put("partnerid", preOrderInfo.getMch_id());map.put("prepayid", preOrderInfo.getPrepay_id());map.put("package", preOrderInfo.getPackage());map.put("noncestr", SnGenerator.randomMix(32));map.put("timestamp", preOrderInfo.getTimestamp());map.put("sign", signMap(map));return map;
}/*** 微信内H5支付二次加签(注意:APP加签字段全部小写,这里加签用驼峰)*/
public static Map<String, Object> sign4WxH5(WeChatPreOrderInfo preOrderInfo) {TreeMap<String, Object> map = new TreeMap<>();map.put("appId", preOrderInfo.getAppid());map.put("timeStamp", preOrderInfo.getTimestamp().toString());map.put("package", "prepay_id=" + preOrderInfo.getPrepay_id());map.put("nonceStr", SnGenerator.randomMix(32));map.put("signType", "MD5");map.put("paySign", signMap(map));return map;
}public static WeChatRefundReqInfo decodeReqInfo(String reqInfo) throws Exception {String md5Key = DigestUtils.md5Hex(WeChatPayConfigurations.getPayKey());Security.addProvider(new BouncyCastleProvider());Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");SecretKey keySpec = new SecretKeySpec(md5Key.getBytes(), "AES"); //生成加密解密需要的Keycipher.init(Cipher.DECRYPT_MODE, keySpec);String result = new String(cipher.doFinal(Base64.decodeBase64(reqInfo)),StandardCharsets.UTF_8);return (WeChatRefundReqInfo) XMLUtils.convertXml2Bean(result, WeChatRefundReqInfo.class);
}}

微信支付常量

代码示例


/**
* 微信支付常量词典
*/
public class WeChatPayConst {// 签名类型md5
public static final String SIGN_TYPE_MD5 = "MD5";
// 签名类型sha256
public static final String SIGN_TYPE_SHA256 = "HMAC-SHA256";// 支付类型:公众号支付
public static final String TRADE_TYPE_OFFICIAL_ACCOUNT = "JSAPI";
// 支付类型:扫码支付
public static final String TRADE_TYPE_SWEEP_CODE = "NATIVE";
// 支付类型:APP
public static final String TRADE_TYPE_APP = "APP";
}

关键步骤

  1. 支付账号的开通
    参考微信官方文档

  2. 微信预支付

    1. 确定金额,生成商户系统相关订单信息,初始化状态为待支付

    2. 构造微信支付实体(WeChatPayInfo)

    WeChatPayInfo weChatPayInfo = new WeChatPayInfo(BODY, orderId, amount, notifyUrl,WeChatPayConst.TRADE_TYPE_SWEEP_CODE, ip);
    
    1. 获取用户侧ip

    2. 设置签名

    3. 生成随机字符串

    4. 定义回调接口url(notifyUrl)

    5. 按要求将微信支付实体转化为xml

    6. 调用微信统一下单接口

    Document doc = XMLUtils.convertMap2Xml(getSignMap(payInfo, WeChatPayInfo.class));
    URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/pay/unifiedorder").build();
    
    1. 将返回结果转化为微信预支付实体类(WeChatPreOrderInfo),并获取二维码url

    备注:预支付详细代码见WeChatPayAssistant工具类的
    preOrder(WeChatPayInfo payInfo)方法

  1. 微信回调(微信支付结果通知)

    • 判断连接和业务是否都成功
    • 验签
    • 修改订单状态(支付成功或失败)
    • 返回回调应答
public String weChatPayNotify(HttpServletRequest request) {
WeChatPayRet weChatPayRet = WeChatPayAssistant.parsePayNotifyRequest(request);
//判断连接和业务是否都成功且通过验签
if (weChatPayRet.isContact() && weChatPayRet.isSuccess() && WeChatPayAssistant.isSignValid(weChatPayRet)) {
//修改订单状态及相关业务处理
}//返回应答
return WeChatPayAssistant.echo();
}

4.如果未收到微信支付回调通知,可主动调用微信查询订单状态接口,
此处逻辑省略 微信官方查询订单

小程序支付

小程序与扫码支付的主要不同之处即小程序需要先调起微信登录接口( wx.login(Object object) )获取登录凭证(code),然后通过code进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。

小程序支付序列图:

关键步骤

  1. 根据code获取openId并将session_key存入redis

     public static String getOpenId(String appId,String appSecret,String code){String s = HttpClientUtil.sendHttpGet(getOpenIdUrl + "&appid=" + appId + "&secret=" + appSecret+"&js_code="+code);if (null!=s&&!"".equals(s)){JSONObject jsonObject = JSON.parseObject(s);String openId = jsonObject.getString("openid");//把session_key存进redisString session_key = jsonObject.getString("session_key");JedisCluster jedisCluster = RedisUtil.getJedisCluster();String s1 = jedisCluster.get(RedisUtil.getKey(sessionKeyPre + openId));if (null!=openId&&!"".equals(openId)){if (null==s1||"".equals(s1)||!s1.equals(session_key)){jedisCluster.set(RedisUtil.getKey(sessionKeyPre+openId),session_key);jedisCluster.expire(RedisUtil.getKey(sessionKeyPre+openId),86400*2);}return openId;} else {Integer errcode = jsonObject.getInteger("errcode");String errmsg = jsonObject.getString("errmsg");log.error("getOpenId is failed errcode:"+errcode+"-----errmsg:"+errmsg);throw new RuntimeException("获取openId失败");}}else {log.error("getOpenId is failed ");throw new RuntimeException("获取openId失败");}}
    
  2. 微信预支付(同扫码支付,但不返回用于生成二维码的code_url)需将预支付交易会话标识prepay_id以及拼接好的数据包package返给前端

  3. 微信回调(同扫码支付)

微信支付之扫码支付与小程序支付相关推荐

  1. 扫码点餐小程序源码 多商户外卖点餐自助扫码预约源码

    智慧餐厅扫码点餐小程序系统源码,二维码点餐,微信支付宝点餐系统源码,外卖点餐源码 1. 开发语言:JAVA 2. 数据库:MySQL 3. 原生小程序 4. Sass 模式 5. 带调试视频 6. 可 ...

  2. 微信扫码点餐小程序springboot外卖点餐系统源码和论文

    开发工具: 后端:idea   用户端:微信开发者工具 数据库 :mysql5.7+ 技术:java  springboot  mybatis  微信原生技术 角色:   管理员  多商家    用户 ...

  3. 2021年新微信小程序开发系统源码易客多小程序saas系统扫码点餐小程序支付宝小程序快速生成系统源码源代码

    2021年新微信小程序开发系统源码小程序saas系统扫码点餐小程序支付宝小程序快速生成系统 产品介绍:易客多多合一小程序快速生成系统是厦门四六开科技有限公司开发的快速开发微信百度支付宝小程序生成系统, ...

  4. 扫码点餐小程序源码_扫码点餐小程序有什么用?怎么制作?

    现在小程序扫码点餐服务已经越来越普及,当用户需要点餐时,无需麻烦服务人员,只需扫描餐桌上或者海报上的小程序码,就能快速点餐下单.这样不仅节约了排队时间,也提高了商家自己的服务效率. 上线了小程序案例, ...

  5. 前后端齐全的扫码点餐小程序(后端Java)

    真正的大师,永远都怀着一颗学徒的心! 一.项目简介 时间真的经不住算计的,一晃,就是好多年. 时间差不多又过了半年,真的好快.转眼间到了而立之年,可是还没立的起来,依旧在人潮汹涌的社流中,被推着往前走 ...

  6. 支付宝扫码跳转小程序并传参

    支付宝扫码跳转小程序并传参 1. 打开支付宝开放平台 支付宝开放平台 打开支付宝开放平台,进入自己的小程序详情页面.点击小程序码>关联普通二维码>添加按钮 2. 输入业务域名 这里我们选择 ...

  7. 微信扫码点餐小程序怎么做,一步步教你

    在当今数字化时代,更多的餐厅开始使用扫码点餐系统.这种系统可以提高顾客的点餐效率,减少服务员的工作负担.如果你也想要在你的餐厅中使用扫码点餐系统,那么你来对地方了. 第一步:搭建微信小程序后台 在微信 ...

  8. 微信扫码跳转小程序并传参

    1. 打开微信公众平台 微信公众平台 扫码登录小程序的后台 开发>开发管理>开发设置>扫普通链接二维码打开小程序>添加 2. 输入业务域名 3. 下载校验文件 这里我用一个aa ...

  9. 腾讯推出“扫码购”,小程序新零售的秘密武器!

    昨天,腾讯公布了2018Q1财报,里面首次提到"扫码购"这个神秘"武器". 财报显示,2018年第一季度,腾讯总收入为人民币735.28亿元,比去年同期增长48 ...

  10. 杉德支付php代码实现_php实现小程序支付完整版

    本文实例为大家分享了php实现小程序支付的具体代码,供大家参考,具体内容如下 环境: tp3.2  + 小程序 微信支付功能开通 Step1:下载PHP 支付SDK(下载地址)  放到Library\ ...

最新文章

  1. 论机器学习领域的内卷:不读PhD,我配不配找工作?
  2. ThinkPhp学习13
  3. 关于未能找到源文件“.NETFramework,Version=v4.0.AssemblyAttributes.cs”问题
  4. linux centos7修改默认启动的内核(升级及切换内核)
  5. 实战深度强化学习DQN-理论和实践
  6. python 状态模式_使用状态模式自由切换登录状态
  7. CentOS 6 编译安装subversion-1.8.10+Apache2.4
  8. SSL/TLS 以及HTTPS 介绍
  9. 【SQL】IN、EXISTS和表连接三者的效率比较
  10. 坐标轨迹计算_【老杨讲坛】737NG无系留最大风速限制的插值计算
  11. python的类方法、静态方法、实例方法_Python 类方法、实例方法、静态方法
  12. 图元变形lisp源码_CAD lisp 实用小程序(源代码)
  13. kms激活win10
  14. 智能营销笔记本全网最低价来了 代理送后台
  15. 常规WebRoot项目在Idea中通过tomcat运行
  16. java计算机毕业设计九宫格日志网站源代码+数据库+系统+lw文档
  17. 云计算已渗透大众生活,去中心化云计算发展前景广阔
  18. activity destory掉后马上释放内存方法
  19. java 代码生成nc 格式的文件,并且读取出nc格式里面的数据(已提供代码)
  20. 《软件工具》手把手教你使用Visual Studio Code开发C/C++(Windows)

热门文章

  1. JAVA—从小白到入门小白
  2. 0602 翻转xy轴
  3. NLP模型开发平台在舆情分析中的设计和实践
  4. 前华为员工的1则爆料帖,让我看清了职场上最赤裸裸的真相
  5. 《计算机网络 自顶向下方法》笔记 第二章 应用层
  6. Linux基础学习(1)
  7. oracle批处理文件
  8. python保存高维数组array
  9. 【MES系统】这个表格让您秒懂MES制造执行系统与ERP企业管理系统的区别及联系
  10. Web Player TcPlayer