微信商家支付

Java 实现微信商家支付


去年微信一波更新把【企业支付】搞炸了,现在都要用【商家付款到零钱】
(注意各个参数到底是什么就简单了)

代码

  1. 自定义请求参数实体
import lombok.Data;

/**
 * @Author:chen
 */
@Data
public class MerchantTransferRequest {

    /**
     * 商户号
     */
    private String mchId;
    /**
     * 公众号/小程序appId
     */
    private String appId;
    /**
     * 请求参数json
     */
    private String requestJson;
    /**
     * 商户证书序列号(微信管理后台管理人员提供)
     */
    private String wechatPaySerialNo;
    /**
     * 本地私钥地址(apiclient_key.pem 文件)
     */
    private String privateKeyPath;
    /**
     * 用户标识 openId (必须在正确的appId管理下)
     */
    private String openId;
    /**
     * v3完整接口请求路径
     */
    private String requestUrl;
    /**
     * v3 接口相对路径
     */
    private String v3Url;
}

  1. 接口认证工具类(通用)
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import java.io.IOException;
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.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Random;

/**
 * 微信V3接口工具类
 * @Author:chen
 */
@Slf4j
public class WechatPayV3Util {

    /**
     * @param method       请求方法 post
     * @param canonicalUrl 请求地址
     * @param body         请求参数   GET请求传空字符
     * @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;
        String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
        //签名操作
        String signature = sign(message.getBytes("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 {

        //log.info("签名 证书地址是 " + filename);
        String content = new String(Files.readAllBytes(Paths.get(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("当前Java环境不支持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 < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

}

  1. 微信付款、查询服务
import com.***t.MerchantTransferRequest;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;

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:chen
 */
@Service
@Slf4j
public class MerchantTransferService {

    @Autowired
    public MerchantTransferService(){}


