v2支付
v2微信官方文档
封装支付请求实体
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
public class WeixinPayForm{
@ApiModelProperty("支付信息")
private String body;
@ApiModelProperty("请求ip")
private String ip;
@ApiModelProperty("交易订单号")
private String outTradeNo;
@ApiModelProperty("总金额")
private BigDecimal totalAmount;
@ApiModelProperty("小程序openid")
private String openId;
@ApiModelProperty("支付回调地址")
private String notifyUrl;
//app,xcx,gzh
@ApiModelProperty("xcx")
private String wxType;
@ApiModelProperty("支付信息")
private String authCode;
//门店编号
@ApiModelProperty("设备信息")
private String deviceInfo;
@ApiModelProperty("商户id")
private String subMchId;
}
controller接口暴露层
@ApiOperation(value = "支付下单")
@ResponseBody
@RequestMapping(value={"/buy"}, method=RequestMethod.POST)
String message = payService.payFoodOrder( orderNo, new PayBaseForm(payMethod, this.getApplicationType(), this.getRequestIp()));
return CommonResult.success(message);
}
payFoodOrder 支付接口实现类
public String payFoodOrder(UmsMember member, String orderNo, PayBaseForm baseForm) {
//查询订单 可以根据实际业务来 目前写死是为了方便理解 实际业务可以换成对应实体参数
WeixinPayForm wxform = new WeixinPayForm();
wxform.setBody("订单支付");
wxform.setIp(getRequestIp());
//商户订单号
wxform.setOutTradeNo("0010001");
//总金额
wxform.setTotalAmount(new BigDecimal(100));
wxform.setWxType("xcx");
wxform.setDeviceInfo("cccc");
wxform.setOpenId("xccxxx*********");
//支付回调地址
wxform.setNotifyUrl(WechatUtil.getPayNotifyUrl() + WXPAY_NOTIFY_URL_FOOD_ORDER);
wxform.setSubMchId("商户号id");
return new WeixinPay().pay(wxform);
}
获取请求ip
public String getRequestIp(){
HttpServletRequest request = this.getRequest();
String ip = request.getHeader("x-forwarded-for");
if(ip == null) {
ip = request.getRemoteAddr();
} else {
String[] ips = ip.split(",");
ip = ips[0];
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
wxform.setNotifyUrl(WechatUtil.getPayNotifyUrl() + WXPAY_NOTIFY_URL_FOOD_ORDER);
这个回调地址是你自己代码里面定义的回调接口,例如你定义的controller回调接口url是 feedback/wx/notifurl ,
即是 wxform.setNotifyUrl(“feedback/wx/notifurl”),微信终端会去调用这个回调方法的,可以通过日志确认。
WeixinPay().pay(wxform)方法
public String pay(WeixinPayForm form) throws Exception{
//微信服务商APPID,一般情况为认证的服务号appid
String appId = "wxd******";
//子商户号
String mchId = "160*****";
//小程序appid
String subAppid = "111111******";
tradeType = "JSAPI";
Map<String,String> paramMap = new LinkedHashMap<>();
paramMap.put("appid", appId);
paramMap.put("body", form.getBody());
paramMap.put("device_info", StringUtils.isNotBlank(form.getDeviceInfo()) ? form.getDeviceInfo() : "WEB");
paramMap.put("fee_type", "CNY");
paramMap.put("limit_pay", "no_credit");
paramMap.put("mch_id", mchId);
paramMap.put("nonce_str", StringUtils.genenrateUniqueInd());
paramMap.put("notify_url", form.getNotifyUrl());
// if(!isApp){
// paramMap.put("openid", form.getOpenId());
// }
paramMap.put("out_trade_no", form.getOutTradeNo());
paramMap.put("sign_type", "MD5");
paramMap.put("spbill_create_ip", form.getIp());
paramMap.put("spbill_create_ip", form.getIp());
paramMap.put("sub_appid", subAppid);
paramMap.put("sub_mch_id", form.getSubMchId());
if(!isApp){
paramMap.put("sub_openid", form.getOpenId());
}
paramMap.put("total_fee", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
paramMap.put("trade_type", tradeType);
System.out.println("=======签名参数开始==========");
System.out.println(paramMap.toString());
System.out.println("=======签名参数结束==========");
paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
//签名
String xmlParams = getPayXmlString(paramMap, isApp);
System.out.println("=======签名XML开始==========");
System.out.println(xmlParams);
System.out.println("=======签名XML结束==========");
String message = HttpUtil.doPostByXml(WechatUtil.GEN_ORDER_URL, xmlParams);
WeixinResponse response = WechatUtil.xmlToBean(message, WeixinResponse.class);
System.out.println("=======返回信息开始==========");
System.out.println(message);
System.out.println("=======返回结束开始==========");
if("SUCCESS".equals(response.getReturn_code())){
if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
//返回公众号,小程序前端所需参数
Map<String,String> result = new LinkedHashMap<>();
result.put("appId", subAppid);
result.put("nonceStr", StringUtils.genenrateUniqueInd());
result.put("package", "prepay_id="+response.getPrepay_id());
result.put("signType", "MD5");
result.put("timeStamp", String.valueOf(System.currentTimeMillis()));
result.put("paySign", WechatUtil.MD5(result, IspConstant.WX_ISP_MCH_KEY));
return JsonUtils.object2JsonString(result);
}else{
throw new ServiceException("支付失败,失败原因:"+response.getErr_code_des());
}
}else{
throw new ServiceException("支付失败,失败原因:"+response.getReturn_msg());
}
}
StringUtils.genenrateUniqueInd() 生成订单号
import java.io.UnsupportedEncodingException;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.math.RandomUtils;
public class StringUtils extends org.apache.commons.lang.StringUtils {
private static final Pattern URL = Pattern.compile(
"^((https|http|ftp|rtsp|mms)?://)"
+ "+(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?"
+ "(([0-9]{1,3}\\.){3}[0-9]{1,3}"
+ "|"
+ "([0-9a-z_!~*'()-]+\\.)*"
+ "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\."
+ "[a-z]{2,6})"
+ "(:[0-9]{1,4})?"
+ "((/?)|"
+ "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$", Pattern.CASE_INSENSITIVE
);
private static final Pattern PHONE = Pattern.compile("^1[3|4|5|6|7|8|9]([0-9])\\d{8}$");
private static final Pattern MONEY = Pattern.compile("^[0-9]+$|^[0-9]+\\.[0-9]{1,6}$");
private static final Pattern TELPHONE = Pattern.compile("^(0[0-9]{2,4}-?[0-9]{7,8})|(1[3|4|5|7|8][0-9]{9})$");
private static final Pattern IP = Pattern.compile("^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]|[*])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]|[*])$");
private static final Pattern EMAIL = Pattern.compile("^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$");
private static final Pattern SUZI = Pattern.compile("^[0-9]*$");
public static boolean isContainChinese(String str) {
Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
Matcher m = p.matcher(str);
if (m.find()) {
return true;
}
return false;
}
public static boolean isEqual(Object obj1, Object obj2) {
if(obj1 == null && obj2 == null) return true;
if(obj1 == null || obj2 == null) return false;
return obj1.equals(obj2);
}
public static boolean isNotEqual(Object obj1, Object obj2) {
return !isEqual(obj1, obj2);
}
/**
* 六位数字验证码
* @return
*/
public static String getSixCode() {
String result = "";
for (int i = 0; i < 6; i++) {
result += new Random().nextInt(10);
}
return result;
}
public static String getFourCode() {
String result = "";
for (int i = 0; i < 4; i++) {
result += new Random().nextInt(10);
}
return result;
}
/**
* 数字字母验证码
*/
public static String getStringRandom(int length) {
String val = "";
Random random = new Random();
//参数length,表示生成几位随机数
for(int i = 0; i < length; i++) {
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
//输出字母还是数字
if( "char".equalsIgnoreCase(charOrNum) ) {
//输出是大写字母还是小写字母
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
val += (char)(random.nextInt(26) + temp);
} else if( "num".equalsIgnoreCase(charOrNum) ) {
val += String.valueOf(random.nextInt(10));
}
}
return val;
}
/**
* 生成20位订单号
*/
public static String genenrateInd(){
Random random = new Random();
Integer x = random.nextInt(899999);
Integer y = x + 100000;
return DateUtil.getShortCurrentTimeStr() + y.toString();
}
public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z" };
public static String generateShortUuid(int length) {
StringBuffer shortBuffer = new StringBuffer();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < length; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
shortBuffer.append(chars[x % 0x3E]);
}
return shortBuffer.toString();
}
/**
* 生成32位的唯一序列号
* @return
*/
public static String genenrateUniqueInd() {
return MD5Util.MD5Encode(UUID.randomUUID().toString(), "utf-8");
}
public static String genOrderNo(Integer memberId) {
return (System.currentTimeMillis() + (memberId==null?0L:memberId)) + StringUtils.leftPad("" + RandomUtils.nextInt(1000), 3, "0");
}
public static void main(String[] args) {
System.out.println(genenrateInd());
}
}
生成xml字符串 getPayXmlString();
private String getPayXmlString(Map<String,String> paramMap, boolean isApp){
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
sb.append("<appid><![CDATA["+MapUtil.getStr(paramMap, "appid")+"]]></appid>");
sb.append("<body><![CDATA["+MapUtil.getStr(paramMap, "body")+"]]></body>");
sb.append("<device_info><![CDATA["+MapUtil.getStr(paramMap, "device_info")+"]]></device_info>");
sb.append("<fee_type><![CDATA["+MapUtil.getStr(paramMap, "fee_type")+"]]></fee_type>");
sb.append("<limit_pay><![CDATA["+MapUtil.getStr(paramMap, "limit_pay")+"]]></limit_pay>");
sb.append("<mch_id><![CDATA["+MapUtil.getStr(paramMap, "mch_id")+"]]></mch_id>");
sb.append("<nonce_str><![CDATA["+MapUtil.getStr(paramMap, "nonce_str")+"]]></nonce_str>");
sb.append("<notify_url><![CDATA["+MapUtil.getStr(paramMap, "notify_url")+"]]></notify_url>");
/*if(!isApp){
sb.append("<openid><![CDATA["+MapUtil.getStr(paramMap, "openid")+"]]></openid>");
}*/
sb.append("<out_trade_no><![CDATA["+MapUtil.getStr(paramMap, "out_trade_no")+"]]></out_trade_no>");
sb.append("<sign_type><![CDATA["+MapUtil.getStr(paramMap, "sign_type")+"]]></sign_type>");
sb.append("<spbill_create_ip><![CDATA["+MapUtil.getStr(paramMap, "spbill_create_ip")+"]]></spbill_create_ip>");
sb.append("<sub_appid><![CDATA["+MapUtil.getStr(paramMap, "sub_appid")+"]]></sub_appid>");
sb.append("<sub_mch_id><![CDATA["+MapUtil.getStr(paramMap, "sub_mch_id")+"]]></sub_mch_id>");
if(!isApp){
sb.append("<sub_openid><![CDATA["+MapUtil.getStr(paramMap, "sub_openid")+"]]></sub_openid>");
}
sb.append("<total_fee><![CDATA["+MapUtil.getStr(paramMap, "total_fee")+"]]></total_fee>");
sb.append("<trade_type><![CDATA["+MapUtil.getStr(paramMap, "trade_type")+"]]></trade_type>");
sb.append("<sign><![CDATA["+MapUtil.getStr(paramMap, "sign")+"]]></sign>");
sb.append("</xml>");
return sb.toString();
}
生成秘钥签名
WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY)
import java.io.Writer;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import cn.hutool.http.HttpUtil;
@Component
public class WechatUtil {
private static final String key1 = "key1";
private static final String key2 = "key2";
public static final String JS_CODE_URL="https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";
public static final String AUTH2_URL="https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
public static final String USERINFO_URL="https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";
public static final String GEN_ORDER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
public static final String WECHAT_TRANSFERS_URL="https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
public static final String REFUND_ORDER_URL="https://api.mch.weixin.qq.com/secapi/pay/refund";
public static final String ACCESS_TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";
public static final String WXACODE="https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN";
public static final String TEMPLATE_URL="https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
public static final String DOWNLOADBILL_URL="https://api.mch.weixin.qq.com/pay/downloadbill";
@Autowired
private WeixinConfigService weixinService;
@Autowired
private RedisUtil redisUtil;
private static SysWeixinConfig config;
@PostConstruct
public void init() throws ServiceException {
config = weixinService.getAllWeixinConfigs().get(0);
}
public static String getPayNotifyUrl() {
return config.getPayNotifyUrl();
}
public static String getKfAppId() {
return config.getKfAppId();
}
public static String getKfSecret() {
return config.getKfSecret();
}
public static String getMchId() {
return config.getMchId();
}
public static String getMchKey() {
return config.getMchKey();
}
public static String getGzhAppId(){
return config.getGzhAppId();
}
public static String getGzhSecret(){
return config.getGzhSecret();
}
public static String getXcxAppId(){
return config.getXcxAppId();
}
public static String getXcxSecret(){
return config.getXcxSecret();
}
public static String getFiveXcxAppId(){
return config.getFiveXcxAppId();
}
public static String getFiveXcxSecret(){
return config.getFiveXcxSecret();
}
public static String getCertPath(){
return config.getCertPath();
}
public static String MD5(Map<String,String> paramMap){
return MD5(paramMap, WechatUtil.getMchKey());
}
/**
* 返回加密签名
* @param paramMap xml字符串
* @param mchKey 微信服务商商户号
* @return
*/
public static String MD5(Map<String,String> paramMap, String mchKey){
StringBuffer signA = new StringBuffer();
for(String key : paramMap.keySet()){
signA.append(key+"="+paramMap.get(key)+"&");
}
String signTemp = signA.toString()+"key="+mchKey;
return MD5Util.MD5Encode(signTemp,"utf-8").toUpperCase();
}
/**
* bean转成微信的xml消息格式
* @param object
* @return
*/
public static String beanToXml( Object object) {
XStream xStream = getMyXStream();
xStream.alias("xml", object.getClass());
xStream.processAnnotations(object.getClass());
String xml = xStream.toXML(object);
if (!StringUtils.isEmpty(xml)){
return xml;
}else{
return null;
}
}
/**
* xml转成bean泛型方法
* @param resultXml
* @param clazz
* @param <T>
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> T xmlToBean(String resultXml, Class clazz) {
// XStream对象设置默认安全防护,同时设置允许的类
XStream stream = new XStream(new DomDriver());
// XStream.setupDefaultSecurity(stream);
// stream.allowTypes(new Class[]{clazz});
stream.processAnnotations(new Class[]{clazz});
// stream.setMode(XStream.NO_REFERENCES);
stream.alias("xml", clazz);
return (T) stream.fromXML(resultXml);
}
public static String getIp2(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
//xstream扩展,bean转xml自动加上![CDATA[]]
public static XStream getMyXStream() {
return new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("rawtypes")
@Override
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
public void sendWxMessage(JSONObject paramsMap, Integer type) throws ServiceException {
String acctenToken = getAccessToken(type);
String message;
try {
message = HttpUtil.post(TEMPLATE_URL.replace("ACCESS_TOKEN", acctenToken), paramsMap.toString());
System.out.println(message);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getAccessToken(Integer type) throws ServiceException {
String key = ACCESS_TOKEN_KEY_7;
if(type == 5) key = ACCESS_TOKEN_KEY_5;
String access_token = (String) redisUtil.getRedisValue(key);
if(StringUtils.isEmpty(access_token)){
JSONObject jsonObject = HttpUtil.doGet(
ACCESS_TOKEN_URL
.replace("APPID", type == 5 ? getFiveXcxAppId() : getXcxAppId())
.replace("SECRET", type == 5 ? getFiveXcxSecret() : getXcxSecret()));
access_token = jsonObject.getString("access_token");
if(!StringUtils.isEmpty(access_token)){
redisUtil.delete(key);
redisUtil.setRedisValue(key, access_token, jsonObject.getLong("expires_in"), TimeUnit.SECONDS);
}
}
return access_token;
}
}
WeixinResponse转换实体
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeixinResponse {
@XStreamAlias("appid")
private String appid;
@XStreamAlias("sub_appid")
private String sub_appid;
@XStreamAlias("sub_mch_id")
private String sub_mch_id;
@XStreamAlias("sub_is_subscribe")
private String sub_is_subscribe;
@XStreamAlias("device_info")
private String device_info;
@XStreamAlias("nonce_str")
private String nonce_str;
@XStreamAlias("sign")
private String sign;
@XStreamAlias("sign_type")
private String sign_type;
@XStreamAlias("settlement_total_fee")
private String settlement_total_fee;
@XStreamAlias("coupon_fee")
private String coupon_fee;
@XStreamAlias("coupon_fee_1")
private String coupon_fee_1;
@XStreamAlias("coupon_count")
private String coupon_count;
@XStreamAlias("coupon_type_0")
private String coupon_type_0;
@XStreamAlias("coupon_id_0")
private String coupon_id_0;
@XStreamAlias("coupon_fee_0")
private String coupon_fee_0;
@XStreamAlias("attach")
private String attach;
@XStreamAlias("code_url")
private String code_url;
@XStreamAlias("return_code")
private String return_code;
@XStreamAlias("return_msg")
private String return_msg;
@XStreamAlias("result_code")
private String result_code;
@XStreamAlias("err_code")
private String err_code;
@XStreamAlias("err_code_des")
private String err_code_des;
@XStreamAlias("mch_billno")
private String mch_billno;
@XStreamAlias("mch_id")
private String mch_id;
@XStreamAlias("wxappid")
private String wxappid;
@XStreamAlias("re_openid")
private String re_openid;
@XStreamAlias("total_amount")
private String total_amount;
@XStreamAlias("send_listid")
private String send_listid;
@XStreamAlias("prepay_id")
private String prepay_id;
@XStreamAlias("trade_type")
private String trade_type;
@XStreamAlias("openid")
private String openid;
@XStreamAlias("sub_openid")
private String sub_openid;
@XStreamAlias("is_subscribe")
private String is_subscribe;
@XStreamAlias("bank_type")
private String bank_type;
@XStreamAlias("total_fee")
private String total_fee;
@XStreamAlias("cash_fee")
private String cash_fee;
@XStreamAlias("transaction_id")
private String transaction_id;
@XStreamAlias("out_trade_no")
private String out_trade_no;
@XStreamAlias("time_end")
private String time_end;
@XStreamAlias("fee_type")
private String fee_type;
@XStreamAlias("cash_fee_type")
private String cash_fee_type;
}
JsonUtils.object2JsonString()
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonUtils {
public static final Gson gson = new Gson();
public static final GsonBuilder gsonBuilder = new GsonBuilder();
public static <T> T json2Object(Object data, Class<T> clazz) {
return gson.fromJson(gson.toJson(data), clazz);
}
public static String object2JsonString(Object obj) {
if(obj == null) {
return null;
}
return gson.toJson(obj).toString();
}
public static <T> T fromJson(String data, Class<T> clazz) {
return gson.fromJson(data, clazz);
}
/**
* 转化成Json,不转化hmtl字符及不忽略空值
* @param obj
* @return
*/
public static String object2JsonNoEscaping(Object obj){
gsonBuilder.disableHtmlEscaping();
gsonBuilder.serializeNulls();
return gsonBuilder.create().toJson(obj);
}
}
v2提现
controller接口暴露层
@ApiOperation(value = "提现")
@OperationLog
@ApiImplicitParams({
@ApiImplicitParam(paramType="query",name = "id", value = "提现订单id",dataType="Long",required=true)
})
@ResponseBody
@RequestMapping(value="/member/ith_draw", method=RequestMethod.GET)
public CommonResult<Boolean> findWithdrawLog(HttpServletRequest request, @RequestParam("id") Long id) throws ServiceException {
return CommonResult.success(this.iUmsMemberRechargeConfigService.doExtract(request,id));
}
接口实现逻辑
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
public class WeixinEntPayForm {
/**
* 提现单号
*/
private String partnerTradeNo;
/**
* 用户openId
*/
private String openId;
/**
* 退款金额
*/
private BigDecimal totalAmount;
/**
* 用户名
*/
private String userName;
//app,xcx,gzh
private String wxType;
}
提现
/**
* 提现
*
* @param request
* @param id 操作id
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean doExtract(HttpServletRequest request, Long id) throws ServiceException {
WeixinEntPayForm form = new WeixinEntPayForm();
form.setOpenId("scx");
form.setTotalAmount(new BigDecimal(111));
form.setUserName("ssss");
form.setPartnerTradeNo("11281932218");
Map<String, String> map = new WeixinPay().entPay(request, form);
if (ObjectUtils.isEmpty(map)) {
return true;
}
Date now = new Date();
if ("200".equals(map.get("code"))) {
//修改提现订单
update();
//生成提现记录 看自己的个人实际业务
insert();
//修改用户余额 积分 冻结金额
update();
} else {
throw new BusinessException(map.get("message"));
}
return false;
}
WeixinPay.entPay()
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.log4j.Log4j2;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
@Log4j2
public class WeixinPay {
public Map<String, String> entPay(HttpServletRequest request, WeixinEntPayForm form) {
String appId = "appid";
String mchId = "商户号";
boolean isApp = false;
Map<String, String> paramMap = new LinkedHashMap<>();
// 商户账号appid
paramMap.put("mch_appid", appId);
// 商户
paramMap.put("mchid", mchId);
// 随机字符串
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
// 交易订单号
paramMap.put("partner_trade_no", form.getPartnerTradeNo());
// 用户openid
paramMap.put("openid", form.getOpenId());
// 校验用户姓名选项
paramMap.put("check_name", "NO_CHECK");
// 金额 分
paramMap.put("amount", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
//备注
paramMap.put("desc", "提现");
paramMap.put("spbill_create_ip", WechatUtil.getIp2(request));
paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
log.info("=======签名参数开始weixin-entpay==========");
log.info(paramMap.toString());
String requestXml = getRequestXml(paramMap);
log.info("=======签名参数结束weixin-entpay==========");
String xmlResult = CertHttpUtil.postData(WechatUtil.WECHAT_TRANSFERS_URL, requestXml, IspConstant.WX_ISP_MCH_ID, IspConstant.WX_CERT_PATH);
log.info("=======返回信息开始weixin-entpay==========");
log.info(xmlResult);
log.info("=======返回信息结束weixin-entpay==========");
Map<String, String> mapResult = null;
try {
mapResult = WXPayUtil.xmlToMap(xmlResult);
} catch (Exception e) {
log.info("xml转换错误======================" + e.getMessage());
}
Map<String, String> result = new HashMap<>();
// 转账成功
if ("SUCCESS".equalsIgnoreCase(mapResult.get("result_code"))) {
//todo 记录提现日志 生成消费单
result.put("code", "200");
result.put("message", "支付成功");
}
// 转帐失败
else if ("FAIL".equalsIgnoreCase(mapResult.get("result_code"))) {
result.put("code", "500");
// 系统错误需要重试[请先调用查询接口,查看此次付款结果,如结果为不明确状态(如订单号不存在),请务必使用原商户订单号进行重试。
if ("SYSTEMERROR".equalsIgnoreCase(mapResult.get("err_code"))) {
// result.put("message", "支付成功");]
}
// 金额超限
else if ("AMOUNT_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
result.put("message", "金额超限");
}
// 余额不足
else if ("NOTENOUGH".equalsIgnoreCase(mapResult.get("err_code"))) {
// result.put("message", "支付成功");
}
// 超过频率限制,请稍后再试。
else if ("FREQ_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
result.put("message", "超过频率限制,请稍后再试。");
}
// 已经达到今日付款总额上限/已达到付款给此用户额度上限
else if ("MONEY_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
result.put("message", "已经达到今日付款总额上限");
}
// 无法给非实名用户付款
else if ("V2_ACCOUNT_SIMPLE_BAN".equalsIgnoreCase(mapResult.get("err_code"))) {
result.put("message", "无法给非实名用户付款");
}
// 该用户今日付款次数超过限制,如有需要请登录微信支付商户平台更改API安全配置
else if ("SENDNUM_LIMIT".equalsIgnoreCase(mapResult.get("err_code"))) {
result.put("message", "今日付款次数超过限制");
}
} else {
// 系统错误
log.info("------------------具体报错信息------------------" + mapResult.get("err_code_des"));
result.put("code", "500");
result.put("message", "系统错误");
}
return result;
}
}
WechatUtil.getIp2(request)
public static String getIp2(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
WechatUtil.generateNonceStr()
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
CertHttpUtil.postData
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
public class CertHttpUtil {
private static int socketTimeout = 10000;// 连接超时时间,默认10秒
private static int connectTimeout = 30000;// 传输超时时间,默认30秒
private static RequestConfig requestConfig;// 请求器的配置
private static CloseableHttpClient httpClient;// HTTP请求器
/**
* 通过Https往API post xml数据
*
* @param url API地址
* @param xmlObj 要提交的XML数据对象
* @param mchId 商户ID
* @param certPath 证书位置
* @return
*/
public static String postData(String url, String xmlObj, String mchId, String certPath) {
// 加载证书
try {
initCert(mchId, certPath);
} catch (Exception e) {
e.printStackTrace();
}
String result = null;
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity postEntity = new StringEntity(xmlObj, "utf-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
// 根据默认超时限制初始化requestConfig
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
// 设置请求器的配置
httpPost.setConfig(requestConfig);
try {
HttpResponse response = null;
try {
response = httpClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
}
HttpEntity entity = response.getEntity();
try {
result = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
} finally {
httpPost.abort();
}
return result;
}
/**
* 加载证书
*
* @param mchId 商户ID
* @param certPath 证书位置
* @throws Exception
*/
@SuppressWarnings("deprecation")
private static void initCert(String mchId, String certPath) throws Exception {
// 证书密码,默认为商户ID
String key = mchId;
// 证书的路径
String path = certPath;
// 指定读取证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// 读取本机存放的PKCS12证书文件
FileInputStream instream = new FileInputStream(new File(path));
try {
// 指定PKCS12的密码(商户ID)
keyStore.load(instream, key.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslcontext, new String[] {"TLSv1"}, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
}
生成xml字符串
@SuppressWarnings("rawtypes")
public static String getRequestXml(Map<String, String> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
v2退款
![](https://img-blog.csdnimg.cn/img_convert/f8c5c0e718095cfa50079c781e3b9101.png#clientId=u89e85d45-da94-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u363733d3&margin=[object Object]&originHeight=486&originWidth=437&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u56a22731-966e-4215-bec6-b7e14ecd38c&title=)
cancelAndRefundOrder 退款接口
@Override
public void cancelAndRefundOrder() {
BigDecimal refundAmount = new BigDecimal(111);
refundAmount = refundAmount == null ? BigDecimal.ZERO : refundAmount;
//获取支付订单
WeixinRefundForm wxform = new WeixinRefundForm();
wxform.setWxType("xcx");
wxform.setOutRefundNo("110001111");
wxform.setOutTradeNo("110001111");
wxform.setRefundFee("退款金额");
wxform.setTotalAmount("订单金额");
wxform.setNotifyUrl("退款回调地址");
String subMchId = "商户号id";
wxform.setSubMchId(subMchId);
new WeixinPay().refund(wxform);
}
refund 退款封装
public void refund(WeixinRefundForm form) throws ServiceException{
String appId = "微信服务商APPID";
String mchId = "微信服务商商户号";
String subAppid = "小程序appid";
boolean isApp = false;
Map<String,String> paramMap = new LinkedHashMap<>();
paramMap.put("appid", appId);
paramMap.put("mch_id", mchId);
paramMap.put("nonce_str", StringUtils.genenrateUniqueInd());
paramMap.put("notify_url", form.getNotifyUrl());
paramMap.put("out_refund_no", form.getOutRefundNo());
paramMap.put("out_trade_no", form.getOutTradeNo());
paramMap.put("refund_fee", String.valueOf(form.getRefundFee().multiply(new BigDecimal(100)).intValue()));
paramMap.put("sign_type", "MD5");
paramMap.put("sub_appid", subAppid);
paramMap.put("sub_mch_id", form.getSubMchId());
paramMap.put("total_fee", String.valueOf(form.getTotalAmount().multiply(new BigDecimal(100)).intValue()));
System.out.println("=======签名参数开始==========");
System.out.println(paramMap.toString());
System.out.println("=======签名参数结束==========");
paramMap.put("sign", WechatUtil.MD5(paramMap, IspConstant.WX_ISP_MCH_KEY));
//签名
String xmlParams = getRefundXmlString(paramMap, isApp);
System.out.println("=======签名XML开始==========");
System.out.println(xmlParams);
System.out.println("=======签名XML结束==========");
String message = CertHttpUtil.postData(WechatUtil.REFUND_ORDER_URL, xmlParams, IspConstant.WX_ISP_MCH_ID, IspConstant.WX_CERT_PATH);
WeixinRefundResponse response = WechatUtil.xmlToBean(message, WeixinRefundResponse.class);
System.out.println("=======返回信息开始==========");
System.out.println(message);
System.out.println("=======返回结束开始==========");
if("SUCCESS".equals(response.getReturn_code())){
if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
//退款申请成功,结果通过退款查询接口查询
}else{
throw new ServiceException("退款申请失败,失败原因:"+response.getErr_code_des());
}
}else{
throw new ServiceException("退款申请失败,失败原因:"+response.getReturn_msg());
}
}
生成退款xml字符串
private String getRefundXmlString(Map<String,String> paramMap, boolean isApp){
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
sb.append("<appid><![CDATA["+MapUtil.getStr(paramMap, "appid")+"]]></appid>");
sb.append("<mch_id><![CDATA["+MapUtil.getStr(paramMap, "mch_id")+"]]></mch_id>");
sb.append("<nonce_str><![CDATA["+MapUtil.getStr(paramMap, "nonce_str")+"]]></nonce_str>");
sb.append("<notify_url><![CDATA["+MapUtil.getStr(paramMap, "notify_url")+"]]></notify_url>");
sb.append("<out_refund_no><![CDATA["+MapUtil.getStr(paramMap, "out_refund_no")+"]]></out_refund_no>");
sb.append("<out_trade_no><![CDATA["+MapUtil.getStr(paramMap, "out_trade_no")+"]]></out_trade_no>");
sb.append("<refund_fee><![CDATA["+MapUtil.getStr(paramMap, "refund_fee")+"]]></refund_fee>");
sb.append("<sign_type><![CDATA["+MapUtil.getStr(paramMap, "sign_type")+"]]></sign_type>");
sb.append("<sub_appid><![CDATA["+MapUtil.getStr(paramMap, "sub_appid")+"]]></sub_appid>");
sb.append("<sub_mch_id><![CDATA["+MapUtil.getStr(paramMap, "sub_mch_id")+"]]></sub_mch_id>");
sb.append("<total_fee><![CDATA["+MapUtil.getStr(paramMap, "total_fee")+"]]></total_fee>");
sb.append("<sign><![CDATA["+MapUtil.getStr(paramMap, "sign")+"]]></sign>");
sb.append("</xml>");
return sb.toString();
}
支付回调
微信支付成功回调----> 这边用到了设计模式,工厂模式
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(tags = "订单支付回调", description = "FoodOrderPayCallbackController")
@Controller
public class FoodOrderPayCallbackController extends PayCallbackController {
@ApiOperation(value = "微信支付回调")
@ResponseBody
@RequestMapping(value="/pay/callbak", method=RequestMethod.POST)
public String paySusForWx(HttpServletRequest request) throws Exception{
return this.paySusForWx(request, CallbackManager.CALLBACK_FOOD_ORDER);
}
}
PayCallbackController 支付回调controller
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Log4j2
@Controller
public class PayCallbackController {
@Autowired
private CallbackManager paySusManager;
public String paySusForWx(HttpServletRequest request, String type) throws Exception{
InputStream inStream = request.getInputStream();
int _buffer_size = 1024;
if (inStream != null) {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[_buffer_size];
int count = -1;
while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) {
outStream.write(tempBytes, 0, count);
}
tempBytes = null; outStream.flush();
//将流转换成字符串
String result = new String(outStream.toByteArray(), "UTF-8");
log.info("微信支付回调:"+result);
WeixinResponse response = WechatUtil.xmlToBean(result, WeixinResponse.class);
//通知成功
if(StringUtils.isNotBlank(response.getReturn_code()) && "SUCCESS".equals(response.getReturn_code())){
if(StringUtils.isNotBlank(response.getResult_code()) && "SUCCESS".equals(response.getResult_code())){
//支付成功
paySusManager.getPaySusFactory(type).paySus(response.getOut_trade_no());
}
}
}
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
}
CallbackManager 定义回调工厂管理类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CallbackManager {
public static final String CALLBACK_RECHARGE = "recharge";
public static final String CALLBACK_FOOD_ORDER = "foodOrder";
/**
* 退款
*/
public static final String CALLBACK_REFUND_HANDLE = "refundHandle";
/**
* 申请退款
*/
public static final String CALLBACK_REFUND_APPLY = "refundApply";
@Autowired
private FoodOrderLogic foodOrderLogic;
@Autowired
private RefundLogic refundLogic;
@Autowired
private RechargeLogic rechargeLogic;
public CallbackFactory getPaySusFactory(String mode) {
switch (mode) {
case CALLBACK_REFUND_APPLY:
return refundLogic;
case CALLBACK_FOOD_ORDER:
return foodOrderLogic;
case CALLBACK_RECHARGE:
return rechargeLogic;
default:
return null;
}
}
}
CallbackFactory回调工厂
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutionException;
@Component
public abstract class CallbackFactory{
public abstract void paySus(String orderNo) ;
}
FoodOrderLogic 回调处理逻辑方法
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.dtp.core.DtpExecutor;
import com.dtp.core.DtpRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
@Slf4j
@Service("paySusFoodOrder")
public class FoodOrderLogic extends CallbackFactory {
@Override
public void paySus(String orderNo) throws ServiceException, ExecutionException, InterruptedException {
//处理回调逻辑
}
public void refundSus(String refundNo, String type) throws ServiceException {
switch (type) {
case CallbackManager.CALLBACK_REFUND_APPLY:
refundService.refundSusCallback(refundNo, null);
break;
case CallbackManager.CALLBACK_REFUND_HANDLE:
OmsFoodOrder order = foodOrderService.getOrderByOrderNo(refundNo);
foodOrderService.cancelAndRefundOrderSus(order);
break;
default:
break;
}
}
}
微信退款回调
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(tags = "申请售后退款回调", description = "RefundApplyCallbackController")
@Controller
public class RefundApplyCallbackController extends RefundCallbackController {
@ApiOperation(value = "微信退款回调")
@RequestMapping(value="/weixin/pay/refund/callback", method=RequestMethod.POST)
@ResponseBody
public String paySusJoinPinkaForWx(HttpServletRequest request) throws Exception{
return this.refundSusForWx(request, CallbackManager.CALLBACK_REFUND_APPLY);
}
}
WeixinRefundResponse 微信退款实体文章来源:https://www.toymoban.com/news/detail-488878.html
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeixinRefundResponse {
@XStreamAlias("appid")
private String appid;
@XStreamAlias("sub_appid")
private String sub_appid;
@XStreamAlias("nonce_str")
private String nonce_str;
@XStreamAlias("sign")
private String sign;
@XStreamAlias("out_refund_no")
private String out_refund_no;
@XStreamAlias("refund_id")
private String refund_id;
@XStreamAlias("refund_fee")
private String refund_fee;
@XStreamAlias("settlement_refund_fee")
private String settlement_refund_fee;
@XStreamAlias("settlement_total_fee")
private String settlement_total_fee;
@XStreamAlias("return_code")
private String return_code;
@XStreamAlias("return_msg")
private String return_msg;
@XStreamAlias("result_code")
private String result_code;
@XStreamAlias("refund_account")
private String refund_account;
@XStreamAlias("err_code")
private String err_code;
@XStreamAlias("err_code_des")
private String err_code_des;
@XStreamAlias("cash_refund_fee")
private String cash_refund_fee;
@XStreamAlias("mch_id")
private String mch_id;
@XStreamAlias("sub_mch_id")
private String sub_mch_id;
@XStreamAlias("coupon_type_$n")
private String coupon_type_$n;
@XStreamAlias("coupon_refund_fee")
private String coupon_refund_fee;
@XStreamAlias("coupon_refund_fee_$n")
private String coupon_refund_fee_$n;
@XStreamAlias("coupon_refund_count")
private String coupon_refund_count;
@XStreamAlias("coupon_refund_id_$n")
private String coupon_refund_id_$n;
@XStreamAlias("total_fee")
private String total_fee;
@XStreamAlias("cash_fee")
private String cash_fee;
@XStreamAlias("transaction_id")
private String transaction_id;
@XStreamAlias("out_trade_no")
private String out_trade_no;
@XStreamAlias("fee_type")
private String fee_type;
@XStreamAlias("cash_fee_type")
private String cash_fee_type;
@XStreamAlias("req_info")
private String req_info;
@XStreamAlias("refund_channel")
private String refund_channel;
@XStreamAlias("refund_recv_accout")
private String refund_recv_accout;
@XStreamAlias("refund_request_source")
private String refund_request_source;
@XStreamAlias("refund_status")
private String refund_status;
@XStreamAlias("success_time")
private String success_time;
}
RefundCallbackController 微信退款回调实现逻辑文章来源地址https://www.toymoban.com/news/detail-488878.html
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Log4j2
@Controller
public class RefundCallbackController {
private static Cipher cipher = null; //解码器
public String refundSusForWx(HttpServletRequest request, String type) throws Exception{
InputStream inStream = request.getInputStream();
int _buffer_size = 1024;
if (inStream != null) {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] tempBytes = new byte[_buffer_size];
int count = -1;
while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) {
outStream.write(tempBytes, 0, count);
}
tempBytes = null; outStream.flush();
//将流转换成字符串
String result = new String(outStream.toByteArray(), "UTF-8");
log.info("微信退款回调:{}", result);
WeixinRefundResponse response = WechatUtil.xmlToBean(result, WeixinRefundResponse.class);
String reqInfo = response.getReq_info();
init(IspConstant.WX_ISP_MCH_KEY);
Base64.Decoder decoder = Base64.getDecoder();
byte[] base64ByteArr = decoder.decode(reqInfo);
String jmResult = new String(cipher.doFinal(base64ByteArr));
log.info("解密结果:{}", jmResult);
String jmr = jmResult.replaceAll("<root>", "<xml>").replaceAll("</root>", "</xml>");
if(jmr.contains("]></refund_recv_accout>") && !jmr.contains("]]></refund_recv_accout>")){
jmr = jmr.replaceAll("]></refund_recv_accout>", "]]></refund_recv_accout>");
}
WeixinRefundResponse notifyResponse = WechatUtil.xmlToBean(jmr, WeixinRefundResponse.class);
//通知成功
if(StringUtils.isNotBlank(notifyResponse.getRefund_status()) && "SUCCESS".equals(notifyResponse.getRefund_status())){
log.info("退款单号:{}, type:{}", notifyResponse.getOut_refund_no(), type);
//具体个人实现逻辑 可以去调用不同自己的实现逻辑代码
refundSus(notifyResponse.getOut_refund_no(), type);
}
}
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
public static void init(String mchKey) {
// String key = MD5Util.MD5Encode(WechatUtil.getMchKey(), "");
String key = MD5Util.MD5Encode(mchKey, "");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
Security.addProvider(new BouncyCastleProvider());
try {
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
到了这里,关于微信小程序基于java实现v2支付,提现,退款的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!