接上一篇:微信红包接入1-介入前准备
参考:
现金红包接口:https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5
接下来我们来说说代码集成,先把上一篇那个图拿过来:
从上图中我们实际第一步要走的就是接口调用:
接着我们看着官方接口描述文件来组织我们需要个个接口调用前的准备,当然还涉及前台的界面编写,我只说逻辑和贴图了,对具体的接口调用会贴代码:
用于企业向微信用户个人发现金红包
目前支持向指定微信用户的openid发放指定金额红包。(获取openid参见微信公众平台开发者文档: 网页授权获取用户基本信息)
如需操作请登录https://pay.weixin.qq.com/
对于如何获取openid,建议大家直接看官方的接口描述就行了,这里就不赘述了,没获取过的朋友可以更贴讨论一下;至于当前页面的:
创建红包流程,实际是微信提供的一种编辑模式,我们在集成中基本不用,除非你要看你的红包接口是否真的已经授权通过,想玩的朋友可以试试,无非就是创建一个对应活动的红包,在「管理红包」栏目中可以直接手动派发给一个用户;
所以个人觉得这个东东不应该放在文档中,让人照成误解,个人觉得微信文档写的有点文艺了有些地方,但是比支付宝那种技术宅写的好像人性一点;
2.接口调用请求说明
请求Url |
https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack |
是否需要证书 |
是(证书及使用说明详见商户证书) |
请求方式 |
POST |
项目结构:
在此也简单说明一下,我当前应用使用的一些东东,j2ee项目,给予maven构建,spring(core)+spring mvc+hibernate来搭建(根据项目的需求而定,再次我要对有人说hibernate不行的同学说一句,没有最好的,只有适合于项目的,我们要从开发周期成本等等方面考虑这个问题)。
在这里我把所有关于微信的基础信息配置,写成一个常量字典文件,首先我在里面加一行配置:
package cn.lexiuba.common;
import cn.lexiuba.model.weichart.AccessToken;
import cn.lexiuba.model.weichart.JSApiTicket;
/**
* Created by zhaojin on 15/5/6.
*/
public class WeiChartDict {
//以往的配置...
//现金红包接口(POST)
public final static String SEND_RED_PACK_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
}
从上图中我们实际第一步要走的就是接口调用:
字段名 |
字段 |
必填 |
示例值 |
类型 |
说明 |
---|---|---|---|---|---|
随机字符串 |
nonce_str |
是 |
5K8264ILTKCH16CQ2502SI8ZNMTM67VS |
String(32) |
随机字符串,不长于32位 |
签名 |
sign |
是 |
C380BEC2BFD727A4B6845133519F3AD6 |
String(32) |
详见签名生成算法 |
商户订单号 |
mch_billno |
是 |
10000098201411111234567890 |
String(28) |
商户订单号(每个订单号必须唯一) 组成: mch_id+yyyymmdd+10位一天内不能重复的数字。 接口根据商户订单号支持重入, 如出现超时可再调用。 |
商户号 |
mch_id |
是 |
10000098 |
String(32) |
微信支付分配的商户号 |
子商户号 |
sub_mch_id |
否 |
10000090 |
String(32) |
微信支付分配的子商户号,受理模式下必填 |
公众账号appid |
wxappid |
是 |
wx8888888888888888 |
String(32) |
商户appid |
提供方名称 |
nick_name |
是 |
天虹百货 |
String(32) |
提供方名称 |
商户名称 |
send_name |
是 |
天虹百货 |
String(32) |
红包发送者名称 |
用户openid |
re_openid |
是 |
oxTWIuGaIt6gTKsQRLau2M0yL16E |
String(32) |
接受收红包的用户 用户在wxappid下的openid |
付款金额 |
total_amount |
是 |
1000 |
int |
付款金额,单位分 |
最小红包金额 |
min_value |
是 |
1000 |
int |
最小红包金额,单位分 |
最大红包金额 |
max_value |
是 |
1000 |
int |
最大红包金额,单位分 ( 最小金额等于最大金额: min_value=max_value =total_amount) |
红包发放总人数 |
total_num |
是 |
1 |
int |
红包发放总人数 total_num=1 |
红包祝福语 |
wishing |
是 |
感谢您参加猜灯谜活动,祝您元宵节快乐! |
String(128) |
红包祝福语 |
Ip地址 |
client_ip |
是 |
192.168.0.1 |
String(15) |
调用接口的机器Ip地址 |
活动名称 |
act_name |
是 |
猜灯谜抢红包活动 |
String(32) |
活动名称 |
备注 |
remark |
是 |
猜越多得越多,快来抢! |
String(256) |
备注信息 |
商户logo的url |
logo_imgurl |
否 |
https://wx.gtimg.com/mch/img/ico-logo.png |
String(128) |
商户logo的url |
创建一个:CashRedPack.java,我放在model.weichart包下;
package cn.lexiuba.model.weichart;
import cn.lexiuba.model.Merchant;
import cn.lexiuba.utils.weichart.common.PayUtils;
import org.apache.http.util.TextUtils;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Created by zhaojin on 15/7/27.
* 参考:
* https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5
* https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_2
* http://blog.csdn.net/xiejiawanwei2_bb/article/details/43938695
*
* 2.接口调用请求说明
* 请求Url
* https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
* 是否需要证书
* 是(证书及使用说明详见商户证书)
* 请求方式
* POST
*
* 发送数据格式:xml
*
*
* 微信红包发送规则
* 现金红包
*
* 发送频率规则
* ◆ 每分钟发送红包数量不得超过1800个;
* ◆ 北京时间0:00-8:00不触发红包赠送;(如果以上规则不满足您的需求,请发邮件至[email protected]获取升级指引)
* 红包规则
* ◆ 单个红包金额介于[1.00元,200.00元]之间;
* ◆ 同一个红包只能发送给一个用户;(如果以上规则不满足您的需求,请发邮件至[email protected]获取升级指引)
* ◆ 一个商户一天内只能给同一个用户发送10个红包;(如果以上规则不满足您的需求,请发邮件至[email protected]获取升级指引)
* 针对一天能给用户发送红包个数已经可以在商户平台中:api安全-》现金红包API安全中修改,区间是1~100个
*/
@Entity
@Table(name = "t_cash_red_pack")
public class CashRedPack {
//请求提现、提现成功、提现失败、提现异常
public static final String STATUS_REQ_WITHDRAWALCASH = "请求提现";
public static final String STATUS_WITHDRAWALCASH_ERROR = "提现异常";
public static final String STATUS_WITHDRAWALCASH_FAIL = "提现失败";
public static final String STATUS_WITHDRAWALCASH_SUCCESS = "提现成功";
private int id;
/**
* 外键
* column:merchant_id
*/
private Integer merchantId;
/**
* 外键
* column: member_id
* 接收红包的会员的id
*/
private Integer memberId;
/**
* 随机字符串
* 随机字符串,不长于32位
* 是
*/
private String nonce_str;
/**
* 签名
* 详见签名生成算法
* https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=4_3
* 是
*/
private String sign;
/**
* 商户订单号
* 商户订单号(每个订单号必须唯一)
* 组成: mch_id+yyyymmdd+10位一天内不能重复的数字。
* 接口根据商户订单号支持重入, 如出现超时可再调用。
* 是
*/
private String mch_billno;
/**
* 商户号
* 微信支付分配的商户号
* 是
*/
private String mch_id;
/**
* 子商户号
* 微信支付分配的子商户号,受理模式下必填
* 否
*/
private String sub_mch_id;
/**
* 公众账号appid
* 商户appid
* 是
*/
private String wxappid;
/**
* 提供方名称
* 如:天虹百货
* 是
*/
private String nick_name;
/**
* 商户名称
* 红包发送者名称
* 如:天虹百货
* 是
*/
private String send_name;
/**
* 用户openid
* 接受收红包的用户用户在wxappid下的openid
* 是
*/
private String re_openid;
/**
* 付款金额
* 付款金额,单位分
* 是
*/
private String total_amount;
/**
* 最小红包金额
* 最小红包金额,单位分
* 是
*/
private String min_value;
/**
* 最大红包金额
* 最大红包金额,单位分
* ( 最小金额等于最大金额: min_value=max_value =total_amount)
* 是
*/
private String max_value;
/**
* 红包发放总人数
* 红包发放总人数total_num=1
* 是
*/
private String total_num;
/**
* 红包祝福语
* 如:感谢您参加猜灯谜活动,祝您元宵节快乐!
* 是
*/
private String wishing;
/**
* Ip地址
* 调用接口的机器Ip地址
* 是
*/
private String client_ip;
/**
* 活动名称
* 如:猜灯谜抢红包活动
* 是
*/
private String act_name;
/**
* 备注
* 备注信息
* 如:猜越多得越多,快来抢!
* 是
*/
private String remark;
/**
* 商户logo的url
* 如:https://wx.gtimg.com/mch/img/ico-logo.png
*/
private String logo_imgurl;
//以下字段分享时设置到对应接口,用于在分享之后朋友圈之类的界面上展示效果
/**
* 分享的内容
*/
private String share_content;
/**
* 分享的链接地址
*/
private String share_url;
/**
* 分享的图片url path
*/
private String share_imgurl;
//以下为请求成功之后返回的结果字段
/**
* 发放成功时间
* 例如:20150520102602
*/
private String send_time;
/**
* 微信单号
* 红包订单的微信单号
*/
private String send_listid;
/**
* 提现标记
* 请求提现、提现成功、提现失败
*/
private String status;
/**
* 返回信息
* 在返回状态码为FAIL时记录
*/
private String return_msg;
/**
* 错误代码
* 在业务结果result_code为FAIL时记录
*/
private String err_code;
/**
* 错误代码描述
* 在业务结果result_code为FAIL时记录
*/
private String err_code_des;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@NotNull(message = "商户不能为空!")
@Column(name = "merchant_id")
public Integer getMerchantId() {
return merchantId;
}
public void setMerchantId(Integer merchantId) {
this.merchantId = merchantId;
}
@NotNull(message = "会员不能为空!")
@Column(name = "member_id")
public Integer getMemberId() {
return memberId;
}
public void setMemberId(Integer memberId) {
this.memberId = memberId;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getMch_billno() {
return mch_billno;
}
public void setMch_billno(String mch_billno) {
this.mch_billno = mch_billno;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getSub_mch_id() {
return sub_mch_id;
}
public void setSub_mch_id(String sub_mch_id) {
this.sub_mch_id = sub_mch_id;
}
public String getWxappid() {
return wxappid;
}
public void setWxappid(String wxappid) {
this.wxappid = wxappid;
}
public String getNick_name() {
return nick_name;
}
public void setNick_name(String nick_name) {
this.nick_name = nick_name;
}
public String getSend_name() {
return send_name;
}
public void setSend_name(String send_name) {
this.send_name = send_name;
}
public String getRe_openid() {
return re_openid;
}
public void setRe_openid(String re_openid) {
this.re_openid = re_openid;
}
public String getTotal_amount() {
return total_amount;
}
public void setTotal_amount(String total_amount) {
this.total_amount = total_amount;
}
public String getMin_value() {
return min_value;
}
public void setMin_value(String min_value) {
this.min_value = min_value;
}
public String getMax_value() {
return max_value;
}
public void setMax_value(String max_value) {
this.max_value = max_value;
}
public String getTotal_num() {
return total_num;
}
public void setTotal_num(String total_num) {
this.total_num = total_num;
}
public String getWishing() {
return wishing;
}
public void setWishing(String wishing) {
this.wishing = wishing;
}
public String getClient_ip() {
return client_ip;
}
public void setClient_ip(String client_ip) {
this.client_ip = client_ip;
}
public String getAct_name() {
return act_name;
}
public void setAct_name(String act_name) {
this.act_name = act_name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getLogo_imgurl() {
return logo_imgurl;
}
public void setLogo_imgurl(String logo_imgurl) {
this.logo_imgurl = logo_imgurl;
}
public String getShare_content() {
return share_content;
}
public void setShare_content(String share_content) {
this.share_content = share_content;
}
public String getShare_url() {
return share_url;
}
public void setShare_url(String share_url) {
this.share_url = share_url;
}
public String getShare_imgurl() {
return share_imgurl;
}
public void setShare_imgurl(String share_imgurl) {
this.share_imgurl = share_imgurl;
}
public String getSend_time() {
return send_time;
}
public void setSend_time(String send_time) {
this.send_time = send_time;
}
public String getSend_listid() {
return send_listid;
}
public void setSend_listid(String send_listid) {
this.send_listid = send_listid;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
@Override
public String toString() {
return "CashRedPack{" +
"id=" + id +
", merchantId=" + merchantId +
", memberId=" + memberId +
", nonce_str='" + nonce_str + '\'' +
", sign='" + sign + '\'' +
", mch_billno='" + mch_billno + '\'' +
", mch_id='" + mch_id + '\'' +
", sub_mch_id='" + sub_mch_id + '\'' +
", wxappid='" + wxappid + '\'' +
", nick_name='" + nick_name + '\'' +
", send_name='" + send_name + '\'' +
", re_openid='" + re_openid + '\'' +
", total_amount='" + total_amount + '\'' +
", min_value='" + min_value + '\'' +
", max_value='" + max_value + '\'' +
", total_num='" + total_num + '\'' +
", wishing='" + wishing + '\'' +
", client_ip='" + client_ip + '\'' +
", act_name='" + act_name + '\'' +
", remark='" + remark + '\'' +
", logo_imgurl='" + logo_imgurl + '\'' +
", share_content='" + share_content + '\'' +
", share_url='" + share_url + '\'' +
", share_imgurl='" + share_imgurl + '\'' +
'}';
}
public static String generateMshBillNO(CashRedPack cashRedPack) {
StringBuilder stringBuilder = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
String dataFlag = simpleDateFormat.format(new Date());
return stringBuilder.append(cashRedPack.getMch_id()).append(dataFlag).append(PayUtils.buildRandom(10)).toString();
}
public static SortedMap
而微信真正需要的是下面xml格式的参数,在post请求中:
数据示例:
|
下面我准备先画界面了,有了参数那么把应用需要和用户交互的先拿到,其他的在考虑:
先开一个pre交易,渲染提现界面:
package cn.lexiuba.controller;
import cn.lexiuba.common.Dict;
import cn.lexiuba.common.DictError;
import cn.lexiuba.common.WeiChartDict;
import cn.lexiuba.exception.CommonErrorException;
import cn.lexiuba.exception.CommonException;
import cn.lexiuba.external.LuosimaoSMSAPI;
import cn.lexiuba.model.*;
import cn.lexiuba.model.weichart.Oauth2Token;
import cn.lexiuba.model.weichart.WeiChartUser;
import cn.lexiuba.model.weichart.req.ReceiveMessage;
import cn.lexiuba.service.*;
import cn.lexiuba.utils.ControllerUtils;
import cn.lexiuba.utils.weichart.CheckUtils;
import cn.lexiuba.utils.weichart.WeiChartUtil;
import cn.lexiuba.utils.weichart.common.JsApiUtil;
import cn.lexiuba.utils.weichart.common.MessageUtil;
import cn.lexiuba.utils.weichart.common.PayUtils;
import cn.lexiuba.utils.weichart.pay.PayCommonUtil;
import cn.lexiuba.utils.weichart.pay.XMLUtil;
import cn.lexiuba.web.AuthMethod;
import cn.lexiuba.web.SystemSessionContext;
import com.alipay.config.AlipayConfig;
import com.alipay.util.AlipayNotify;
import com.alipay.util.AlipaySubmit;
import com.google.gson.*;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.zhaojin.basic.common4.*;
import org.zhaojin.basic.model.Pager;
import org.zhaojin.basic.model.SystemContext;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Created by zhaojin on 15/4/8.
*/
@Controller
@RequestMapping("/puser")
public class FPUserController extends BaseController {
//省略既往代码
@AuthMethod(role = "RolePuser", pageTitle = "提现")
@RequestMapping(value = "/withdrawalCash", method = RequestMethod.GET)
public String withdrawalCash(HttpSession httpSession, Model model) {
Object tmpLoginUser = httpSession.getAttribute(Dict.LOGGED_PUSER2SESSION);
if (null == tmpLoginUser || !(tmpLoginUser instanceof PUser)) {
throw new CommonErrorException("亲,你确定你登录了自己的账号?不要企图搞破坏!");
} else {
PUser pUser = (PUser) tmpLoginUser;
if (pUser.getWealthValue() <= 0) {
throw new CommonErrorException("亲,您的余额不足,暂时不能进行该操作!");
} else {
model.addAttribute(pUser);
return "foreground/puser/withdrawalCash"
}
}
}
首先我项目对应包下有针对会员的一个FPUserController(使用spring mvc方式定),个人习惯接口、方法、jsp3个文件的名称统一,之后方便查找,之后从session中判断登录用户是否存在,存在再简单的判断用户的秀币账户是否有钱,一切成功跳转到充值的录入页面;
接下来我就需要定义这个jsp页面,在这里先说一下,html前端为了做自适应我使用bootstrap框架,下面针对其就不多说啥了,个人感觉用来用去还是这套流程比较顺手,简单交流一下,个人觉得要学bootstrap,如果单纯从使用角度来说,只要弄清楚它是如何布局的,也就是栅格系统,其他的在项目中需要什么拿过来直接用就ok;
先看最后的页面效果:
根据业务需求和接口分析(https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5),我们页面中仅仅需要用户录入他需要提现多少,其他的参数都是常量或者计算出来的,所以界面很简单;
我在对应的项目目录下新建对应的jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
提现规则:1秀币可兑换1元人民币,最终将以微信红包方式返还
关键的一个引入就是与当前jsp对应的resources/js/foreground/puser/withdrawalCash.js,我之前也说,我习惯统一名称。
下面是该脚本的代码:
/**
* Created by zhaojin on 15/5/4.
*/
gloablObj.option.pageInit = function (memberId) {
if (gloablObj.dict.isPhone) {
$('#formPanel').css({'max-width': '400px'});
}
var total_amount = $(':input[name="total_amount"]');
var totalAmountShow = $('#totalAmountShow');
total_amount.on('change', function () {
var me = $(this);
var val = me.val();
var num = new Number(val);
if (isNaN(num)) {
me.val('');
$.scojs_message('提现金额必须为大于1分!', $.scojs_message.TYPE_ERROR);
} else {
//微信只能支持到分
num = num.toFixed(2);
totalAmountShow.html(num + '元');
}
});
$(':input[name="inlineRadioOptions"]').each(function () {
var item = $(this);
item.on('change', function () {
var val = item.val();
var num = new Number(val);
if (isNaN(num)) {
$.scojs_message('充值数必须为大于1分!', $.scojs_message.TYPE_ERROR);
} else {
//微信只能支持到分
num = num.toFixed(2);
totalAmountShow.html(num + '元');
total_amount.val(num);
}
});
});
var registConfirmEvent = function(){
$('#confirmBtn').on('click', function () {
var me = $(this);
me.off('click');
//微信一分为单位,我这里以元作为单位,需要做个转换
var totalAmountVal = total_amount.val() * 100;
if (!playI.obj.isDecimal(totalAmountVal) || parseFloat(totalAmountVal) < 0.01) {
$.scojs_message('充值数必须为大于1分!', $.scojs_message.TYPE_ERROR);
return;
} else {
var merchantName = $(':input[name="merchantName"]').val();
var openid = $(':input[name="openid"]').val();
var pageTitle = $(':input[name="pageTitle"]').val();
var logo_imgurl = $(':input[name="logo_imgurl"]').val();
me.attr({'disabled': 'disabled'});
gloablObj.interaction.doPost(gloablObj.dict.APP_CONTEXT + '/puser/withdrawalCashConfirm', {
nick_name: merchantName,//提供方名称
send_name: merchantName,//商户名称
re_openid: openid,//接受收红包的用户用户在wxappid下的openid
total_amount: totalAmountVal,
min_value: totalAmountVal,
max_value: totalAmountVal,
total_num: 1,
wishing: '提现红包 —— www.lexiuba.com 人人都有奖金的欢乐秀场',//红包祝福语
act_name: '提现红包',//活动名称
remark: '提现规则:1秀币可兑换1元人民币,最终将以微信红包方式返还',//备注信息
logo_imgurl: logo_imgurl,//商户logo的url
share_content: '乐秀吧提现',
share_url: 'www.lexiuba.com',
share_imgurl: logo_imgurl
}, function (res, msg) {
gloablObj.interaction.doSomeThing(function () {
//直接返回个人管理页面
gloablObj.interaction.redirectTo(gloablObj.dict.APP_CONTEXT + '/puser/manage')
}, 2000);
}, function () {
//交易完成后始终会回调当前函数
me.removeAttr('disabled');
}, function () {
registConfirmEvent();
});
}
});
}
registConfirmEvent();
};
至于里面关键的就是收集参数发了一个
的交易,需要注意的是,我把一些非关键的参数都在这里配置了,参数的含义就不用我多说了,下面看关键的确认交易:
交易定义:
@AuthMethod(role = "RolePuser", pageTitle = "提现")
@RequestMapping(value = "/withdrawalCashConfirm", method = RequestMethod.POST)
@ResponseBody
public String withdrawalCashConfirm(HttpSession httpSession, @Validated CashRedPack cashRedPack,
BindingResult br) {
AjaxObj ao = null;
Object tmpLoginUser = httpSession.getAttribute(Dict.LOGGED_PUSER2SESSION);
if (null == tmpLoginUser || !(tmpLoginUser instanceof PUser)) {
throw new CommonException("亲,你确定你登录了自己的账号?不要企图搞破坏!");
} else {
if (br.hasErrors()) {
throw new CommonException(String.valueOf(EasyUIJsonUtils.getErroredRes2EsayUI(br.getErrorCount(), br.getAllErrors()).get(EasyUIJsonUtils.ERRORMSG)));
} else {
PUser pUser = (PUser) tmpLoginUser;
Merchant lexiu = merchantService.loadByUsername("lexiu");
//转换分到元(参数中记录的是微信需要的分)
BigDecimal transform = new BigDecimal(100);
BigDecimal needDeduct = NumberUtils.createBigDecimal(cashRedPack.getTotal_amount()).divide(transform);
if (-1 == new BigDecimal(pUser.getWealthValue()).compareTo(needDeduct)) {
throw new CommonException("亲,您的余额不足,暂时不能进行该操作!");
} else {
//TODO:调用微信现金红包
//随机字符串
cashRedPack.setNonce_str(WeiChartUtil.createNonceStr(32));
cashRedPack.setMch_billno(CashRedPack.generateMshBillNO(cashRedPack));
cashRedPack.setMch_id(WeiChartDict.MchId);
cashRedPack.setWxappid(WeiChartDict.AppID);
cashRedPack.setClient_ip(Dict.SERVER_IP_ADDRESS);
cashRedPack.setMemberId(pUser.getId());
cashRedPack.setMerchantId(lexiu.getId());
//TODO:添加充值记录
cashRedPack.setStatus(CashRedPack.STATUS_REQ_WITHDRAWALCASH);
//存储之后包含id信息
cashRedPack = puserService.addCashRedPack(cashRedPack);
Map res = null;
try {
res = WeiChartUtil.callSendRedPack(cashRedPack);
logger.error("WeiChartUtil.callSendRedPack res:" + res);
} catch (Exception e) {
e.printStackTrace();
cashRedPack.setStatus(CashRedPack.STATUS_WITHDRAWALCASH_ERROR);
puserService.updateCashRedPack(cashRedPack);
throw new CommonException("向微信申请提现异常,请稍后再试!");
}
//先存储签名数据
String sign = res.get("req_sign");
cashRedPack.setSign(sign);
//解析返回结果:
// 返回状态码
if (null != res) {
String return_code = res.get("return_code");
if (return_code.equals("SUCCESS")) {
//以下字段在return_code为SUCCESS的时候有返回
String result_code = res.get("result_code");
if (result_code.equals("SUCCESS")) {
// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
//以下字段有必要的话可以校验一下
String mch_billno = res.get("mch_billno");
String mch_id = res.get("mch_id");
String wxappid = res.get("wxappid");
String re_openid = res.get("re_openid");
String total_amount = res.get("total_amount");
//发放成功时间
String send_time = res.get("send_time");
//微信单号
String send_listid = res.get("send_listid");
//TODO:更新充值记录
cashRedPack.setStatus(CashRedPack.STATUS_WITHDRAWALCASH_SUCCESS);
cashRedPack.setSend_time(send_time);
cashRedPack.setSend_listid(send_listid);
puserService.updateCashRedPack(cashRedPack,pUser,needDeduct);
logger.error("STATUS_WITHDRAWALCASH_SUCCESS updateCashRedPack:" + cashRedPack);
ao = new AjaxObj(AjaxObj.SUCCESS, "提现申请成功,红包已经发放,请注意查收哦!", JsonUtil.getInstance().obj2json(res));
} else {
//发送红包业务逻辑失败处理
String err_code = res.get("err_code");
String err_code_des = res.get("err_code_des");
cashRedPack.setStatus(CashRedPack.STATUS_WITHDRAWALCASH_FAIL);
cashRedPack.setErr_code(err_code);
cashRedPack.setErr_code_des(err_code_des);
puserService.updateCashRedPack(cashRedPack);
ao = new AjaxObj(AjaxObj.ERROR, "提现失败(返回失败原因:[" + err_code + "] " + err_code_des + ")");
}
logger.error("return res :" + ao);
return JsonUtil.getInstance().obj2json(ao);
} else {
String return_msg = res.get("return_msg");
cashRedPack.setStatus(CashRedPack.STATUS_WITHDRAWALCASH_FAIL);
cashRedPack.setReturn_msg(return_msg);
puserService.updateCashRedPack(cashRedPack);
throw new CommonException("向微信申请提现异常(返回错误信息:" + return_msg + "),请稍后再试!");
}
} else {
cashRedPack.setStatus(CashRedPack.STATUS_WITHDRAWALCASH_ERROR);
puserService.updateCashRedPack(cashRedPack);
throw new CommonException("向微信申请提现异常(请求返回信息为空),请稍后再试!");
}
}
}
}
}
其实最基础的就是红包接口调用的代码段:
res = WeiChartUtil.callSendRedPack(cashRedPack);
下面贴出来:
public static Map callSendRedPack(CashRedPack cashRedPack) throws JDOMException, IOException, CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, DocumentException {
SortedMap params = new TreeMap();
params.put("nonce_str", cashRedPack.getNonce_str());
params.put("mch_billno", cashRedPack.getMch_billno());
params.put("mch_id", cashRedPack.getMch_id());
if (null != cashRedPack.getSub_mch_id() && TextUtils.isEmpty(cashRedPack.getSub_mch_id())) {
params.put("sub_mch_id", cashRedPack.getSub_mch_id());
}
params.put("wxappid", cashRedPack.getWxappid());
params.put("nick_name", cashRedPack.getNick_name());
params.put("send_name", cashRedPack.getSend_name());
params.put("re_openid", cashRedPack.getRe_openid());
params.put("total_amount", cashRedPack.getTotal_amount());
params.put("min_value", cashRedPack.getMin_value());
params.put("max_value", cashRedPack.getMax_value());
params.put("total_num", cashRedPack.getTotal_num());
params.put("wishing", cashRedPack.getWishing());
params.put("client_ip", cashRedPack.getClient_ip());
params.put("act_name", cashRedPack.getAct_name());
params.put("remark", cashRedPack.getRemark());
params.put("logo_imgurl", cashRedPack.getLogo_imgurl());
params.put("share_content", cashRedPack.getShare_content());
params.put("share_url", cashRedPack.getShare_url());
params.put("share_imgurl", cashRedPack.getShare_imgurl());
//获取签名
String sign = PayUtils.createSign("UTF-8", params);
params.put("sign", sign);
log.error("sign::" + sign);
String requestXML = PayUtils.getRequestXml(params);
log.error("requestXML::" + requestXML);
Map res = ConnectUtil.httpRequestUseCert(WeiChartDict.SEND_RED_PACK_URL, WeiChartDict.CERT_PATH, cashRedPack.getMch_id(), requestXML);
//TODO:为了在控制器中做校验使用
res.put("req_sign",sign);
log.error("res::" + res);
return res;
}
请求方法:
public static Map httpRequestUseCert(String url, String certPath, String mch_id, String requestXML) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, DocumentException {
// 将解析结果存储在HashMap中
Map res = new HashMap();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File(certPath));
try {
keyStore.load(instream, mch_id.toCharArray());
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mch_id.toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpPost = new HttpPost(url);
StringEntity reqEntity = new StringEntity(requestXML, "utf-8");
// 设置类型
reqEntity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(reqEntity);
log.error("executing request" + httpPost.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
log.error("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
// 从request中取得输入流
InputStream inputStream = entity.getContent();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList) {
res.put(e.getName(), e.getText());
}
// 释放资源
inputStream.close();
inputStream = null;
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
return res;
}
注:不过上面的代码我今天才写完,一起把博客更新了,如果之后测试发现有啥问题,我在修正;
当前的代码已经部署到「乐秀吧」服务号,进行过测试,应该没啥大问题,如有疑问请关注下面的服务号,我们会进行解答。