微信支付现金红包,微信网页授权 - Java 开发

微信支付之现金红包 - Java 开发

本文章是首次接触微信支付所写下,如果对您有帮助希望点个赞。若有疑问或不对的地方欢迎各位留言或私信指正交流

基本原理就是调用微信现金红包接口(ssh带证书和签名),传入参数,获取响应

  • 接口url:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
  • 入参:WxRedpackSdkVo
  • 出参:WxRedpackSdkRes
  • 入参和出参均为 xml格式

另外 binarywang的WxJava开源项目已经把这些都封装好了,可以直接食用https://gitee.com/binary/weixin-java-tools

产品简介

现金红包,是营销工具之一,受广大商户与用户的喜爱

商户可以通过公众号或者服务通知向用户发放现金红包

用户领取红包后,资金到达用户零钱

若用户未领取,资金将会在24小时后退回商户的微信支付账户中

必备要素 【对照官网进行准备】

  1. 一个正常的微信商户号
  2. 开通现金红包权限
  3. 一个公众号(服务号)并且绑定到该商户号
  4. 在商户平台下载 API证书
  5. 给你的商户充上两块钱
  6. 在微信平台设置一些必要参数

支付接口核心代码

SSL 请求调用 微信的现金红包接口

// xian'j Url 地址
private static final SENDREDPACK_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
// 证书存放地址
private static final PKCS12_FILE_PATH = "../../apiclient_cert.p12";

/**
 * ssl 请求
 *
 * @param url 
 * @param reqXmlData xml 格式请求参数
 * @param mchId 商户 ID
 * @return
 */
private static String ssl(String url, String reqXmlData, String mchId) {
    // 证书准备
    char[] password = mchId.toCharArray();
    KeyStore ks = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(WXPayConstants.CERT_PATH_SERVER);
    ks.load(instream, password);
    log.info("Load keyStore success");


    // 实例化密钥库 & 初始化密钥工厂 && 创建 SSL 连接
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(ks, password);
    BasicHttpClientConnectionManager connManager = getBasicHttpClientConnectionManager(kmf);
    log.info("nit keyManagerFactory success。Create SSLContext & connManager init success");


    // 准备 POST 请求
    HttpPost httpPost = new HttpPost(url);
    RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(8 * 1000).setConnectTimeout(6 * 1000).build();
    httpPost.setConfig(requestConfig);
    httpPost.addHeader("Content-Type", "text/xml");
    httpPost.addHeader("User-Agent", USER_AGENT + " " + mchId);
    httpPost.setEntity(new StringEntity(reqXmlData, "UTF-8"));
    log.info("Create httpClient & httpPost success");


    // 通过 HttpClient 发起 POST 请求,HttpResponse 接收请求结果
    HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
    HttpResponse httpResponse = httpClient.execute(httpPost);

    // 返回响应体内容 xml(String)
    return EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
}

请求 xml 示列

<xml>
    <mch_id>8888888888</mch_id>
    <wxappid>wxappid88888888888</wxappid>
    <re_openid>oPPyl1cjU8fcOps-qKwIOY9XuyI8</re_openid>
    
    <total_num>1</total_num>
    <total_amount>1000</total_amount>
    <wishing>恭喜发财,大吉大利</wishing>
    <send_name>白嫖不对哦</send_name>
    
    <sign>00OSD69724074UI982867A1607A87UF2V</sign>
    <nonce_str>DmTGX1VZ9KoJirLKwo86HnnKhFo0BtLH</nonce_str>
    <mch_billno>8888888888202212128888888888</mch_billno>

    <act_name>快快要请好友一起领取现金红包吧</act_name>
    <scene_id>PRODUCT_3</scene_id>
    <client_ip>118.122.18.168</client_ip>
    <remark>感谢支持现金红包</remark>
    <risk_info/>
</xml>

响应 xml 示列

<xml>
    <return_code><![CDATA[SUCCESS]]></return_code>
    <return_msg><![CDATA[发放成功]]></return_msg>
    <result_code><![CDATA[SUCCESS]]></result_code>
    <err_code><![CDATA[SUCCESS]]></err_code>
    <err_code_des><![CDATA[发放成功]]></err_code_des>
    <mch_billno><![CDATA[8888888888202212128888888888]]></mch_billno>
    <mch_id><![CDATA[8888888888]]></mch_id>
    <wxappid><![CDATA[wxappid88888888888]]></wxappid>
    <re_openid><![CDATA[oPPyl1cjU8fcOps-qKwIOY9XuyI8]]></re_openid>
    <total_amount>1000</total_amount>
    <send_listid><![CDATA[1000041701202212053666666888888]]></send_listid>
</xml>

接口请求入参类

WxRedpackSdkVo.class

package com.lcl.redpacket.config.wx;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * 微信发放红包接口 sdkVo
 *
 * @author lichenglong
 * @date 2022-11-24 10:24
 */
@Data
@Component
@ConfigurationProperties(prefix = "wx.redpack")
public class WxRedpackSdkVo implements Serializable {
    private String nonce_str;
    private String sign;
    private String mch_billno;
    private String mch_id;
    private String wxappid;
    private String send_name;
    private String re_openid;
    private int total_amount;
    private int total_num;
    private String wishing;
    private String client_ip;
    private String act_name;
    private String remark;
    private String scene_id;
    private String risk_info;
}

接口响应结果类

WxRedpackSdkRes.class

package com.lcl.redpacket.config.wx;

import java.io.Serializable;

/**
 * 微信现金红包接口 sdkRes
 *
 * @author lichenglong
 * @date 2022-11-24 10:24
 */
@Data
public class WxRedpackSdkRes implements Serializable {
    /**
     * 返回状态码 SUCCESS/FAIL
     */
    private String return_code;
    /**
     * 返回信息
     */
    private String return_msg;

    // ===== 以下字段在return_code为SUCCESS的时候有返回 =====
    /**
     * 业务结果 SUCCESS/FAIL
     * 注意:当状态为FAIL时,存在业务结果未明确的情况。所以如果状态是FAIL,请务必再请求一次查询接口[请务必关注错误代码(err_code字段),通过查询得到的红包状态确认此次发放的结果。],以确认此次发放的结果。
     */
    private String result_code;
    /**
     * 错误码信息
     * 注意:出现未明确的错误码(SYSTEMERROR等)时,请务必用原商户订单号重试,或者再请求一次查询接口以确认此次发放的结果。
     */
    private String err_code;
    /**
     * 错误代码描述
     */
    private String err_code_des;

    // ===== 以下字段在return_code和result_code都为SUCCESS的时候有返回 =====
    /**
     * 商户订单号:必须唯一
     * 组成:mch_id+yyyymmdd+10位一天内不能重复的数字
     */
    private String mch_billno;
    /**
     * 商户号
     */
    private String mch_id;
    /**
     * 公众账号 appid
     */
    private String wxappid;
    /**
     * 接受收红包的用户
     */
    private String re_openid;
    /**
     * 付款金额,单位分
     */
    private int total_amount;
    /**
     * 红包订单的微信单号
     */
    private String send_listid;
}

微信支付之网页授权 - Java 开发

本文章是首次接触微信支付所写下,如果对您有帮助希望点个赞。若有疑问或不对的地方欢迎各位留言或私信指正交流

用户在微信中访问第三方网页,公众号可以通过微信网页授权机制获取用户基本信息,实现业务逻辑:网页授权 | 微信开放文档 (qq.com)

前期踩坑:

  1. 最开始的时候是一个订阅号,普通订阅号无法拉起微信的授权页面
  2. 然后改方案通过用户向订阅号号发送消息,后端去监听这个消息就能拿到用户的openId
  3. 再然后就是订阅号不能发送现金红包,就换了一个服务号

最终方案:

  1. 用户向服务号发消息
  2. 获取到openId -> 返回一个超链接跳转html资源文件
  3. h5中再请求自己的后端接口并带上 openId

必备要素

  1. 有自己的域名,比如www.bigbanan.com
  2. 在公众平台官网中开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息的配置选项中,修改授权回调域名
  3. 用户关注了公众号后,才能发消息才能调用成功的

用户的 openid 在各个公众号下不同、用户的 UnionID 在同一个开发者下的不同公众号相同

方案一:拉起微信的授权页面

官网文档授权方式

  • snsapi_base - 只能获取 openid
  • snsapi_userinfo - 可获取 openid、unionid、头像、昵称等信息

拉起授权核心代码

/**
 * 1.拉起微信授权页面获取 code、用户同意授权后跳转到后端 myApi 接口
 */
