接上一篇:微信紅包接入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不觸發紅包贈送;(如果以上規則不滿足您的需求,請發郵件至[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<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