微信红包接入2-项目集成
接上一篇:微信红包接入1-介入前准备
参考:
现金红包接口:https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5
接下来我们来说说代码集成,先把上一篇那个图拿过来:
从上图中我们实际第一步要走的就是接口调用:
接着我们看着官方接口描述文件来组织我们需要个个接口调用前的准备,当然还涉及前台的界面编写,我只说逻辑和贴图了,对具体的接口调用会贴代码:
接口详细介绍
1.红包发放说明
用于企业向微信用户个人发现金红包
目前支持向指定微信用户的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不行的同学说一句,没有最好的,只有适合于项目的,我们要从开发周期成本等等方面考虑这个问题)。
在这里我把所有关于微信的基础信息配置,写成一个常量字典文件,首先我在里面加一行配置:
<span style="font-size:12px;">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";
}</span>
可以看见这个url,不需要我们进行参数的替换,因为其是POST的嘛,哈哈,有点废话了;
从上图中我们实际第一步要走的就是接口调用:
3.请求参数
字段名 |
字段 |
必填 |
示例值 |
类型 |
说明 |
---|---|---|---|---|---|
随机字符串 |
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 |
这个接口的请求参数也是相当的多了,我们接下来一个个过,首先我希望把他作为一个POJO,并和我的数据库进行映射起来,之后在对应的「查看提现记录」的模块中使用,其实对于这种和账户、资金相关的交易都需要记录流水,我这里简单化了:
创建一个: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* <p/>* 2.接口调用请求说明* 请求Url* https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack* 是否需要证书* 是(证书及使用说明详见商户证书)* 请求方式* POST* <p/>* 发送数据格式:xml* <p/>* <p/>* 微信红包发送规则* 现金红包* <p/>* 发送频率规则* ◆ 每分钟发送红包数量不得超过1800个;* ◆ 北京时间0:00-8:00不触发红包赠送;(如果以上规则不满足您的需求,请发邮件至wxhongbao@tencent.com获取升级指引)* 红包规则* ◆ 单个红包金额介于[1.00元,200.00元]之间;* ◆ 同一个红包只能发送给一个用户;(如果以上规则不满足您的需求,请发邮件至wxhongbao@tencent.com获取升级指引)* ◆ 一个商户一天内只能给同一个用户发送10个红包;(如果以上规则不满足您的需求,请发邮件至wxhongbao@tencent.com获取升级指引)* 针对一天能给用户发送红包个数已经可以在商户平台中: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@GeneratedValuepublic 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;}@Overridepublic 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<Object, Object> formate(CashRedPack cashRedPack) {SortedMap<Object, Object> params = new TreeMap<Object, Object>();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());return params;}
}
基本就是把上面请求参数表格中的字段都列进去了,因为根据我自身的需要有多加了id、会员id和商户id以便在之后进行查询或者其他操作;
而微信真正需要的是下面xml格式的参数,在post请求中:
数据示例:
<xml> |
这里就涉及到对象和xml格式的映射,其实有很多框架在干这件事,到时候会贴出对应的工具接口方法(对于这种非和本文相关的技术性问题,我就不深入了,大家可以自行讨论:))
下面我准备先画界面了,有了参数那么把应用需要和用户交互的先拿到,其他的在考虑:
先开一个pre交易,渲染提现界面:
<span style="font-size:12px;">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 {//省略既往代码</span>
<span style="white-space:pre"><span style="font-size:12px;"> </span></span>
<pre name="code" class="java"><span style="font-size:12px;"> @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"
<span style="white-space: pre;"> </span> }
}}
}
首先我项目对应包下有针对会员的一个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" %><!DOCTYPE html>
<html>
<head><jsp:include page="/WEB-INF/jsp/foreground/include.jsp"><jsp:param name="pageTitle" value="${pageTitle}"/><jsp:param name="needScojsStyle4Valid" value="true"/></jsp:include><style>.formInput, textarea, input[type="text"], input[type="password"], input[type="tel"], input[type="email"], input[type="number"], input[type="search"], select[multiple], select[size], input[type="date"] {width: 55%;}.form-horizontal .radio-inline {padding-left: 30px;margin-left: 0px;min-width: 130px;min-height: 50px;line-height: 16px;font-size: 22px;}.unitLabel {line-height: 40px;margin-left: 5px;}@media (min-width: 768px) {.control-group {margin: 0 auto;width: 560px;}}</style>
</head>
<body id="pageBody">
<jsp:include page="/WEB-INF/jsp/foreground/commnav.jsp"><jsp:param name="pageTitle" value="${pageTitle}"/>
</jsp:include>
<div class="container pageContainer"><div class="row" class="form-group" style="padding: 10px 10px 10px 10px;"><div class="panel panel-default" id="formPanel" style="margin: 0 auto;"><div class="panel-heading" style="padding: 0px;"><div class="alert marginNone" role="alert"><p>提现规则:1秀币可兑换1元人民币,最终将以微信红包方式返还</p></div></div><div class="panel-body" id="userNameFormBox" style="padding: 0px;"><form class="form-horizontal pageForm" style="padding: 10px; padding-top: 30px;"id="submitByUserNameForm"><fieldset><div class="container" style="padding: 0px;"><div class="row"><div class="col-xs-12 col-md-6"><label class="control-group"><div class="control-label formLabel">提现金额:</div><input class="formInput" type="text" placeholder="请输入提现金额,红包方式返还"name="total_amount" id="total_amount"/><spanclass="unitLabel">秀币</span></label><label class="control-group"><div class="control-label formLabel">快速选择:</div></label><label class="control-group"><label class="radio-inline"><input type="radio" name="inlineRadioOptions" id="inlineRadio1"value="100"> <spanclass="redColor">100</span> <span class="font14">秀币</span></label><label class="radio-inline"><input type="radio" name="inlineRadioOptions" id="inlineRadio2"value="300"> <spanclass="redColor">300</span> <span class="font14">秀币</span></label><label class="radio-inline"><input type="radio" name="inlineRadioOptions" id="inlineRadio3"value="1000"> <spanclass="redColor">1000</span> <span class="font14">秀币</span></label><label class="radio-inline"><input type="radio" name="inlineRadioOptions" id="inlineRadio4"value="5000"> <spanclass="redColor">5000</span> <span class="font14">秀币</span></label></label></div></div><div class="row" style="padding: 15px;"><p>你希望提现:<strong id="totalAmountShow"></strong></p></div></div></fieldset></form></div><div class="panel-footer"><button type="button" class="btn btn-danger btn-lg btn-block" id="confirmBtn">确认</button></div></div></div></div>
<!-- /container --><!-- other -->
<input name="pageTitle" value="${pageTitle}" type="hidden">
<input name="merchantName" value="${appName}" type="hidden">
<input name="openid" value="${loginPuser.openid}" type="hidden">
<input name="logo_imgurl" value="<%=request.getContextPath()%>/resources/img/lexiu_logo_108.png" type="hidden">
<jsp:include page="/WEB-INF/jsp/foreground/footer.jsp"><jsp:param name="pageInitScript" value="resources/js/foreground/puser/withdrawalCash.js"/><jsp:param name="needWeiChartApi" value="true"/>
</jsp:include>
<!-- /other -->
</body>
</html>
上面引入了几个include文件,为非就是导入通用的头部底部,需要的相关资源文件(如引入bootstrap等等,这里我就不贴了,需要的可以关注我们的服务号,发送请求,我会把这几个文件的源码打个zip共享出来;)
关键的一个引入就是与当前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下的openidtotal_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的urlshare_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();};
简单解释一下,我当前项目有一个总的全局对象,gloabelObj,之后所有东西都往上附加,比如文档的初始化函数,这个只是我个人的习惯,其实这个pageInit函数是在页面加载完毕之后被调用;
至于里面关键的就是收集参数发了一个
的交易,需要注意的是,我把一些非关键的参数都在这里配置了,参数的含义就不用我多说了,下面看关键的确认交易:
交易定义:
@AuthMethod(role = "RolePuser", pageTitle = "提现")@RequestMapping(value = "/withdrawalCashConfirm", method = RequestMethod.POST)@ResponseBodypublic 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<String, String> 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<String, String> callSendRedPack(CashRedPack cashRedPack) throws JDOMException, IOException, CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, DocumentException {SortedMap<Object, Object> params = new TreeMap<Object, Object>();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<String, String> 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<String, String> httpRequestUseCert(String url, String certPath, String mch_id, String requestXML) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, DocumentException {// 将解析结果存储在HashMap中Map<String, String> res = new HashMap<String, String>();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 certsSSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();// Allow TLSv1 protocol onlySSLConnectionSocketFactory 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<Element> 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;}
以上就是核心的一些代码块,最终实现出来的效果如下:
到此结束;
注:不过上面的代码我今天才写完,一起把博客更新了,如果之后测试发现有啥问题,我在修正;
当前的代码已经部署到「乐秀吧」服务号,进行过测试,应该没啥大问题,如有疑问请关注下面的服务号,我们会进行解答。
微信红包接入2-项目集成相关推荐
- 微信红包项目测试小结
没有总结与思考的项目是不完整的项目 第一步:测试需求分析 经过需求分析,对原始需求列表中列出的每一个需求点,找到我们需要测试的测试要点,针对所确定的测试要点,分析测试执行时对应的测试方案,越详细越精准 ...
- UNI-APP/VUE 项目集成 微信SDK /微信开放标签 教程和避坑指南(下)——微信开放标签篇
UNI-APP/VUE 项目集成 微信开放标签 教程和避坑指南 文章同步发布于
- UNI-APP/VUE 项目集成 微信SDK /微信开放标签 教程和避坑指南
UNI-APP/VUE 项目集成微信SDK教程和避坑指南 文章同步发布于
- 腾腾流氓,云云更流氓(问微信怎样接入支付宝支付),手贱的赶紧点,你会感谢我的...
草原上的两匹马! 打从当年微信开始布局公众号之初时,估计就已经想到了与支付宝正面冲突的场面,所以微信先来个瞒天过海,在春晚搞了个微信红包,那叫一个火呀,此时的云云隐隐感觉到些许不安. 早期的微信开发者 ...
- 简单的python抢红包脚本-Python自动抢红包,超详细教程,再也不会错过微信红包了!...
来源:python专栏 github:https://github.com/MiracleYoung/You-are-Pythonista/tree/master/PythonExercise/Too ...
- QQ会员2018春节红包抵扣券项目背后的故事
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 1. 活动数据 截止3月1日手Q运动红包会员礼包发放核销数据 参与红包活动用户数:2亿+ 发券峰值:52w/min 2. 需求背景 2.1 ...
- C#开发微信门户及应用(32)--微信支付接入和API封装使用
C#开发微信门户及应用(32)--微信支付接入和API封装使用 在微信的应用上,微信支付是一个比较有用的部分,但也是比较复杂的技术要点,在微商大行其道的年代,自己的商店没有增加微信支付好像也说不过去, ...
- Python自动抢红包,从此再也不会错过微信红包了!
作者 | 上海小胖 来源 | Python专栏(ID:xpchuiit) 目录: 0 引言 1 环境 2 需求分析 3 前置准备 4 抢红包流程回顾 5 代码梳理 6 后记 0 引言 提到抢红包,就 ...
- IVX低代码平台——小程序微信红包的应用的做法
前言 本文将向大家展示低代码开发:通过使用用户组件鉴权避免非微信用户获取红包,并使用服务和事务记录用户申领红包的记录:符合获取红包的用户,则通过后台逻辑发送后台生成的金额至本人微信中. 文章目录 前言 ...
最新文章
- map根据value值排序_凯哥带你从零学大数据系列之Java篇---第十九章:集合(Map+Collections)...
- Linux系统下查看目录大小
- 制作测试MM32F3277-MicroPython最小电路板
- 使用opencv作物件识别(一) —— 积分直方图加速HOG特征计算
- TS流解析之PAT表格解析
- 017_SpringBoot异常处理方式-自定义错误页面
- 背包学习————完全背包
- java导出mysql数据库失败_利用Java进行MySql数据库的导入和导出
- oracle告警日志备份,教你怎样用Oracle方便地查看报警日志错误
- 实时获取ccd图像_图像处理基础
- 嵌入式Linux系统编程学习之二十五信号量
- 一级计算机技术,《一级考试大参考》自动化技术、计算机技术.pdf
- 【数据结构笔记19】File Transfer的C语言实现,集合的简化表示,按秩归并,路径压缩
- 彻底删除SQL Server2005(转)
- 一键把动态IP自动设置为静态IP
- 数据恢复软件的原理是什么?
- Python: PS 滤镜--马赛克
- 微型机器人正迎来发展,三大领域应用大有可为
- PHP是专为后端,后端开发PHP入门必备
- Git 之二 架构、工作流程、.git 目录文件
热门文章
- 2020年回顾与2021年展望
- Streaming的介绍
- 中国金属通报杂志中国金属通报杂志社中国金属通报编辑部2022年第4期目录
- 肝素修饰载玻片/生物芯片(Heparin Functional Glass Slides)
- [市民呼声] 双色球3.5亿大奖爆出惊天骗局!(转载)
- java导入进度显示_java excel导入获取实时进度
- Linux 23年来的发展历史
- project server 2016的应用
- SPL 的日期时间函数
- MACD指标与成交量指标怎么配合使用?耳闻过成交量指标可以辅助MACD指标判势吗?