@RequestMapping(value = "/wxOauth2H5", method = {RequestMethod.GET, RequestMethod.POST})
public String wxOauth2H5(){
    private String OAUTH2_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
    private static final String APPID = "wxappid88888888888";
    private static final String REDIRECT_URL = "www.bigbanana.com/wxOauth2AccessToken";
    private static final String OAUTH_TYPE = "snsapi_base";
    return OAUTH2_URL.replace("APPID",APPID).replace("REDIRECT_URI",encodeurl).replace("SCOPE",OAUTH_TYPE)
}

/**
 * 2.在 wxOauth2AccessToken 接口中,使用 code 换取 access_token
 */
@RequestMapping(value = "/wxOauth2AccessToken", method = {RequestMethod.GET, RequestMethod.POST})
public String myApi(@RequestParam("code") String code){
    private String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
    private static final String APPID = "wxappid88888888888";
    private static final String SECRET = "secret88888888888";
    OAUTH2_ACCESS_TOKEN_URL.replace("APPID",APPID).replace("SECRET","SECRET").replace("CODE",CODE);
	// 通过http工具发起请求(略)
    String response = HttpUtil.get(url);
    // 返回内容大概长这样{ "access_token":"ACCESS_TOKEN", "expires_in":7200, "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE" }
    return response;
}

方案二:通过客服消息

通过指定消息服务器方法,用户向公众号发送的消息可以传到后代 api 进行处理

/**
 * Get 请求用于保存服务器配置时候的验证
 */
@RequestMapping(value = "/receiveEvent", method = RequestMethod.GET)
public String receiveEventGet(@RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) {
    try {
        // 请求参数非空判断
        if (StrUtil.hasBlank(signature, timestamp, nonce, echostr)) {
            log.error("存在空参数");
            return "error";
        }
        // 校验签名
        if (checkSign(signature, timestamp, nonce)) {
            return echostr;
        }
        return "signature check failed.";
    } catch (Exception e) {
        log.error("yun callback error.", e);
        return "yun callback error.";
    }
}

@RequestMapping(value = "/receiveEvent", method = RequestMethod.POST)
public String receiveEventPost(HttpServletRequest request) {
    log.info("==================== Listener receive event post ====================");
    String xmlData = WXPayUtil.getDataBodyByRequest(request);
    if (StrUtil.isBlank(xmlData)) {
        return StringUtils.EMPTY;
    }
    Map<String, String> xmlMapReq;
    try {
        xmlMapReq = WXPayUtil.xmlToMap(xmlData);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    log.info("xmlData to xmlMap -> {}", xmlMapReq);

    // 消息类型
    String msgType = xmlMapReq.get("MsgType");
    if (null == msgType) {
        return StringUtils.EMPTY;
    }
    // 根据不同消息类型做不同处理
    switch (msgType) {
        case WXPayConstants.MSG_TYPE_TEXT:
            String content = xmlMapReq.get("Content");
            if (StrUtil.isNotBlank(content) && content.equals("领红包")) {
                String str = "<a href='https://www.xxx.cn/redPack/index.html?openId=" + xmlMapReq.get("FromUserName") + "'>点我领红包</a>";
                Map<String, String> resMap = new HashMap<>();
                resMap.put("ToUserName", xmlMapReq.get("FromUserName"));
                resMap.put("FromUserName", xmlMapReq.get("ToUserName"));
                resMap.put("CreateTime", "123456");
                resMap.put("MsgType", "text");
                resMap.put("Content", str);
                String resXml;
                try {
                    resXml = WXPayUtil.mapToXml(resMap);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                log.info("用户发送了文本内容,返回:" + resXml);
                return resXml;
            }
            break;
        default:
            break;
    }
    return StringUtils.EMPTY;
}

/*
 * 签名验证
 */
public boolean checkSign(String signature, String timestamp, String nonce) {
    // 生成签名
    List<String> signList = new ArrayList<>();
    // 配置文件中读取token(微信后台配置的Token)
    signList.add("mykey");
    signList.add(timestamp);
    signList.add(nonce);
    // 1. 将token、timestamp、nonce三个参数进行字典序排序
    signList.sort(Comparator.naturalOrder());
    // 2. 将三个参数字符串拼接成一个字符串进行sha1加密
    StringBuilder signSb = new StringBuilder();
    signList.forEach(signSb::append);
    String signStr = DigestUtils.sha1Hex(signSb.toString());
    // 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
    log.info("signature=[{}], signStr=[{}]", signature, signStr);
    return signature.equals(signStr);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值