微信企业付款到零钱(发红包)

本文详细介绍了如何使用微信支付的企业付款到零钱功能,包括必要的开发准备信息如商户号、appid和API证书,以及开发步骤和关键代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做微信用户提现功能用到了微信的企业付款到零钱,做简单记录
微信开发者文档->企业付款
开发之前需要获得的重要信息

  1. 商户号mchid:微信支付分配的商户号
  2. 商户账号appid
  3. 在有效期内的API证书:微信商户平台(pay.weixin.qq.com)–>账户中心–>账户设置–>API安全

开发步骤
在这里插入图片描述
在这里插入图片描述
使用的工具类,有获取签名和转换参数的方法



import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WxUtils {
	
	private static Logger logger = (Logger) LoggerFactory.getLogger(WxUtils.class);
	
	/**
	 * 生成随机数
	 * <p>
	 * 算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
	 * </p>
	 * 
	 * @return 随机数字符串
	 */
	public static String createNonceStr() {
		SecureRandom random = new SecureRandom();
		int randomNum = random.nextInt();
		return Integer.toString(randomNum);
	}

	/**
	 * 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign
	 * <p>
	 * 算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
	 * </p>
	 * 
	 * @param map 需要发送的所有数据设置为的Map
	 * @return 签名sign
	 */
	public static String createSign(Map<String, Object> map,String parKey) {
		String signValue = "";
		String stringSignTemp = "";
		String stringA = "";
		Map<String, Object> sortParams = new TreeMap<String, Object>(map);
		// 获得stringA
		Set<String> keys = sortParams.keySet();
		for (String key : keys) {
			stringA += (key + "=" + sortParams.get(key) + "&");
		}
		stringA = stringA.substring(0, stringA.length() - 1);
		// 获得stringSignTemp
		stringSignTemp = stringA + "&key=" + parKey;
		// 获得signValue
		signValue = encryptByMD5(stringSignTemp).toUpperCase();
		logger.debug("预支付签名:" + signValue);
		return signValue;
	}
	
	
	public static String createSignSha256(Map<String, Object> map,String parKey) {
		String signValue = "";
		String stringSignTemp = "";
		String stringA = "";
		Map<String, Object> sortParams = new TreeMap<String, Object>(map);
		// 获得stringA
		Set<String> keys = sortParams.keySet();
		for (String key : keys) {
			stringA += (key + "=" + sortParams.get(key) + "&");
		}
		stringA = stringA.substring(0, stringA.length() - 1);
		// 获得stringSignTemp
		stringSignTemp = stringA + "&key=" + parKey;
		// 获得signValue
		signValue = sha256_HMAC(stringSignTemp, parKey).toUpperCase();
		logger.debug("预支付签名:" + signValue);
		return signValue;
	}
	

	/**
	 * MD5加密
	 *
	 * @param sourceStr
	 * @return
	 */
	public static String encryptByMD5(String sourceStr) {
		String result = "";
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			try {
				md.update(sourceStr.getBytes("UTF-8"));
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			byte b[] = md.digest();
			int i;
			StringBuffer buf = new StringBuffer("");
			for (int offset = 0; offset < b.length; offset++) {
				i = b[offset];
				if (i < 0)
					i += 256;
				if (i < 16)
					buf.append("0");
				buf.append(Integer.toHexString(i));
			}
			result = buf.toString();
		} catch (NoSuchAlgorithmException e) {
			System.out.println(e);
		}
		return result;
	}
	
	/**
	 * 获取ip
	 * @return
	 */
	public static String getLocalIP() {   
        InetAddress addr = null;   
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }         
        byte[] ipAddr = addr.getAddress();   
        String ipAddrStr = "";   
        for (int i = 0; i < ipAddr.length; i++) {   
            if (i > 0) {   
                ipAddrStr += ".";   
            }   
            ipAddrStr += ipAddr[i] & 0xFF;   
        }   
        return ipAddrStr;   
    } 
	
	
	/*
     * 将SortedMap<Object,Object> 集合转化成 xml格式
     */
    public static String SortedMaptoXml(SortedMap<String,Object> parameters){
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = 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 ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
                sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
            }else {
                sb.append("<"+k+">"+v+"</"+k+">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }
	
    
    
    /**
     * 将加密后的字节数组转换成字符串
     *
     * @param b 字节数组
     * @return 字符串
     */
    public  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);
            byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
            hash = byteArrayToHexString(bytes);
        } catch (Exception e) {
            System.out.println("Error HmacSHA256 ===========" + e.getMessage());
        }
        return hash;
    }
    
	
	
	public static void main(String[] args) {
		Map<String, Object> map = new HashMap<>();
		map.put("appid", "wxd930ea5d5a258f4f");
		map.put("mch_id", "10000100");
		map.put("device_info", "1000");
		map.put("body", "test");
		map.put("nonce_str", "ibuaiVcKdpRxkhJA");
		System.out.println(WxUtils.createSignSha256(map, "192006250b4c09247ec02edce69f6a2d"));
	}
	
	
}