    /**
     * 发起批量转账API 批量转账到零钱
     * @param request 微信商家转账请求参数
     * @return
     */
    public static String postTransBatRequest(MerchantTransferRequest request) {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        CloseableHttpClient httpClient = null;
        try {
            HttpPost httpPost = createHttpPost(request.getRequestUrl(), request.getRequestJson(), request.getWechatPaySerialNo(), request.getMchId(), request.getPrivateKeyPath(), request.getV3Url());
            httpClient = HttpClients.createDefault();
            //发起转账请求
            response = httpClient.execute(httpPost);
            log.info("response:{}", response);
            entity = response.getEntity();//获取返回的数据
            log.info("-----getHeaders.Request-ID:" + 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 request 微信商家转账请求参数
     * @return
     */
    public static String getTransBatRequest(MerchantTransferRequest request) throws NotFoundException {
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        CloseableHttpClient httpClient = null;
        try {
            HttpGet httpPost = createHttpGet(request.getRequestUrl(), request.getWechatPaySerialNo(), request.getMchId(), request.getPrivateKeyPath(), request.getV3Url());
            httpClient = HttpClients.createDefault();
            //查询请求
            response = httpClient.execute(httpPost);
            log.info("response:{}", response);
            //获取返回的数据
            entity = response.getEntity();
            log.info("-----getHeaders.Request-ID:" + 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 requestJson       请求参数
     * @param wechatPayserialNo 支付证书序列号
     * @param mchId             商户号
     * @param privatekeypath    私钥路径
     * @param servletPath       相对路径
     * @return
     */
    private static HttpPost createHttpPost(String requestUrl,
                                           String requestJson,
                                           String wechatPayserialNo,
                                           String mchId,
                                           String privatekeypath, String servletPath) {
        //商户私钥证书
        HttpPost httpPost = new HttpPost(requestUrl);
        // NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
        httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
        httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
        httpPost.addHeader(WECHAT_PAY_SERIAL, wechatPayserialNo);

        //-------------------------核心认证 start-----------------------------------------------------------------
        String strToken = null;
        try {
            log.info("requestJson:{}", requestJson);
            strToken = WechatPayV3Util.getToken("POST",
                    servletPath,
                    requestJson, mchId, wechatPayserialNo, privatekeypath);
        } catch (Exception e) {
            log.error("createHttpPost error:", e);
            e.printStackTrace();
        }
        StringEntity reqEntity = new StringEntity(requestJson, APPLICATION_JSON);
        log.info("token " + strToken);
        // 添加认证信息
        httpPost.addHeader("Authorization",
                "WECHATPAY2-SHA256-RSA2048" + " "
                        + strToken);
        //---------------------------核心认证 end---------------------------------------------------------------
        httpPost.setEntity(reqEntity);
        return httpPost;
    }

    /**
     * 创建get 请求
     *
     * @param requestUrl        请求完整地址
     * @param wechatPayserialNo 商户证书序列号
     * @param mchId             商户号
     * @param privatekeypath    私钥路径
     * @param servletPath       相对路径  请求地址上如果有参数 则此处需要带上参数
     * @return HttpGet
     */
    private static HttpGet createHttpGet(String requestUrl,
                                         String wechatPayserialNo,
                                         String mchId,
                                         String privatekeypath, String servletPath) {
        //商户私钥证书
        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("createHttpGet error:", e);
            e.printStackTrace();
        }

        log.info("token " + strToken);
        // 添加认证信息
        httpGet.addHeader("Authorization",
                "WECHATPAY2-SHA256-RSA2048" + " "
                        + strToken);
        //---------------------------核心认证 end---------------------------------------------------------------
        return httpGet;
    }
}

  1. 使用
		// 自行组装各个参数
		MerchantTransferRequest request = generalWechatInfo(prize,raffleRecord.getOrderNo());
       
        try {
            String result = merchantTransferService.postTransBatRequest(request);
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (!StringUtils.isEmpty(jsonObject.get("create_time"))){
                //转账成功
                log.info("记录ID {} 订单 {} 成功调用转账接口",raffleRecord.getId(),raffleRecord.getOrderNo());
            }else{
                //失败 -> 接口调用不成功,接口调用成功了也不代表成功,如果接口调用成功仍未到账,需要调用查询接口查看具体失败原因
                log.error("记录ID {} 订单 {} 转账失败,{}",raffleRecord.getId(),raffleRecord.getOrderNo(),jsonObject.get("message"));
                throw new ApiException("自定义异常信息");
            }
        } catch (Exception e) {
            log.error("商家转账支付失败,记录ID {} 订单 {}",raffleRecord.getId(),raffleRecord.getOrderNo());
            throw new ApiException("自定义异常信息");
        }

ps:查询接口组装例子
在这里插入图片描述

  1. 常用地址
    /**
     * v3商家转账完整路径
     */
    String BATCHES_TRANSFER_URL = "https://api.mch.weixin.qq.com/v3/transfer/batches";
    /**
     * v3商家转账相对路径
     */
    String BATCHES_TRANSFER_OPPOSITE_URL = "/v3/transfer/batches";

    /**
     * v3批量转账信息查询完整路径前缀
     */
    String BATCHES_TRANSFER_QUERY_URL_PREFIX = "https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/";
    /**
     * v3批量信息查询
     */
    String BATCHES_TRANSFER_QUERY_URL_SUFFIX = "?need_query_detail=true&detail_status=ALL";
    /**
     * v3详细信息查询
     */
    String BATCHES_TRANSFER_DETAIL_URL_SUFFIX = "/details/out-detail-no/";
    /**
     * v3商家批量转账信息查询相对路径
     */
    String  BATCHES_TRANSFER_QUERY_OPPOSITE_URL = "/v3/transfer/batches/out-batch-no/";
  1. 相关资料
    转账参数
    账单批量查询
    账单明细查询及失败原因

如果是类似抽奖的一次只给一个用户转账,那么批次单号跟个人单号参数一致也可以

### 回答1: 写一个对接微信商家支付的程序需要满足以下几个步骤: 1. 创建微信商家账号并申请支付接口权限。 2. 根据微信支付接口文档,使用 Python 实现请求支付的功能。 3. 将 Python 程序部署到服务器上,使用 SSL 证书确保数据的安全性。 4. 在 Python 程序中处理微信支付的异步通知,确保订单信息的准确性。 5. 定期对账,确保支付结果的准确性。 这里只是大致的流程,详细的实现细节需要根据微信支付接口文档进行深入学习和理解。 ### 回答2: 要用Python编写一个对接微信商家支付的程序,首先需要安装并导入相应的库,如wechatpy和flask。 1. 首先,需要在微信支付商户平台上注册并获取商户号、支付密钥等信息。 2. 导入相关库和模块,如: ```python from wechatpy import WeChatPay from flask import Flask, request ``` 3. 创建Flask应用和WeChatPay实例,配置商户信息: ```python app = Flask(__name__) wechat_pay = WeChatPay( appid='Your App ID', api_key='Your API Key', mch_id='Your Merchant ID', ) ``` 4. 创建接收微信支付结果通知的路由,并编写相应的处理逻辑: ```python @app.route('/notify', methods=['POST']) def handle_pay_result(): xml_data = request.data # 获取微信支付结果的XML数据 result = wechat_pay.parse_payment_result(xml_data) # 解析支付结果 # 处理支付结果,如更新订单状态、发送通知等 return wechat_pay.build_response('SUCCESS', 'OK') # 返回处理结果给微信支付平台 ``` 5. 创建发起支付请求的路由,并编写相应的处理逻辑: ```python @app.route('/pay', methods=['POST']) def handle_pay_request(): # 获取订单信息 order_id = request.form.get('order_id') total_fee = request.form.get('total_fee') # 构建支付请求参数 params = { 'body': 'Your Order Description', 'out_trade_no': order_id, 'total_fee': int(total_fee), 'spbill_create_ip': request.remote_addr, 'notify_url': 'Your Notify URL', 'trade_type': 'JSAPI', 'openid': 'User OpenID', } # 发起支付请求,并获取预支付会话标识 prepay_id = wechat_pay.order.create(params)['prepay_id'] # 生成微信支付JSAPI参数 jsapi_params = wechat_pay.jsapi.get_jsapi_params(prepay_id) return jsapi_params # 返回给前端,用于调起微信支付 ``` 6. 运行Flask应用: ```python if __name__ == '__main__': app.run() ``` 以上就是用Python编写对接微信商家支付的程序的基本步骤。通过这个程序,商家可以接收到微信支付的结果通知,并发起支付请求。当然,根据实际需求,还可以进一步完善程序,如添加订单状态查询功能、支付回调验证和处理、异常情况处理等。 ### 回答3: 用Python对接微信商家支付的程序需要使用到微信支付的开放接口,以下是一种示例的实现方法: 首先,需要安装 Python 的微信支付 SDK 依赖库,比如 `wechatpay` 或 `wechatpy` 等。可以使用 pip 安装这些库。 然后,在程序中引入对应的库,并进行必要的配置,包括商户号、API 密钥、证书路径等。根据微信支付开放接口的规范,使用统一下单接口生成订单,并传入必要的参数,比如订单金额、商品描述、回调URL等。 接着,将生成的订单信息通过支付接口进行签名,并将签名后的数据转换为 XML 格式。然后,使用 HTTP 请求库发送 POST 请求,将 XML 数据发送到微信支付接口。 微信支付接口接收到请求后,会验证签名并处理支付,返回支付结果给商户的回调 URL。商户收到回调后,需要验证签名、验证支付结果,比对订单金额等,并根据支付结果进行相应的业务逻辑处理。处理完成后,将结果再次返回给微信支付接口。 以上就是用 Python 编写对接微信商家支付的基本流程。需要注意的是,为了保证交易的安全性,可以进一步对接口调用过程加上异常处理、数据验证、日志记录等功能。另外,根据具体需求,还可以添加其他接口的调用,如退款、查询订单等。最后,程序中的商户号、API 密钥等关键信息需要妥善保管,避免泄露。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值