微信红包接入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
    @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<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> 
   <sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>
   <mch_billno><![CDATA[0010010404201411170000046545]]></mch_billno>
   <mch_id><![CDATA[1000888888]]></mch_id>
   <wxappid><![CDATA[wxcbda96de0b165486]]></wxappid> 
   <nick_name><![CDATA[nick_name]]></nick_name> 
   <send_name><![CDATA[send_name]]></send_name> 
   <re_openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6uI]]></re_openid> 
   <total_amount><![CDATA[200]]></total_amount> 
   <min_value><![CDATA[200]]></min_value> 
   <max_value><![CDATA[200]]></max_value> 
   <total_num><![CDATA[1]]></total_num> 
   <wishing><![CDATA[恭喜发财]]></wishing>
   <client_ip><![CDATA[127.0.0.1]]></client_ip> 
   <act_name><![CDATA[新年红包]]></act_name> 
   <act_id><![CDATA[act_id]]></act_id>
   <remark><![CDATA[新年红包]]></remark> 
   <logo_imgurl><![CDATA[https://xx/img/wxpaylogo.png]]></logo_imgurl> 
   <share_content><![CDATA[share_content]]></share_content> 
   <share_url><![CDATA[https://xx/img/wxpaylogo.png]]></share_url> 
   <share_imgurl><![CDATA[https:/xx/img/wxpaylogo.png]]></share_imgurl> 
   <nonce_str><![CDATA[50780e0cca98c8c8e814883e5caa672e]]></nonce_str> 
</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"/><span
                                            class="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"> <span
                                                class="redColor">100</span> <span class="font14">秀币</span>
                                        </label>
                                        <label class="radio-inline">
                                            <input type="radio" name="inlineRadioOptions" id="inlineRadio2"
                                                   value="300"> <span
                                                class="redColor">300</span> <span class="font14">秀币</span>
                                        </label>
                                        <label class="radio-inline">
                                            <input type="radio" name="inlineRadioOptions" id="inlineRadio3"
                                                   value="1000"> <span
                                                class="redColor">1000</span> <span class="font14">秀币</span>
                                        </label>
                                        <label class="radio-inline">
                                            <input type="radio" name="inlineRadioOptions" id="inlineRadio4"
                                                   value="5000"> <span
                                                class="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下的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();


};


简单解释一下,我当前项目有一个总的全局对象,gloabelObj,之后所有东西都往上附加,比如文档的初始化函数,这个只是我个人的习惯,其实这个pageInit函数是在页面加载完毕之后被调用;

至于里面关键的就是收集参数发了一个

的交易,需要注意的是,我把一些非关键的参数都在这里配置了,参数的含义就不用我多说了,下面看关键的确认交易:

交易定义:
@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<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 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<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;
    }

以上就是核心的一些代码块,最终实现出来的效果如下:





到此结束;

注:不过上面的代码我今天才写完,一起把博客更新了,如果之后测试发现有啥问题,我在修正;

当前的代码已经部署到「乐秀吧」服务号,进行过测试,应该没啥大问题,如有疑问请关注下面的服务号,我们会进行解答。

谢谢大家,需要交流的朋友可以关注我们的服务号(还在开发,有时候不能及时回复请见谅),之后在服务号的管理后台会把相关代码打包提供给需要的关注用户:)


微信号:PlayPlayInteractive

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值