将证书放在自定义目录中(当前win7)
封装参数调用接口

public static void wxSendWallet(Map<String, Object> map) {
		
		
		String openid = map.get("openid").toString();     //openid
		double money =   Double.valueOf(map.get("double").toString());
//		String openid = "";
		BigDecimal df = new BigDecimal(money+"");
		df = df.multiply(new BigDecimal("100"));
     	int fee = df.intValue();
     	
     	//创建一个唯一订单号
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String time = sdf.format(new Date());
     	String orderId = "T"+time + (int)(Math.random()*1000);
     	
     	String nonceStr = RandomUtil.getRandomString(32);
        //SortedMap接口主要提供有序的Map实现,默认的排序是根据key值进行升序排序
        SortedMap<String,Object> parameters = new TreeMap<String,Object>();
        parameters.put("mch_appid", appid);
        parameters.put("mchid", mchid);
        parameters.put("nonce_str", nonceStr);
        parameters.put("partner_trade_no", orderId);
        parameters.put("openid", openid);
        parameters.put("check_name", "NO_CHECK");   //不进行实名认证
        parameters.put("amount", String.valueOf(fee));
        parameters.put("spbill_create_ip", WxUtils.getLocalIP());
        parameters.put("desc", "福利红包");
        //签名
        parameters.put("sign", WxUtils.createSign(parameters,key));
        String xml =WxUtils.SortedMaptoXml(parameters);
     	
        System.out.println(xml);
        try {
        	//指定读取证书格式为PKCS12
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            //windows系统
            FileInputStream instream = new FileInputStream(new File("F:\\cert\\apiclient_cert.p12"));
            //linux系统,读取本机存放的PKCS12证书文件
            //FileInputStream instream = new FileInputStream(new File("/alidata/opt/paycert/apiclient_cert.p12"));
            try {
            	//指定PKCS12的密码(商户ID)
                //keyStore.load(instream, accountUtil.getWxPartnerId().toCharArray());
            	keyStore.load(instream, mchid.toCharArray());
            }finally {
                instream.close();
            }
         // Trust own CA and all self-signed certs
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchid.toCharArray()).build();
        	//指定TLS版本, Allow TLSv1 protocol only
           
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            
            //设置httpclient的SSLSocketFactory
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers");
        	//这里要设置编码,不然xml中有中文的话会提示签名失败或者展示乱码
            httppost.addHeader("Content-Type", "text/xml");
            StringEntity se = new StringEntity(xml,"UTF-8");
            httppost.setEntity(se);
            CloseableHttpResponse responseEntry = httpclient.execute(httppost);
            try {
                HttpEntity entity = responseEntry.getEntity();
                if (entity != null) {
                    System.out.println("响应内容长度 : "+ entity.getContentLength());
                    SAXReader saxReader = new SAXReader();
                    Document document = saxReader.read(entity.getContent());
                    Element rootElt = document.getRootElement();
                    String resultCode = rootElt.elementText("result_code");
                    System.out.println(WeixinUtil.doc2String(document));
                    /*****************************记录日志**************************************/
                    PayLog log = new PayLog();
                	log.setId(RandomUtil.getRandomString(32));
                	log.setCode("WX_SEND_WALL");
                	log.setInXml(xml);
                	log.setOutXml(WeixinUtil.doc2String(document));
                	log.setCreateDate(new Date());
                	log.setOrderNo(orderId);
                    if(resultCode.equals("SUCCESS")){
                    	//处理业务逻辑
                    }else{
                    	System.out.println(rootElt.elementText("err_code_des"));
                    }
                }
                EntityUtils.consume(entity);
            }catch(Exception e){
            	System.out.println("请求失败");
            }
            finally {
                responseEntry.close();
            }
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

过程中遇到了一下错误
在这里插入图片描述
检查证书位置和引入名称
在这里插入图片描述
在这里插入图片描述
成功

### 微信发红包功能的完整测试用例 以下是针对微信发红包功能设计的一套完整的测试用例,涵盖了功能性、性能、安全性、兼容性和用户体验等多个方面。 #### 功能性测试 1. **金额校验** - 输入金额为0时,确认红包按钮是否呈灰色且不可点击[^3]。 - 测试最大支持金额(如单个红包不超过200元),超出范围是否有提示。 2. **红包类型** - 验证普通红包的功能:每个红包金额相等,总额分配无误[^3]。 - 验证拼手气红包的功能:每人领取金额随机,但总金额等于发出金额[^3]。 - 验证专属红包的功能:仅指定人员可领取,其他成员无法领取。 3. **留言功能** - 当留言为空时,默认显示“恭喜发财,大吉大利”。 - 留言长度控制在0至25字符范围内,超限应有提示。 4. **支付方式** - 使用零钱支付时余额不足的情况,系统是否提示更换支付方式。 - 同时使用零钱和银行卡支付时逻辑是否正确。 - 如果零钱与银行卡均不足以完成支付,则发送失败并给出相应提示[^3]。 5. **特殊场景** - 删除好友后再向其发送红包的行为验证。 - 超过24小时未领取的红包是否自动退回到原支付账户。 - 用户未绑定银行卡的情况下尝试抢红包行为验证。 #### 性能测试 1. **弱网环境下的表现** - 在网络较差条件下发送红包能否成功,并记录所需时间[^1]。 - 抢红包过程中遇到断网情况后的恢复机制验证[^1]。 2. **耗电量评估** - 记录连续多次收发红包过程中的设备电池消耗情况[^1]。 3. **流量统计** - 统计每次收发红包所耗费的数据量大小[^1]。 #### 安全性测试 1. **密码保护措施** - 输入错误密码达到一定次数后(通常为三次),是否触发身份认证流程。 - 对方异地登录时是否存在及时提醒机制[^2]。 2. **资金变动监控** - 成功发送红包后,付款方余额减少而收款方余额增加的现象是否一致发生[^2]。 - 若因故未能成功发送红包,则确保款项不会从任何一方扣除[^2]。 3. **通知服务** - 发送成功的红包是否会通过微信支付推送消息给接收者。 #### 兼容性测试 1. **跨平台适配** - 检查iOS各版本(如iOS 14到更早版本)下该功能运行状况良好[^1]。 - Android端同样覆盖主流操作系统版本(如Android 11至较旧版本)[^1]。 - 黑莓及其他非主流移动终端上的基本可用性检测。 2. **国际化考量** - 不同国家和地区货币单位转换准确性检验。 #### 用户体验/UI测试 1. **交互细节优化** - 所有涉及的文字表述清晰准确,不存在错别字现象。 - 图形化元素如动画表情加载顺畅与否。 - 自定义封面选项灵活多样满足个性化需求。 2. **便捷操作手段引入** - 是否提供指纹识别解锁代替传统数字密码输入作为快捷途径之一。 - 探索人脸识别技术应用于高敏感度交易场合的可能性探讨。 --- ```python # 示例代码片段用于模拟简单的红包分发算法 import random def distribute_red_packet(total_amount, num_people): """将total_amount分成num_people份""" amounts = [] remaining_total = total_amount * 100 # 将金额转化为整数处理 for i in range(num_people - 1): max_possible = min(int((remaining_total / (num_people - i)) * 2), remaining_total) amount = random.randint(1, max_possible) amounts.append(amount / 100) remaining_total -= amount amounts.append(round(remaining_total / 100, 2)) return amounts print(distribute_red_packet(10, 5)) # 输出五个随机金额之和为10的结果 ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值