28 changed files with 1482 additions and 83 deletions
@ -0,0 +1,151 @@ |
|||||
|
package com.ccsens.util.bean.wx.dto; |
||||
|
|
||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; |
||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
@JacksonXmlRootElement(localName = "xml") |
||||
|
public class WxGzhAction { |
||||
|
@Data |
||||
|
private static class ScanCodeInfo{ |
||||
|
@JacksonXmlProperty(localName = "ScanType") |
||||
|
private String scanType; |
||||
|
@JacksonXmlProperty(localName = "ScanResult") |
||||
|
private String scanResult; |
||||
|
}; |
||||
|
|
||||
|
@Data |
||||
|
private static class SendPicsInfo{ |
||||
|
@Data |
||||
|
private static class Item{ |
||||
|
@JacksonXmlProperty(localName = "PicMd5Sum") |
||||
|
private String picMd5Sum; |
||||
|
} |
||||
|
@JacksonXmlProperty(localName = "Count") |
||||
|
private Integer count; |
||||
|
@JacksonXmlProperty(localName = "PicList") |
||||
|
private List<Item> picList; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
private static class SendLocationInfo{ |
||||
|
@JacksonXmlProperty(localName = "Location_X") |
||||
|
private String x; |
||||
|
@JacksonXmlProperty(localName = "Location_Y") |
||||
|
private String y; |
||||
|
@JacksonXmlProperty(localName = "Scale") |
||||
|
private String scale; |
||||
|
@JacksonXmlProperty(localName = "Label") |
||||
|
private String label; |
||||
|
@JacksonXmlProperty(localName = "Poiname") |
||||
|
private String poiname; |
||||
|
} |
||||
|
|
||||
|
@JacksonXmlProperty(localName = "FromUserName") |
||||
|
private String openId; |
||||
|
@JacksonXmlProperty(localName = "ToUserName") |
||||
|
private String gzhId; |
||||
|
@JacksonXmlProperty(localName = "CreateTime") |
||||
|
private Long createTime; |
||||
|
@JacksonXmlProperty(localName = "MsgType") |
||||
|
private String msgType; |
||||
|
|
||||
|
/** |
||||
|
* MsgType: Text (Content,MsgId) |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Content") |
||||
|
private String content; |
||||
|
@JacksonXmlProperty(localName = "MsgId") |
||||
|
private String msgId; |
||||
|
/** |
||||
|
* MsgType: image (PicUrl,MediaId,MsgId) |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "PicUrl") |
||||
|
private String picUrl; |
||||
|
@JacksonXmlProperty(localName = "MediaId") |
||||
|
private String mediaId; |
||||
|
/** |
||||
|
* MsgType: voice (MediaId,Format,MsgId) |
||||
|
* Recognition: 开通语音识别后生效 |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Format") |
||||
|
private String format; |
||||
|
@JacksonXmlProperty(localName = "Recognition") |
||||
|
private String recognition; |
||||
|
|
||||
|
/** |
||||
|
* MsgType: video/shortvideo(MediaId,ThumbMediaId,MsgId) |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "ThumbMediaId") |
||||
|
private String thumbMediaId; |
||||
|
/** |
||||
|
* MsgType: location(Location_x,Location_y,Scale,Label,MsgId) |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Location_X") |
||||
|
private String x; |
||||
|
@JacksonXmlProperty(localName = "Location_Y") |
||||
|
private String y; |
||||
|
@JacksonXmlProperty(localName = "Scale") |
||||
|
private String scale; |
||||
|
@JacksonXmlProperty(localName = "Label") |
||||
|
private String label; |
||||
|
/** |
||||
|
* MsgType: link(Title,Description,Url,MsgId) |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Title") |
||||
|
private String title; |
||||
|
@JacksonXmlProperty(localName = "Description") |
||||
|
private String description; |
||||
|
@JacksonXmlProperty(localName = "Url") |
||||
|
private String url; |
||||
|
|
||||
|
/** |
||||
|
* MsgType: event |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Event") |
||||
|
private String event; |
||||
|
@JacksonXmlProperty(localName = "EventKey") |
||||
|
private String eventKey; |
||||
|
|
||||
|
/** |
||||
|
* Event: subscribe/SCAN |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Ticket") |
||||
|
private String ticket; |
||||
|
/** |
||||
|
* Event: Location |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "Latitude") |
||||
|
private String latitude; |
||||
|
@JacksonXmlProperty(localName = "Longitude") |
||||
|
private String longitude; |
||||
|
@JacksonXmlProperty(localName = "Precision") |
||||
|
private String precision; |
||||
|
|
||||
|
/** |
||||
|
* Event: VIEW/view_miniprogram |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "MenuId") |
||||
|
private String menuId; |
||||
|
/** |
||||
|
* Event: scancode_push/scancode_waitmsg |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "ScanCodeInfo") |
||||
|
private ScanCodeInfo scanCodeInfo; |
||||
|
/** |
||||
|
* Event: pic_sysphoto/pic_photo_or_album/pic_weixin |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "SendPicsInfo") |
||||
|
private SendPicsInfo sendPicsInfo; |
||||
|
/** |
||||
|
* Event: location_select |
||||
|
*/ |
||||
|
@JacksonXmlProperty(localName = "SendLocationInfo") |
||||
|
private SendLocationInfo sendLocationInfo; |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxAccessToken extends WxBaseEntity{ |
||||
|
@JsonProperty("access_token") |
||||
|
private String accessToken; |
||||
|
@JsonProperty("expires_in") |
||||
|
private Long expiresIn; |
||||
|
@JsonIgnore |
||||
|
private Long createdAt; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxBaseEntity { |
||||
|
private Integer errcode; |
||||
|
private String errmsg; |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
|
||||
|
public enum WxGzhAuthType { |
||||
|
//基本授权类型,静默,只能获取openId
|
||||
|
SNSAPI_BASE("snsapi_base"), |
||||
|
//信息授权类型,弹出提示框,可以获取openId,unionId,nickanme,city等用户信息
|
||||
|
SNSAPI_USERINFO("snsapi_userinfo"); |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
private String text; |
||||
|
|
||||
|
private WxGzhAuthType(String text){ |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollectionUtil; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import com.fasterxml.jackson.annotation.JsonValue; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.Getter; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxGzhMenu { |
||||
|
public enum ButtonType{ |
||||
|
//点击推事件
|
||||
|
CLICK("click"), |
||||
|
//跳转URL
|
||||
|
VIEW("view"), |
||||
|
//小程序
|
||||
|
MINIPROGRAM("miniprogram"), |
||||
|
//扫码推事件
|
||||
|
SCANCODE_PUSH("scancode_push"), |
||||
|
//扫码推事件且弹出“消息接收中”提示框
|
||||
|
SCANCODE_WAITMSG("scancode_waitmsg"), |
||||
|
//弹出系统拍照发图
|
||||
|
PIC_SYSPHOTO("pic_sysphoto"), |
||||
|
//弹出拍照或者相册发图
|
||||
|
PIC_PHOTO_OR_ALBUM("pic_photo_or_album"), |
||||
|
//弹出微信相册发图器
|
||||
|
PIC_WEIXIN("pic_weixin"), |
||||
|
//弹出地理位置选择器
|
||||
|
LOCATION_SELECT("location_select"), |
||||
|
//下发消息(除文本消息)
|
||||
|
MEDIA_ID("media_id"), |
||||
|
//跳转图文消息URL
|
||||
|
VIEW_LIMITED("view_limited"); |
||||
|
|
||||
|
@JsonValue |
||||
|
private String text; |
||||
|
|
||||
|
ButtonType(String text){ |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
|
@Builder(toBuilder = true) |
||||
|
@Getter |
||||
|
public static class Button{ |
||||
|
/** |
||||
|
* 必须 |
||||
|
*/ |
||||
|
private ButtonType type; |
||||
|
/** |
||||
|
* 必须 |
||||
|
*/ |
||||
|
private String name; |
||||
|
/** |
||||
|
* click等点击类型必须 |
||||
|
*/ |
||||
|
private String key; |
||||
|
/** |
||||
|
* view、miniprogram类型必须 |
||||
|
*/ |
||||
|
private String url; |
||||
|
/** |
||||
|
* media_id类型和view_limited类型必须 |
||||
|
*/ |
||||
|
@JsonProperty("media_id") |
||||
|
private String mediaId; |
||||
|
/** |
||||
|
* miniprogram类型必须 |
||||
|
*/ |
||||
|
private String appid; |
||||
|
private String pagepath; |
||||
|
/** |
||||
|
* optional |
||||
|
*/ |
||||
|
@JsonProperty("sub_button") |
||||
|
private List<Button> subButton; |
||||
|
} |
||||
|
@JsonProperty("button") |
||||
|
private List<Button> buttons; |
||||
|
|
||||
|
public static WxGzhMenu newInstance(){ |
||||
|
return new WxGzhMenu(); |
||||
|
} |
||||
|
|
||||
|
public WxGzhMenu add(Button button){ |
||||
|
if(buttons == null){ |
||||
|
buttons = CollectionUtil.newArrayList(); |
||||
|
} |
||||
|
buttons.add(button); |
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
public enum WxGzhMsgEvent { |
||||
|
/** |
||||
|
* 关注/取消关注事件 |
||||
|
*/ |
||||
|
SUBSCRIBE("subscribe"),UNSUBSCRIBE("unsubscribe"), |
||||
|
/** |
||||
|
* 扫描带参数(公众号)二维码事件 |
||||
|
* 未关注:产生Event: SUBSCRIBE EventKey: qrscene_xxx |
||||
|
* 已关注:产生Event: Scan EventKey: scene_value |
||||
|
*/ |
||||
|
SCAN("SCAN"), |
||||
|
/** |
||||
|
* 上报地理位置事件 |
||||
|
*/ |
||||
|
LOCATION("LOCATION"), |
||||
|
/** |
||||
|
* 自定义菜单事件 |
||||
|
*/ |
||||
|
//点击菜单拉取消息时的事件推送
|
||||
|
CLICK("CLICK"), |
||||
|
//点击菜单跳转链接时的事件推送
|
||||
|
VIEW("VIEW"), |
||||
|
//扫码推事件的事件推送
|
||||
|
SCANCODE_PUSH("scancode_push"), |
||||
|
//扫码推事件且弹出“消息接收中”提示框的事件推送
|
||||
|
SCANCODE_WAITMSG("scancode_waitmsg"), |
||||
|
//弹出系统拍照发图的事件推送
|
||||
|
PIC_SYSPHOTO("pic_sysphoto"), |
||||
|
//弹出拍照或者相册发图的事件推送
|
||||
|
PIC_PHOTO_OR_ALBUM("pic_photo_or_album"), |
||||
|
//弹出微信相册发图器的事件推送
|
||||
|
PIC_WEIXIN("pic_weixin"), |
||||
|
//弹出地理位置选择器的事件推送
|
||||
|
LOCATION_SELECT("location_select"), |
||||
|
//点击菜单跳转小程序的事件推送
|
||||
|
VIEW_MINIPROGRAM("view_miniprogram"); |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
private String text; |
||||
|
|
||||
|
private WxGzhMsgEvent(String text){ |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.Setter; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
public enum WxGzhMsgType { |
||||
|
/** |
||||
|
* 1.普通消息 |
||||
|
*/ |
||||
|
//1.1 文本消息
|
||||
|
TEXT("text"), |
||||
|
IMAGE("image"), |
||||
|
VOICE("voice"), |
||||
|
VIDEO("video"), |
||||
|
SHORTVIDEO("shortvideo"), |
||||
|
LOCATION("location"), |
||||
|
LINK("link"), |
||||
|
|
||||
|
/** |
||||
|
* 2.事件推送 |
||||
|
*/ |
||||
|
EVENT("event"); |
||||
|
|
||||
|
@Getter |
||||
|
@Setter |
||||
|
private String text; |
||||
|
|
||||
|
private WxGzhMsgType(String text){ |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxOauth2AccessToken extends WxBaseEntity{ |
||||
|
@JsonProperty("access_token") |
||||
|
private String accessToken; |
||||
|
@JsonProperty("expires_in") |
||||
|
private int expiresIn; |
||||
|
@JsonProperty("refresh_token") |
||||
|
private String refreshToken; |
||||
|
@JsonProperty("openid") |
||||
|
private String openId; |
||||
|
@JsonProperty("scope") |
||||
|
private String scope; |
||||
|
@JsonProperty("unionid") |
||||
|
private String unionId; |
||||
|
@JsonIgnore |
||||
|
private Long createdAt; |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxOauth2UserInfo extends WxBaseEntity{ |
||||
|
@JsonProperty("openid") |
||||
|
private String openId; |
||||
|
|
||||
|
@JsonProperty("nickname") |
||||
|
private String nickname; |
||||
|
|
||||
|
@JsonProperty("sex") |
||||
|
private int sex; |
||||
|
|
||||
|
@JsonProperty("province") |
||||
|
private String province; |
||||
|
|
||||
|
@JsonProperty("city") |
||||
|
private String city; |
||||
|
|
||||
|
@JsonProperty("country") |
||||
|
private String country; |
||||
|
|
||||
|
@JsonProperty("headimgurl") |
||||
|
private String headImgUrl; |
||||
|
|
||||
|
@JsonProperty("privilege") |
||||
|
private List<String> privilegeList; |
||||
|
|
||||
|
@JsonProperty("unionid") |
||||
|
private String unionId; |
||||
|
|
||||
|
@JsonProperty("language") |
||||
|
private String language; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonValue; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
|
||||
|
public enum WxOperation { |
||||
|
/** |
||||
|
* 创建公众号底部菜单 |
||||
|
*/ |
||||
|
CREATE_MENU("CreateMenu"); |
||||
|
|
||||
|
@JsonValue |
||||
|
private String text; |
||||
|
|
||||
|
WxOperation(String text){ |
||||
|
this.text = text; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
package com.ccsens.util.exception; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
public class BusinessException extends BaseException { |
||||
|
private String type = "BusinessError"; |
||||
|
|
||||
|
public BusinessException(){ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public BusinessException(Integer errcode, String errmsg){ |
||||
|
super(errcode,errmsg); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.ccsens.util.exception; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxException extends BaseException{ |
||||
|
private String type = "WxError"; |
||||
|
|
||||
|
public WxException(){ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public WxException(Integer errcode, String errmsg){ |
||||
|
super(errcode,errmsg); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,273 @@ |
|||||
|
package com.ccsens.util.wx; |
||||
|
|
||||
|
import cn.hutool.core.lang.Console; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import cn.hutool.core.util.URLUtil; |
||||
|
import cn.hutool.http.HttpRequest; |
||||
|
|
||||
|
import com.ccsens.util.DateUtil; |
||||
|
import com.ccsens.util.JacksonUtil; |
||||
|
import com.ccsens.util.bean.wx.po.*; |
||||
|
import com.ccsens.util.exception.BaseException; |
||||
|
import com.ccsens.util.exception.BusinessException; |
||||
|
import com.ccsens.util.exception.WxException; |
||||
|
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.security.MessageDigest; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
public class WxGzhUtil { |
||||
|
private static final Logger logger = LoggerFactory.getLogger(WxGzhUtil.class); |
||||
|
/** |
||||
|
* 全局唯一accessToken |
||||
|
*/ |
||||
|
private static WxAccessToken globalWxAccessToken; |
||||
|
/** |
||||
|
* accessToken 过期提前10分钟刷新 |
||||
|
*/ |
||||
|
private static final Integer ACCESS_TOKEN_RESERVED_SECONDS = 10 * 60; |
||||
|
|
||||
|
private static final String URL_LOGIN |
||||
|
= "https://api.weixin.qq.com/sns/jscode2session?appid=%1$s&secret=%2$s&js_code=%3$s&grant_type=%4$s"; |
||||
|
private static final String URL_GET_ACCESS_TOKEN |
||||
|
= "https://api.weixin.qq.com/cgi-bin/token?grant_type=%1$s&appid=%2$s&secret=%3$s"; |
||||
|
private static final String URL_GET_WX_CODE_A |
||||
|
= "https://api.weixin.qq.com/wxa/getwxacode?access_token=%1$s"; |
||||
|
private static final String URL_GET_WX_CODE_B |
||||
|
= "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%1$s"; |
||||
|
private static final String URL_GET_WX_CODE_C |
||||
|
= "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=%1$s"; |
||||
|
private static final String URL_PREPARE_PAY |
||||
|
= "https://api.mch.weixin.qq.com/pay/unifiedorder"; |
||||
|
private static final String URL_PAY_TO_USR_WX |
||||
|
= "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; |
||||
|
private static final String URL_PAY_TO_USR_BANK |
||||
|
= "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank"; |
||||
|
private static final String URL_CREATE_MENU |
||||
|
= " https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%1$s"; |
||||
|
private static final String URL_QUERY_MENU |
||||
|
= "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=%1$s"; |
||||
|
private static final String GZH_AUTH_URL |
||||
|
= "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%1$s&redirect_uri=%2$s&response_type=code&scope=%3$s&state=STATE#wechat_redirect"; |
||||
|
private static final String URL_GET_OAUTH2_ACCESS_TOKEN |
||||
|
= "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%1$s&secret=%2$s&code=%3$s&grant_type=authorization_code"; |
||||
|
private static final String URL_GET_OAUTH2_USERINFO |
||||
|
= "https://api.weixin.qq.com/sns/userinfo?access_token=%1$s&openid=%2$s"; |
||||
|
private static final String APPID = "wx7af1bf1e14facf82"; |
||||
|
private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9"; |
||||
|
private static final String TOKEN = "nNzkL9KkZUOIS8uU"; |
||||
|
private static final String ENCODING_AES_KEY = "MQEXG7grhRNsARbUzem6OwnGr2ZW9o5jsauNqaQWOuu"; |
||||
|
private static final String MCHID = ""; |
||||
|
private static final String KEY = ""; |
||||
|
|
||||
|
/** |
||||
|
* 数组转字符串 |
||||
|
*/ |
||||
|
private static String arrayToString(String[] arr) { |
||||
|
StringBuilder bf = new StringBuilder(); |
||||
|
for (String s : arr) { |
||||
|
bf.append(s); |
||||
|
} |
||||
|
return bf.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* sha1加密 |
||||
|
*/ |
||||
|
private static String sha1Encode(String sourceString) { |
||||
|
String resultString = null; |
||||
|
try { |
||||
|
MessageDigest md = MessageDigest.getInstance("SHA-1"); |
||||
|
resultString = byte2hexString(md.digest(sourceString.getBytes())); |
||||
|
} catch (Exception ex) { |
||||
|
ex.printStackTrace(); |
||||
|
} |
||||
|
return resultString; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 字节数组转字符串 |
||||
|
*/ |
||||
|
private static String byte2hexString(byte[] bytes) { |
||||
|
StringBuilder buf = new StringBuilder(bytes.length * 2); |
||||
|
for (byte aByte : bytes) { |
||||
|
if (((int) aByte & 0xff) < 0x10) { |
||||
|
buf.append("0"); |
||||
|
} |
||||
|
buf.append(Long.toString((int) aByte & 0xff, 16)); |
||||
|
} |
||||
|
return buf.toString().toUpperCase(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 公众号接入验证 |
||||
|
* @param signature signature |
||||
|
* @param timestamp timestamp |
||||
|
* @param nonce nonce |
||||
|
* @param echostr echostr |
||||
|
* @return true or false |
||||
|
*/ |
||||
|
public static boolean checkSignature(String signature,String timestamp,String nonce,String echostr){ |
||||
|
if(StrUtil.isEmpty(signature) || StrUtil.isEmpty(timestamp) || StrUtil.isEmpty(nonce) || StrUtil.isEmpty(echostr)){ |
||||
|
return false; |
||||
|
} |
||||
|
String[] tmpArr = { TOKEN, timestamp, nonce }; |
||||
|
Arrays.sort(tmpArr); |
||||
|
String sign = sha1Encode( arrayToString(tmpArr)); |
||||
|
Console.log("sign:{} , signature: {}",sign,signature); |
||||
|
if (sign.equalsIgnoreCase(signature)) { |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取Access_token |
||||
|
*/ |
||||
|
public static String getAccessToken() throws BaseException { |
||||
|
if(globalWxAccessToken == null || |
||||
|
globalWxAccessToken.getCreatedAt() + globalWxAccessToken.getExpiresIn() |
||||
|
>= DateUtil.currentSeconds() - ACCESS_TOKEN_RESERVED_SECONDS){ |
||||
|
WxAccessToken wxAccessToken = null; |
||||
|
String url = String.format(URL_GET_ACCESS_TOKEN,"client_credential",APPID,SECRET); |
||||
|
String response = HttpRequest.get(url).execute().body(); |
||||
|
Console.log("getAccessToken: {}",response); |
||||
|
try { |
||||
|
if(StrUtil.isEmpty(response) || null == (wxAccessToken = JacksonUtil.jsonToBean(response, WxAccessToken.class))) { |
||||
|
throw new BusinessException(-1,"the response of HttpRequest is empty."); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
if(null != wxAccessToken.getErrcode()){ |
||||
|
throw new WxException(wxAccessToken.getErrcode(),wxAccessToken.getErrmsg()); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(wxAccessToken.getAccessToken())) { |
||||
|
throw new BusinessException(-1,"can't find the access_token attribute."); |
||||
|
} |
||||
|
globalWxAccessToken = wxAccessToken; |
||||
|
globalWxAccessToken.setCreatedAt(DateUtil.currentSeconds()); |
||||
|
} |
||||
|
return globalWxAccessToken.getAccessToken(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询微信公众号底部菜单 |
||||
|
*/ |
||||
|
public static String queryMenu() throws BaseException { |
||||
|
String url = String.format(URL_QUERY_MENU,getAccessToken()); |
||||
|
String menuString = HttpRequest.get(url).execute().body(); |
||||
|
Console.log("url:{}, menuString: {}",url,menuString); |
||||
|
return menuString; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建微信公众号底部菜单 |
||||
|
*/ |
||||
|
public static void createMenu(WxGzhMenu gzhMenu) throws BaseException { |
||||
|
String url = String.format(URL_CREATE_MENU,getAccessToken()); |
||||
|
String menuString = null; |
||||
|
try { |
||||
|
menuString = JacksonUtil.beanToJson(gzhMenu); |
||||
|
Console.log(menuString); |
||||
|
} catch (JsonProcessingException e) { |
||||
|
e.printStackTrace(); |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
String response = HttpRequest.post(url).body(menuString).execute().body(); |
||||
|
if(StrUtil.isEmpty(response)){ |
||||
|
throw new BusinessException(-1,"the response of HttpRequest is empty."); |
||||
|
} |
||||
|
Map<String,Object> map = null; |
||||
|
try { |
||||
|
map = JacksonUtil.jsonToMap(response); |
||||
|
} catch (IOException e) { |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
Object errcode = map.get("errcode"); |
||||
|
if(errcode == null || ((Integer)errcode) != 0) { |
||||
|
throw new WxException((Integer)errcode,(String)map.get("errmsg")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static String getAuthedUrl(String url, WxGzhAuthType wxGzhAuthType){ |
||||
|
return String.format(GZH_AUTH_URL,APPID, URLUtil.encode(url),wxGzhAuthType.getText()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取网页授权凭证 |
||||
|
* @param code OAuth2授权码 |
||||
|
* @return WxOauth2AccessToken |
||||
|
*/ |
||||
|
public static WxOauth2AccessToken getOauth2AccessToken(String code) throws BaseException { |
||||
|
WxOauth2AccessToken wxOauth2AccessToken = null; |
||||
|
String url = String.format(URL_GET_OAUTH2_ACCESS_TOKEN,APPID,SECRET,code); |
||||
|
String response = HttpRequest.get(url).execute().body(); |
||||
|
Console.log("url: {}\nresponse: {}",url,response); |
||||
|
try { |
||||
|
if(StrUtil.isEmpty(response) || null == (wxOauth2AccessToken = JacksonUtil.jsonToBean(response,WxOauth2AccessToken.class))) { |
||||
|
throw new BusinessException(-1,"the response of HttpRequest is empty."); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
if(null != wxOauth2AccessToken.getErrcode()){ |
||||
|
throw new WxException(wxOauth2AccessToken.getErrcode(),wxOauth2AccessToken.getErrmsg()); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(wxOauth2AccessToken.getAccessToken())) { |
||||
|
throw new BusinessException(-1,"can't find the access_token attribute."); |
||||
|
} |
||||
|
wxOauth2AccessToken.setCreatedAt(DateUtil.currentSeconds()); |
||||
|
return wxOauth2AccessToken; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 通过网页授权获取用户信息 |
||||
|
* |
||||
|
* @param accessToken 网页授权接口调用凭证 |
||||
|
* @param openId 用户标识 |
||||
|
* @return SNSUserInfo |
||||
|
*/ |
||||
|
public static WxOauth2UserInfo getOauth2UserInfo(String accessToken, String openId) throws BaseException { |
||||
|
WxOauth2UserInfo wxOauth2UserInfo = null; |
||||
|
String url = String.format(URL_GET_OAUTH2_USERINFO,accessToken,openId); |
||||
|
String response = HttpRequest.get(url).execute().body(); |
||||
|
Console.log("url: {}\nresponse: {}",url,response); |
||||
|
try { |
||||
|
if(StrUtil.isEmpty(response) || null == (wxOauth2UserInfo = JacksonUtil.jsonToBean(response,WxOauth2UserInfo.class))) { |
||||
|
throw new BusinessException(-1,"the response of HttpRequest is empty."); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
if(null != wxOauth2UserInfo.getErrcode()){ |
||||
|
throw new WxException(wxOauth2UserInfo.getErrcode(),wxOauth2UserInfo.getErrmsg()); |
||||
|
} |
||||
|
if (StrUtil.isEmpty(wxOauth2UserInfo.getOpenId())) { |
||||
|
throw new BusinessException(-1,"can't find the openid attribute."); |
||||
|
} |
||||
|
return wxOauth2UserInfo; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 通过网页授权code拉取用户信息,封装了getOauth2AccessToken和getOauth2UserInfo(accessToken,openId) |
||||
|
* @param code 网页授权码 |
||||
|
* @return WxOauth2UserInfo |
||||
|
* @throws BaseException 异常 |
||||
|
*/ |
||||
|
public static WxOauth2UserInfo getOauth2UserInfo(String code) throws BaseException { |
||||
|
WxOauth2AccessToken wxOauth2AccessToken = getOauth2AccessToken(code); |
||||
|
return getOauth2UserInfo(wxOauth2AccessToken.getAccessToken(),wxOauth2AccessToken.getOpenId()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,406 @@ |
|||||
|
package com.ccsens.util.wx; |
||||
|
|
||||
|
import cn.hutool.core.util.ObjectUtil; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import cn.hutool.http.HttpRequest; |
||||
|
|
||||
|
import com.ccsens.util.HttpsUtil; |
||||
|
import com.ccsens.util.JacksonUtil; |
||||
|
import com.ccsens.util.WebConstant; |
||||
|
import com.ccsens.util.exception.BusinessException; |
||||
|
import com.ccsens.util.exception.WxException; |
||||
|
import lombok.Data; |
||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||
|
|
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.io.UnsupportedEncodingException; |
||||
|
import java.net.ConnectException; |
||||
|
import java.util.*; |
||||
|
|
||||
|
@Data |
||||
|
class GetUserInfoException extends RuntimeException{ |
||||
|
private int code; |
||||
|
public GetUserInfoException(int code,String message){ |
||||
|
super(message); |
||||
|
this.code = code; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
class PayException extends RuntimeException{ |
||||
|
private int code; |
||||
|
public PayException(int code,String message){ |
||||
|
super(message); |
||||
|
this.code = code; |
||||
|
} |
||||
|
|
||||
|
public PayException(String message){ |
||||
|
super(message); |
||||
|
this.code = -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
public class WxXcxUtil { |
||||
|
|
||||
|
/** |
||||
|
* 被Jasckson使用的内部类 必须是static的 |
||||
|
*/ |
||||
|
public static class WechatUser { |
||||
|
public String openid; |
||||
|
public String session_key; |
||||
|
public String unionid; |
||||
|
public Integer errcode; |
||||
|
public String errmsg; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get wx code |
||||
|
*/ |
||||
|
public static class WechatCode{ |
||||
|
public String scene; |
||||
|
public String page; |
||||
|
public Integer width; |
||||
|
public Boolean auto_color; |
||||
|
public Boolean line_color; |
||||
|
public Boolean is_hyaline; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Prepare pay |
||||
|
*/ |
||||
|
public static class PreparePayBean{ |
||||
|
public String appid;//小程序ID
|
||||
|
public String mch_id;//商户号
|
||||
|
public String device_info;//设备号
|
||||
|
public String nonce_str;//随机字符串
|
||||
|
public String sign;//签名
|
||||
|
public String sign_type; //签名类型
|
||||
|
public String body;//商品描述
|
||||
|
public String detail;//商品详情
|
||||
|
public String attach;//附加数据
|
||||
|
public String out_trade_no;//商户订单号
|
||||
|
public String fee_type;//货币类型
|
||||
|
public Integer total_fee; //金额(分)
|
||||
|
public String spbill_create_ip;//终端IP
|
||||
|
public String time_start;//交易起始时间
|
||||
|
public String time_expire;//交易结束时间
|
||||
|
public String goods_tag;//商品标记
|
||||
|
public String notify_url;//通知地址
|
||||
|
public String trade_type;//交易类型
|
||||
|
public String product_id;//商品id
|
||||
|
public String limit_pay;//指定支付方式
|
||||
|
public String openid;//用户标识
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Prepare pay result |
||||
|
*/ |
||||
|
public static class PreparePayResultBean{ |
||||
|
public String return_code; |
||||
|
public String return_msg; |
||||
|
public String result_code; |
||||
|
public String prepay_id; |
||||
|
public String err_code; |
||||
|
public String err_code_des; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Prepare pay resign |
||||
|
*/ |
||||
|
public static class PreparePayReSignBean{ |
||||
|
public String nonceStr; |
||||
|
public String _package; |
||||
|
public long timeStamp; |
||||
|
public String paySign; |
||||
|
public String signType; |
||||
|
} |
||||
|
|
||||
|
private static final String URL_LOGIN |
||||
|
= "https://api.weixin.qq.com/sns/jscode2session?appid=%1$s&secret=%2$s&js_code=%3$s&grant_type=%4$s"; |
||||
|
private static final String URL_GET_ACCESS_TOKEN |
||||
|
= "https://api.weixin.qq.com/cgi-bin/token?grant_type=%1$s&appid=%2$s&secret=%3$s"; |
||||
|
private static final String URL_GET_WX_CODE_A |
||||
|
= "https://api.weixin.qq.com/wxa/getwxacode?access_token=%1$s"; |
||||
|
private static final String URL_GET_WX_CODE_B |
||||
|
= "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%1$s"; |
||||
|
private static final String URL_GET_WX_CODE_C |
||||
|
= "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=%1$s"; |
||||
|
private static final String URL_PREPARE_PAY |
||||
|
= "https://api.mch.weixin.qq.com/pay/unifiedorder"; |
||||
|
private static final String URL_PAY_TO_USR_WX |
||||
|
= "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; |
||||
|
private static final String URL_PAY_TO_USR_BANK |
||||
|
= "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank"; |
||||
|
private static final String URL_CREATE_MENU |
||||
|
= " https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%1$s"; |
||||
|
|
||||
|
private static final String appid = "wx7af1bf1e14facf82"; |
||||
|
private static final String secret = "a6613fae11b497639c0224b820aaf6d9"; |
||||
|
private static final String mchid = ""; |
||||
|
private static final String key = ""; |
||||
|
|
||||
|
/** |
||||
|
* 小程序登陆,根据code获取用户信息(openid,sessionKey,) |
||||
|
* @param code |
||||
|
* @return |
||||
|
* @throws ConnectException |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static WechatUser getUserInfo(String code) throws Exception { |
||||
|
WechatUser wechatUser = null; |
||||
|
String url = String.format(URL_LOGIN, appid, secret, code, "authorization_code"); |
||||
|
String response = HttpRequest.get(url).execute().body(); |
||||
|
try { |
||||
|
if(StrUtil.isEmpty(response) || null == (wechatUser = JacksonUtil.jsonToBean(response, WechatUser.class))) { |
||||
|
throw new BusinessException(-1,"the response of HttpRequest is empty."); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new BusinessException(-1,e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
if(null != wechatUser.errcode){ |
||||
|
throw new WxException(wechatUser.errcode, wechatUser.errmsg); |
||||
|
} |
||||
|
return wechatUser; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 获取小程序二维码/小程序码 |
||||
|
* @return |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public static void getWxCode(String page,String scene,String path) throws Exception { |
||||
|
String url = String.format(URL_GET_WX_CODE_B, WxGzhUtil.getAccessToken()); |
||||
|
|
||||
|
WechatCode wechatCode = new WechatCode(); |
||||
|
wechatCode.page = page; |
||||
|
wechatCode.scene = scene; |
||||
|
|
||||
|
String postStr = JacksonUtil.beanToJson(wechatCode); |
||||
|
System.out.println(postStr); |
||||
|
|
||||
|
HttpsUtil.httpsRequest(url,"POST",postStr,path); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 统一下单 |
||||
|
*/ |
||||
|
public static PreparePayReSignBean preparePay(String openid,String out_trade_no,String body,int total_fee,String spbill_create_ip, |
||||
|
String notify_url) throws Exception { |
||||
|
PreparePayBean preparePayBean = new PreparePayBean(); |
||||
|
preparePayBean.appid = appid; |
||||
|
preparePayBean.mch_id = mchid; |
||||
|
preparePayBean.openid = openid; |
||||
|
preparePayBean.total_fee = total_fee; |
||||
|
preparePayBean.out_trade_no = out_trade_no; |
||||
|
//以utf-8编码放入paymentPo,微信支付要求字符编码统一采用UTF-8字符编码
|
||||
|
preparePayBean.body = new String(body.getBytes("ISO-8859-1"),"UTF-8"); |
||||
|
//preparePayBean.body = new String("小程序-充值");
|
||||
|
preparePayBean.spbill_create_ip = spbill_create_ip; |
||||
|
preparePayBean.notify_url = notify_url; |
||||
|
preparePayBean.trade_type = "JSAPI"; |
||||
|
preparePayBean.nonce_str = createNonceStr(); |
||||
|
|
||||
|
// 把请求参数打包成数组
|
||||
|
Map<String,Object> sParaTemp = new HashMap(); |
||||
|
sParaTemp.put("appid", preparePayBean.appid); |
||||
|
sParaTemp.put("mch_id", preparePayBean.mch_id); |
||||
|
sParaTemp.put("openid", preparePayBean.openid); |
||||
|
sParaTemp.put("total_fee",preparePayBean.total_fee + ""); |
||||
|
sParaTemp.put("out_trade_no", preparePayBean.out_trade_no); |
||||
|
sParaTemp.put("body", preparePayBean.body); |
||||
|
sParaTemp.put("spbill_create_ip", preparePayBean.spbill_create_ip); |
||||
|
sParaTemp.put("notify_url",preparePayBean.notify_url); |
||||
|
sParaTemp.put("trade_type", preparePayBean.trade_type); |
||||
|
sParaTemp.put("nonce_str", preparePayBean.nonce_str); |
||||
|
|
||||
|
preparePayBean.sign = createSign(sParaTemp); |
||||
|
|
||||
|
String reqrXml = JacksonUtil.beanToXml(preparePayBean,"xml"); |
||||
|
String respXml = HttpsUtil.httpsRequest(URL_PREPARE_PAY,"POST",reqrXml); |
||||
|
|
||||
|
System.out.println("---------------PreparePay Request Xml-----------------"); |
||||
|
System.out.println(reqrXml); |
||||
|
System.out.println("---------------PreparePay Response Xml-----------------"); |
||||
|
System.out.println(respXml); |
||||
|
System.out.println("---------------PreparePay end-----------------"); |
||||
|
|
||||
|
PreparePayResultBean resultBean = null; |
||||
|
PreparePayReSignBean reSignBean = new PreparePayReSignBean(); |
||||
|
resultBean = JacksonUtil.xmlToBean(new ByteArrayInputStream(respXml.getBytes()),PreparePayResultBean.class); |
||||
|
|
||||
|
if(ObjectUtil.isNull(resultBean)) { |
||||
|
throw new PayException("PreparePay 返回的resultBean为空"); |
||||
|
} |
||||
|
if(!StrUtil.isEmpty(resultBean.return_code) |
||||
|
&& !StrUtil.isEmpty(resultBean.result_code)){ |
||||
|
if(resultBean.result_code.equals("SUCCESS") && resultBean.return_code.equals(resultBean.result_code)){ |
||||
|
reSignBean.nonceStr = createNonceStr(); |
||||
|
reSignBean._package = "prepay_id="+resultBean.prepay_id; |
||||
|
reSignBean.timeStamp = System.currentTimeMillis() / 1000; |
||||
|
reSignBean.signType = "MD5"; |
||||
|
|
||||
|
// 把请求参数打包成数组
|
||||
|
sParaTemp = new HashMap(); |
||||
|
sParaTemp.put("appId", appid); |
||||
|
sParaTemp.put("package", reSignBean._package); |
||||
|
sParaTemp.put("nonceStr", reSignBean.nonceStr); |
||||
|
sParaTemp.put("signType", reSignBean.signType); |
||||
|
sParaTemp.put("timeStamp",reSignBean.timeStamp + ""); |
||||
|
|
||||
|
reSignBean.paySign = createSign(sParaTemp); |
||||
|
|
||||
|
return reSignBean; |
||||
|
}else{ |
||||
|
throw new PayException("[" + resultBean.err_code + "]" |
||||
|
+ resultBean.err_code_des); |
||||
|
} |
||||
|
}else { |
||||
|
throw new PayException(resultBean.return_msg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Create Sign |
||||
|
* @param sParaTemp |
||||
|
* @return |
||||
|
*/ |
||||
|
public static String createSign(Map<String,Object> sParaTemp){ |
||||
|
// 除去数组中的空值和签名参数
|
||||
|
Map sPara = paraFilter(sParaTemp); |
||||
|
// 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
|
||||
|
String prestr = createLinkString(sPara); |
||||
|
//MD5运算生成签名
|
||||
|
String sign = sign(prestr, key, "utf-8").toUpperCase(); |
||||
|
return sign; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 除去数组中的空值和签名参数 |
||||
|
* @param sArray 签名参数组 |
||||
|
* @return 去掉空值与签名参数后的新签名参数组 |
||||
|
*/ |
||||
|
private static Map paraFilter(Map<String,Object> sArray) { |
||||
|
Map result = new HashMap(); |
||||
|
if (sArray == null || sArray.size() <= 0) { |
||||
|
return result; |
||||
|
} |
||||
|
for (String key : sArray.keySet()) { |
||||
|
String value = "" + sArray.get(key); |
||||
|
if (StrUtil.isEmpty(value) || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { |
||||
|
continue; |
||||
|
} |
||||
|
result.put(key, value); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 |
||||
|
* @param params 需要排序并参与字符拼接的参数组 |
||||
|
* @return 拼接后字符串 |
||||
|
*/ |
||||
|
private static String createLinkString(Map<String,String> params) { |
||||
|
List<String> keys = new ArrayList<String>(params.keySet()); |
||||
|
Collections.sort(keys); |
||||
|
StringBuilder prestr = new StringBuilder(); |
||||
|
for (int i = 0; i < keys.size(); i++) { |
||||
|
String key = keys.get(i); |
||||
|
String value = params.get(key); |
||||
|
if (i == keys.size() - 1) { |
||||
|
prestr.append(key).append("=").append(value); |
||||
|
} else { |
||||
|
prestr.append(key).append("=").append(value).append("&"); |
||||
|
} |
||||
|
} |
||||
|
return prestr.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 签名字符串 |
||||
|
* @param text 需要签名的字符串 |
||||
|
* @param key 密钥 |
||||
|
* @param input_charset 编码格式 |
||||
|
* @return 签名结果 |
||||
|
*/ |
||||
|
private static String sign(String text, String key, String input_charset) { |
||||
|
text = text + "&key=" + key; |
||||
|
return DigestUtils.md5Hex(getContentBytes(text, input_charset)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* getContentBytes |
||||
|
*/ |
||||
|
private static byte[] getContentBytes(String content, String charset) { |
||||
|
if (charset == null || "".equals(charset)) { |
||||
|
return content.getBytes(); |
||||
|
} |
||||
|
try { |
||||
|
return content.getBytes(charset); |
||||
|
} catch (UnsupportedEncodingException e) { |
||||
|
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static String createNonceStr(){ |
||||
|
return UUID.randomUUID().toString().replace("-",""); |
||||
|
} |
||||
|
|
||||
|
public static Map<String,Object> payToUsrWx( |
||||
|
String partner_trade_no,String openid,int total_fee,String desc,String spbill_create_ip) |
||||
|
throws Exception{ |
||||
|
final String SUCCESS = "SUCCESS"; |
||||
|
//1.构造请求字符串
|
||||
|
Map<String,Object> reqMap = new HashMap<>(); |
||||
|
reqMap.put("mch_appid",appid); |
||||
|
reqMap.put("mchid",mchid); |
||||
|
//reqMap.put("device_info",null); //不能添加空字段,Jackson会生成闭包xml <device_info/>导致签名失败
|
||||
|
reqMap.put("nonce_str",createNonceStr()); |
||||
|
reqMap.put("partner_trade_no",partner_trade_no); |
||||
|
reqMap.put("openid",openid); |
||||
|
reqMap.put("check_name","NO_CHECK"); |
||||
|
//reqMap.put("re_user_name",null);
|
||||
|
reqMap.put("amount",total_fee + ""); |
||||
|
reqMap.put("desc",desc); |
||||
|
reqMap.put("spbill_create_ip",spbill_create_ip); |
||||
|
reqMap.put("sign",createSign(reqMap)); |
||||
|
|
||||
|
//2.发送付款请求
|
||||
|
String reqrXml = JacksonUtil.mapToXml(reqMap,"xml"); |
||||
|
String respXml = HttpsUtil.httpsRequest(URL_PAY_TO_USR_WX,"POST",reqrXml, |
||||
|
WebConstant.PATH_WX_CRET,mchid); |
||||
|
System.out.println("---------------PayToUsrWx Request Xml-----------------"); |
||||
|
System.out.println(reqrXml); |
||||
|
System.out.println("---------------PayToUsrWx Response Xml-----------------"); |
||||
|
System.out.println(respXml); |
||||
|
System.out.println("---------------PayToUsrWx end-----------------"); |
||||
|
|
||||
|
//3.判断成功失败
|
||||
|
Map<String,Object> respMap = JacksonUtil.xmlToMap(new ByteArrayInputStream(respXml.getBytes())); |
||||
|
String return_code,result_code; |
||||
|
return_code = (String)respMap.get("return_code"); |
||||
|
result_code = (String)respMap.get("result_code"); |
||||
|
|
||||
|
if(!StrUtil.isEmpty(return_code) && !StrUtil.isEmpty(result_code)){ |
||||
|
if(return_code.equals(SUCCESS) && return_code.equals(result_code)){ |
||||
|
return respMap; |
||||
|
}else{ |
||||
|
throw new PayException("[" + respMap.get("err_code") + "]" |
||||
|
+ respMap.get("err_code_des")); |
||||
|
} |
||||
|
}else { |
||||
|
throw new PayException((String) respMap.get("return_msg")); |
||||
|
} |
||||
|
//return null;
|
||||
|
} |
||||
|
|
||||
|
public static Map<String,String> payToUsrBank(){ |
||||
|
//Fix Me
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue