小工具知识网

当前位置: 首页 > 行业知识 > 微信小程序商家转账到零钱【java】

行业知识

微信小程序商家转账到零钱【java】

2026-10-20 02:29:31 来源:互联网转载

微信小程序商家转账到零钱

    • 微信支付平台 开通权限
    • 开发文档
    • 代码
      • pom 文件
      • HttpUtil
      • WechatPayV3Util
      • ApiUrl
      • 提现记录表
      • controller 商家转账到零钱
      • ServiceImpl
      • controller 通过商家明细单号查询明细单
      • ServiceImpl

        微信支付平台 开通权限

        商家转账到零钱官���简介

        登录微信支付平台 点击产品中心

        产品大全 -> 我的 -> 运营工具

        然后根据要求 上传资料 提交开通申请 他说得是7-15天但是快一点也就两三天之内就有结果了

        开通之后需要设置一些东西

        点击前往功能

        • 设置转账账户
        • 设置转账发起方式 有两种 页面发起 && API发起
        • 设置转账配置 转账额度啥的
        • 重要: 设置访问IP 就是白名单的意思 如果本地的话查看外网IP(http://ifconfig.me ),服务器的自己找,hhhc(公司应该有记录)

          开发文档

          ps:很好找的其实,我刚开始还找了半天,哎

          微信支付文档中心

          进入之后点击 API文档

          到下面这个页面(把目录都收起来就好找了)

          然后点击运营工具就看见了

          • emmm 再多写点吧 转账API 地址 和 查询API 地址也给你吧

            都在这了,自己拿吧hhh

            商家转账到零钱API传送门

            通过商家批次单号查询转账结果传送门

            ps :okok 好人做到底了

            代码

            pom 文件

              com.github.wechatpay-apiv3   wechatpay-apache-httpclient   0.4.7

            HttpUtil

            import lombok.extern.slf4j.Slf4j;import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import java.io.IOException;import java.util.Arrays;import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;import static org.apache.http.HttpHeaders.ACCEPT;import static org.apache.http.HttpHeaders.CONTENT_TYPE;import static org.apache.http.entity.ContentType.APPLICATION_JSON;/** * @Author: 小新 * @Date: 2023/12/29 10:01 * 微信支付专用类 请求操作方法 */@Slf4jpublic class HttpUtil {    /**     * 发起批量转账API 批量转账到零钱     *     * @param requestUrl     * @param requestJson 组合参数     * @param wechatPayserialNo 商户证书序列号     * @param mchID  商户号     * @param privatekeypath  商户私钥证书路径     * @return     */    public static String postTransBatRequest(            String requestUrl,            String requestJson,            String wechatPayserialNo,            String mchID4M,            String privatekeypath) {        CloseableHttpClient httpclient = HttpClients.createDefault();        CloseableHttpResponse response = null;        HttpEntity entity = null;        try {            HttpPost httpPost = new HttpPost(requestUrl);            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误            httpPost.addHeader("Content-Type", "application/json");            httpPost.addHeader("Accept", "application/json");            httpPost.addHeader("Wechatpay-Serial", wechatPayserialNo);            /*****************核心认证 start*****************/            String strToken = WechatPayV3Util.getToken("POST",                    "/v3/transfer/batches",                    requestJson,mchID4M,wechatPayserialNo, privatekeypath);            log.error("微信转账token "+strToken);            // 添加认证信息            httpPost.addHeader("Authorization",                    "WECHATPAY2-SHA256-RSA2048" + " "                            + strToken);            /*****************核心认证 end*****************/                        httpPost.setEntity(new StringEntity(requestJson, "UTF-8"));            //发起转账请求            response = httpclient.execute(httpPost);            entity = response.getEntity();//获取返回的数据            log.info("返回请求ID :{} ", Arrays.toString(response.getHeaders("Request-ID")));            return EntityUtils.toString(entity);        } catch (Exception e) {            e.printStackTrace();        } finally {            // 关闭流            try {                if (httpclient != null) {                    httpclient.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }    /**     * 查询账单     * @param requestUrl        请求地址     * @param wechatPayserialNo 商户证书序列号     * @param privatekeypath    商户私钥证书路径     * @return     */    public static String getTransBatRequest(            String requestUrl,            String wechatPayserialNo,            String mchId,            String privatekeypath,            String servletPath){        CloseableHttpResponse response = null;        HttpEntity entity = null;        CloseableHttpClient httpClient = HttpClients.createDefault();        try {            HttpGet httpGet = new HttpGet(requestUrl);            // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误            httpGet.addHeader("Content-Type", "application/json");            httpGet.addHeader("Accept", "application/json");            httpGet.addHeader("Wechatpay-Serial", wechatPayserialNo);                        /*****************核心认证 start*****************/            String strToken = null;            try {                strToken = WechatPayV3Util.getToken("GET", servletPath, "", mchId, wechatPayserialNo, privatekeypath);            } catch (Exception e) {                log.error("请求异常:{}", e.getMessage());                e.printStackTrace();            }            log.info("token " + strToken);            // 添加认证信息            httpGet.addHeader("Authorization",                    "WECHATPAY2-SHA256-RSA2048" + " "                            + strToken);            /*****************核心认证 end*****************/            //发起查询请求            response = httpClient.execute(httpGet);            log.info("响应:  {}", response);            entity = response.getEntity();//获取返回的数据            return EntityUtils.toString(entity);        } catch (Exception e) {            e.printStackTrace();        } finally {            // 关闭流            try {                if (httpClient != null) {                    httpClient.close();                }            } catch (IOException e) {                e.printStackTrace();            }        }        return null;    }}

            WechatPayV3Util

            import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;import lombok.extern.slf4j.Slf4j;import org.apache.commons.codec.binary.Base64;import org.apache.commons.lang3.StringUtils;import java.io.*;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import java.security.KeyFactory;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.Signature;import java.security.cert.X509Certificate;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.util.Random;/** * @Author: 小新 * @Date: 2023/12/29 9:58 */@Slf4jpublic class VechatPayV3Util {    /**     * @param method       请求方法 post     * @param canonicalUrl 请求地址     * @param body         请求参数      * @param merchantId   商户号     * @param certSerialNo 商户证书序列号     * @param keyPath      私钥商户证书地址     * @return     * @throws Exception     */    public static String getToken(            String method,            String canonicalUrl,            String body,            String merchantId,            String certSerialNo,            String keyPath) throws Exception {        String signStr = "";        //获取32位随机字符串        String nonceStr = getRandomString(32);        //当前系统运行时间        long timestamp = System.currentTimeMillis() / 1000;        if (StringUtils.isEmpty(body)) {            body = "";        }        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);        //签名操作        //签名操作        String signature = sign(message.getBytes(StandardCharsets.UTF_8), keyPath);        //组装参数        signStr = "mchid=\"" + merchantId + "\",timestamp=\"" +  timestamp+ "\",nonce_str=\"" + nonceStr                + "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\"";        return signStr;    }    public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {        return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";    }    public static String sign(byte[] message, String keyPath) throws Exception {        Signature sign = Signature.getInstance("SHA256withRSA");        sign.initSign(getPrivateKey(keyPath));        sign.update(message);        return Base64.encodeBase64String(sign.sign());    }    /**     * @param filename 私钥文件路径  (required)     * @return 私钥对象     */    public static PrivateKey getPrivateKey(String filename) throws IOException {        //todo 我放在项目里的  也可以放在服务器上 都可以 看自己需求了        String content = new String(Files.readAllBytes(Paths.get("src/main/resources" + filename)), "utf-8");        try {            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")                    .replace("-----END PRIVATE KEY-----", "")                    .replaceAll("\\s+", "");            KeyFactory kf = KeyFactory.getInstance("RSA");            return kf.generatePrivate(                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));        } catch (NoSuchAlgorithmException e) {            throw new RuntimeException("不支持RSA", e);        } catch (InvalidKeySpecException e) {            throw new RuntimeException("无效的密钥格式");        }    }    /**     * 获取随机位数的字符串     * @param length  随机数长度     * @return     */    public static String getRandomString(int length) {        String base = "abcdefghijklmnopqrstuvwxyz0123456789";        Random random = new Random();        StringBuffer sb = new StringBuffer();        for (int i = 0; i  

            ApiUrl

            /** * 第三方api地址 */public class ApiUrl {//ps: 当时申请商家转账还没通过的时候 想着先写代码,结果写错了,写成了企业转账到零钱,吐血    /**     * 企业转账到零钱      */    //public static String WECHAT_TRANSFERS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";    /**     * 商家转账到零钱     */    public static String WECHAT_TRANSFERS_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches";    /**     * 商户批次号查询     */    public static String SELECT_ORDER_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no";}

            好像就没有了 对了 那个提现记录表也给你们吧 mysql的,看不上的你们就自己设计一个hhh

            提现记录表

            CREATE TABLE `tb_weixin_entpay_form` (  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',  `total_amount` decimal(19,2) DEFAULT NULL COMMENT '提现金额',  `user_name` varchar(255) DEFAULT NULL COMMENT '用户名',  `open_id` varchar(255) DEFAULT NULL COMMENT 'openid',  `partner_trade_no` varchar(255) DEFAULT NULL COMMENT '提现单号',  `progress` varchar(255) DEFAULT NULL COMMENT '提现进度 SUCCESS  INPROGRESS  FAIL',  `wx_type` varchar(255) DEFAULT NULL COMMENT '类型',  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',  `start_time` datetime DEFAULT NULL COMMENT '开始时间',  `end_time` datetime DEFAULT NULL COMMENT '结束时间',  `batch_id` varchar(255) DEFAULT NULL COMMENT '微信明细单号',  PRIMARY KEY (`id`))comment '提现记录表';

            下面的就是根据自己的业务来写了

            controller 商家转账到零钱

            @PostMapping("/transfer")    public Result transfer(@RequestParam(name = "amount", required = true) String amount, HttpServletRequest request) {        Long userId = (Long) request.getAttribute("userId");         return wxPayService.transfer(amount, userId, request);    }

            ServiceImpl

            @Value("${wx.WeChat.AppID}")    private String AppId;    @Value("${wx.WeChat.SerialNo}")    private String SerialNo;   // 支付证书序列号    @Value("${wx.WeChat.Privatekeypath}")    private String Privatekeypath;  // 我的是本地项目resource下的  /cert/apiclient_key.pem  这个 (别弄错了)    // todo  当时因为这个项目 证书给我了但是我不知道是哪个序列号 因为有好多个 ,所以又找了个根据商户私钥来找序列号// 在哪找到的呢 emm  还是告诉你们吧,藏在代码里 应该看的人不多嘿嘿嘿//https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-faqs.html/** 有需要的可以试试  1900009191_20180326_cert.pem 这个就是你本地存放地址了  window + R 就行*   openssl x509 -noout -modulus -in 1900009191_20180326_cert.pemModulus=C6D43C87B991...openssl rsa -noout -modulus -in 1900009191_20180326_key.pemModulus=C6D43C87B991...*//**     * 商家转账到零钱     * @param amount     * @param userId     * @param request     * @return     */    @Override    @Transactional(rollbackFor = Exception.class)    public Result transfer(String amount, Long userId, HttpServletRequest request) {        // 查询当前用户        LbUser lbUser = lbUserMapper.selectById(userId);        if (lbUser == null) {            return new Result("400", "当前用户未注册");        }        // 查询是否绑定微信        List userWxList = lbUserWxMapper.findByUserIdAndAppid(userId,AppId);        if (userWxList.isEmpty()){            return new Result("400", "请先绑定微信");        }        // 查询余额        List accountList = tbUserAccountMapper.selectList(new QueryWrapper().lambda().eq(TbUserAccount::getUserId, userId).orderByDesc(TbUserAccount::getId));        if (accountList.isEmpty()) {            return new Result("400", "当前用户未登录");        }        // 是否可以提现        TbUserAccount tbUserAccount = accountList.get(0);        BigDecimal availableBalance = tbUserAccount.getAvailableBalance();        if (availableBalance.compareTo(new BigDecimal(amount))  

            controller 通过商家明细单号查询明细单

            @GetMapping("/getTransBat")    public Result getTransBat(@RequestParam(name = "outBatchNo", required = true) String outBatchNo) {        return wxPayService.getTransBat(outBatchNo);    }

            ServiceImpl

            @Overridepublic Result getTransBat(String outBatchNo) {     String result  =  HttpUtil.getTransBatRequest(             ApiUrl.SELECT_ORDER_URL+"/"+outBatchNo+"?need_query_detail=True&detail_status=ALL",             SerialNo,             wxPayConfig.getMchId(),             Privatekeypath,"/v3/transfer/batches/out-batch-no"+"/"+outBatchNo+"?need_query_detail=True&detail_status=ALL");     JSONObject  results  =  JSONObject.parseObject(result);     return new Result("200",results); }/**{  "code": "200",  "data": {    "offset": 0,    "transfer_detail_list": [      {        "detail_status": "SUCCESS",  // 这个就是返回的结果了 还有失败和进行中什么的 文档里都有,自己看吧        "out_detail_no": "不给看",        "detail_id": "不给看"      }    ],    "limit": 20,    "transfer_batch": {      "mchid": "不给看",      "create_time": "也不给看",      "success_amount": 不给看,      "batch_id": "不给看",      "success_num": 1,      "batch_status": "FINISHED",      "out_batch_no": "不给看",      "batch_name": "提现",      "update_time": "也不给看",      "fail_amount": 0,      "total_amount": 100,//分      "appid": "不给看",      "total_num": 1,      "batch_remark": "提现",      "batch_type": "API",      "fail_num": 0,      "transfer_scene_id": "不给看" // 这是场景ID    }  },  "message": "操作成功"}*/

            跟转账的参数也大差不差 返回没处理 直接返回了 懒狗hhhhc

            • okok 到这应该就没有了 ,对了,我还写了个定时器来查询结果更新一些数据的,但是我给公司留了不少坑hhhc,反正能用,你们看看乐子就行。
              import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.math.BigDecimal;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.List;/** * @Author: 小新 * @Date: 2023/12/29 16:05 */@Slf4j@Configurationpublic class WexinEntPayTask {    @Resource    private WxPayConfig wxPayConfig;    @Value("${wx.WeChat.SerialNo}")    private String SerialNo;    @Value("${wx.WeChat.Privatekeypath}")    private String Privatekeypath;    @Resource    private WeixinEntPayFormMapper weixinEntPayFormMapper;    @Resource    private LbUserMapper lbUserMapper;    @Resource    private TbUserAccountMapper tbUserAccountMapper;    @Resource    private LbUserLawyerMessageLogMapper lbUserLawyerMessageLogMapper;    @Resource    private UrlLinkUtil urlLinkUtil;    @Resource    private ShlianLuUtil shlianLuUtil;    private static final String SUCCESS = "SUCCESS";    private static final String FAIL = "FAIL";    @Scheduled(cron = "0 */3 * * * ?")    @Transactional    void selectWexinEnPay() {        log.info("==========开始查询转账记录================");        // 查询所有进行中的转账记录        List list = weixinEntPayFormMapper.selectList(new QueryWrapper().lambda().eq(WeixinEntPayForm::getProgress, "INPROGRESS"));        if (list.isEmpty()) {            log.info("没有转账记录");            return;        }        for (WeixinEntPayForm weixinEntPayForm : list) {            LbUser user = lbUserMapper.selectById(weixinEntPayForm.getUserId());            if (user == null) {                log.info("用户不存在");                continue;            }            try {                String result = HttpUtil.getTransBatRequest(buildUrl(weixinEntPayForm), SerialNo, wxPayConfig.getMchId(), Privatekeypath, buildOutBatchNoUrl(weixinEntPayForm));                JSONObject results = JSONObject.parseObject(result);                processTransferResult(weixinEntPayForm, user, results);                log.info("=========={} 转账记录查询及后续操作完成================",weixinEntPayForm.getId());            } catch (Exception e) {                log.error("查询转账记录出现错误:{}, 商户批次单号:{}", e.getMessage(), weixinEntPayForm.getPartnerTradeNo());                continue;            }        }    }    /**     * 查询请求地址     * @param weixinEntPayForm     * @return     */    private String buildUrl(WeixinEntPayForm weixinEntPayForm) {        return ApiUrl.SELECT_ORDER_URL + "/" + weixinEntPayForm.getPartnerTradeNo() + "?need_query_detail=True&detail_status=ALL";    }    /**     * 相对路径     * @param weixinEntPayForm     * @return     */    private String buildOutBatchNoUrl(WeixinEntPayForm weixinEntPayForm) {        return "/v3/transfer/batches/out-batch-no" + "/" + weixinEntPayForm.getPartnerTradeNo() + "?need_query_detail=True&detail_status=ALL";    }    /**     * 根据请求结果 进行处理     * @param weixinEntPayForm 转账记录     * @param user 用户     * @param results 转账结果返回     * @throws ParseException     */    private void processTransferResult(WeixinEntPayForm weixinEntPayForm, LbUser user, JSONObject results) throws ParseException {        int updateAccount = 0;        boolean flag = true;        JSONArray transferDetailList = results.getJSONArray("transfer_detail_list");        String detailStatus = transferDetailList.getJSONObject(0).getString("detail_status");        if (SUCCESS.equals(detailStatus)) {            // 转账成功            updateAccount = updateAccount(weixinEntPayForm);            weixinEntPayForm.setProgress(SUCCESS);        } else if (FAIL.equals(detailStatus)) {            // 转账失败            weixinEntPayForm.setProgress(FAIL);        }        flag = flag && (updateAccount == 1);        if (flag) {            updateOrderAndSendMessage(weixinEntPayForm, user, results);        }    }    /**     * 更新余额     * @param weixinEntPayForm     * @return     */    private int updateAccount(WeixinEntPayForm weixinEntPayForm) {        List accountList = tbUserAccountMapper.selectList(new QueryWrapper().lambda().eq(TbUserAccount::getUserId, weixinEntPayForm.getUserId()).orderByDesc(TbUserAccount::getId));        if (!accountList.isEmpty()) {            TbUserAccount tbUserAccount = accountList.get(0);            // 更新余额            BigDecimal subtract = tbUserAccount.getAvailableBalance().subtract(weixinEntPayForm.getTotalAmount());            tbUserAccount.setAvailableBalance(subtract);            tbUserAccount.setBalance(subtract);            return tbUserAccountMapper.updateById(tbUserAccount);        }        return 0;    }    /**     * 更新 转账记录  和  发送消息     * @param weixinEntPayForm     * @param user     * @param results     * @throws ParseException     */    private void updateOrderAndSendMessage(WeixinEntPayForm weixinEntPayForm, LbUser user, JSONObject results) throws ParseException {        // 处理时间        Date updateTime = parseUpdateTime(results);        weixinEntPayForm.setEndTime(updateTime == null ? new Date() : updateTime);        boolean flag = weixinEntPayFormMapper.updateById(weixinEntPayForm) == 1;        try {            LbUserLawyerMessageLog lbUserLawyerMessageLog = buildMessageLog(weixinEntPayForm, user);            flag = flag && (lbUserLawyerMessageLogMapper.insert(lbUserLawyerMessageLog) == 1);        } catch (Exception e) {            log.info("存储消息失败 原因:{}", e.getMessage());        }        if (flag) {            if (SUCCESS.equals(weixinEntPayForm.getProgress())) {                sendTransferSMS(user,SUCCESS);            } else {                sendTransferSMS(user,FAIL);            }        } else {            log.info("转账短信发送失败");        }    }    /**     * 处理时间     * @param results     * @return     * @throws ParseException     */    private Date parseUpdateTime(JSONObject results) throws ParseException {        String timeWithTimeZone = results.getJSONObject("transfer_batch").getString("update_time");        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");        try {           return dateFormat.parse(timeWithTimeZone);        } catch (ParseException e) {            log.info("日期处理失败");            return null;        }    }    /**     * 构建短信记录     * @param weixinEntPayForm     * @param user     * @return     */    private LbUserLawyerMessageLog buildMessageLog(WeixinEntPayForm weixinEntPayForm, LbUser user) {        LbUserLawyerMessageLog lbUserLawyerMessageLog = new LbUserLawyerMessageLog();        lbUserLawyerMessageLog.setUserId(weixinEntPayForm.getUserId());        lbUserLawyerMessageLog.setCreatedTime(new Date());        lbUserLawyerMessageLog.setTopicLawyerId(0L);        lbUserLawyerMessageLog.setIdentityType(user.getIsAuth());        lbUserLawyerMessageLog.setReadIsNot(1);        lbUserLawyerMessageLog.setMessageType(SUCCESS.equals(weixinEntPayForm.getProgress()) ? UserMessageType.TRANSFERSUCCESS : UserMessageType.TRANSFERFAIL);        return lbUserLawyerMessageLog;    }    /**     * 发送短信     * @param user     * @param successOrFail     */    private void sendTransferSMS(LbUser user,String successOrFail) {        // 发送短信        String smsResult2 = shlianLuUtil.sendSMSTemplate(new String[]{user.getPhone()}, SUCCESS.equals(successOrFail) ? SmsTemplateIdUtil.transferSuccess : SmsTemplateIdUtil.transferFail,                new String[]{                        user.getNickname(),                        urlLinkUtil.jumpAppletShortUrl("")});        log.info("转账短信发送返回 smsResult2:{}", smsResult2);    }}
              • 老板应该看不见吧 hhhc 这次就到这吧,家人们看看乐子。

上一篇:lunix服务器怎么更改时间?

下一篇:dlink打印服务器如何使用?