17 changed files with 789 additions and 25 deletions
@ -0,0 +1,20 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxApiTicket { |
||||
|
@JsonProperty("errcode") |
||||
|
private Integer errcode; |
||||
|
@JsonProperty("errmsg") |
||||
|
private String errmsg; |
||||
|
@JsonProperty("ticket") |
||||
|
private String ticket; |
||||
|
@JsonProperty("expires_in") |
||||
|
private Long expiresIn; |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package com.ccsens.util.bean.wx.po; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author __zHangSan |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxGetIp extends WxBaseEntity{ |
||||
|
@JsonProperty("ipList") |
||||
|
private List<String> ip_list; |
||||
|
} |
@ -0,0 +1,145 @@ |
|||||
|
package com.ccsens.wechatutil.bean.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class NoticeResourceDecode { |
||||
|
|
||||
|
public enum TRADE_STATE { |
||||
|
/*** 支付成功 */ |
||||
|
SUCCESS("SUCCESS","支付成功"), |
||||
|
/*** 转入退款 */ |
||||
|
REFUND("REFUND","转入退款"), |
||||
|
/*** 未支付 */ |
||||
|
NOTPAY("NOTPAY","未支付"), |
||||
|
/*** 已关闭 */ |
||||
|
CLOSED("CLOSED","已关闭"), |
||||
|
/*** 已撤销 */ |
||||
|
REVOKED("REVOKED","已撤销"), |
||||
|
/*** 用户支付中 */ |
||||
|
USERPAYING("USERPAYING","用户支付中"), |
||||
|
/*** 支付失败 */ |
||||
|
PAYERROR("PAYERROR","支付失败"); |
||||
|
|
||||
|
public String value; |
||||
|
public String phase; |
||||
|
TRADE_STATE(String value,String thePhase){ |
||||
|
this.value =value; |
||||
|
this.phase = thePhase; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*** 微信支付订单号 微信支付系统生成的订单号 */ |
||||
|
private String transaction_id; |
||||
|
/*** 订单金额 */ |
||||
|
private AmountBean amount; |
||||
|
/*** 应用ID */ |
||||
|
private String mchid; |
||||
|
/*** |
||||
|
* 交易状态,枚举值: |
||||
|
* SUCCESS:支付成功 |
||||
|
* REFUND:转入退款 |
||||
|
* NOTPAY:未支付 |
||||
|
* CLOSED:已关闭 |
||||
|
* REVOKED:已撤销(付款码支付) |
||||
|
* USERPAYING:用户支付中(付款码支付) |
||||
|
* PAYERROR:支付失败(其他原因,如银行返回失败) |
||||
|
*/ |
||||
|
private String trade_state; |
||||
|
/*** 银行类型,采用字符串类型的银行标识 */ |
||||
|
private String bank_type; |
||||
|
/*** 支付完成时间 yyyy-MM-DDTHH:mm:ss+TIMEZONE*/ |
||||
|
private String success_time; |
||||
|
/*** 支付者 */ |
||||
|
private PayerBean payer; |
||||
|
/*** 商户订单号 商户系统内部订单号 */ |
||||
|
private String out_trade_no; |
||||
|
/*** 应用ID */ |
||||
|
private String appid; |
||||
|
/*** 交易状态描述*/ |
||||
|
private String trade_state_desc; |
||||
|
/*** |
||||
|
* 交易类型,枚举值: |
||||
|
* JSAPI:公众号支付 |
||||
|
* NATIVE:扫码支付 |
||||
|
* APP:APP支付 |
||||
|
* MICROPAY:付款码支付 |
||||
|
* MWEB:H5支付 |
||||
|
* FACEPAY:刷脸支付 |
||||
|
*/ |
||||
|
private String trade_type; |
||||
|
/*** 附加数据 */ |
||||
|
private String attach; |
||||
|
/*** 场景信息 */ |
||||
|
private SceneInfoBean scene_info; |
||||
|
/*** 优惠功能 */ |
||||
|
private List<PromotionDetailBean> promotion_detail; |
||||
|
|
||||
|
@Data |
||||
|
public static class AmountBean { |
||||
|
/*** 用户支付金额,单位为分 */ |
||||
|
private int payer_total; |
||||
|
/*** 订单总金额,单位为分 */ |
||||
|
private int total; |
||||
|
/*** 货币类型 */ |
||||
|
private String currency; |
||||
|
/*** 用户支付币种 */ |
||||
|
private String payer_currency; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class PayerBean { |
||||
|
/*** 用户标识 */ |
||||
|
private String openid; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class SceneInfoBean { |
||||
|
/*** 终端设备号(门店号或收银设备ID) */ |
||||
|
private String device_id; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class PromotionDetailBean { |
||||
|
/*** 优惠券面额 */ |
||||
|
private int amount; |
||||
|
/*** 微信出资 */ |
||||
|
private int wechatpay_contribute; |
||||
|
/*** 券ID */ |
||||
|
private String coupon_id; |
||||
|
/*** 优惠范围 */ |
||||
|
private String scope; |
||||
|
/*** 商户出资 */ |
||||
|
private int merchant_contribute; |
||||
|
/*** 优惠名称 */ |
||||
|
private String name; |
||||
|
/*** 优惠类型 */ |
||||
|
private String type; |
||||
|
/*** 其他出资 */ |
||||
|
private int other_contribute; |
||||
|
/*** 优惠币种 */ |
||||
|
private String currency; |
||||
|
/*** 活动ID */ |
||||
|
private String stock_id; |
||||
|
/*** 单品列表 */ |
||||
|
private List<GoodsDetailBean> goods_detail; |
||||
|
|
||||
|
@Data |
||||
|
public static class GoodsDetailBean { |
||||
|
/*** 商品备注 */ |
||||
|
private String goods_remark; |
||||
|
/*** 商品数量 */ |
||||
|
private int quantity; |
||||
|
/*** 商品优惠金额 */ |
||||
|
private int discount_amount; |
||||
|
/*** 商品编码 */ |
||||
|
private String goods_id; |
||||
|
/*** 商品单价 */ |
||||
|
private int unit_price; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
package com.ccsens.wechatutil.bean.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxNativePayNotice { |
||||
|
|
||||
|
/**"消息id"*/ |
||||
|
private String id; |
||||
|
/**"通知创建的时间,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE"*/ |
||||
|
private String create_time; |
||||
|
/**"通知的类型 SUCCESS"*/ |
||||
|
private String event_type; |
||||
|
/**"通知的资源数据类型 encrypt-resource"*/ |
||||
|
private String resource_type; |
||||
|
/**"回调摘要"*/ |
||||
|
private String summary; |
||||
|
/**"微信支付回调消息"*/ |
||||
|
private NoticeResource resource; |
||||
|
|
||||
|
@Data |
||||
|
public static class NoticeResource { |
||||
|
//"对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM ")
|
||||
|
private String algorithm; |
||||
|
//"Base64编码后的开启/停用结果数据密文")
|
||||
|
private String ciphertext; |
||||
|
//"附加数据")
|
||||
|
private String associated_data; |
||||
|
//"原始回调类型 为transaction")
|
||||
|
private String original_type; |
||||
|
//"加密使用的随机串")
|
||||
|
private String nonce; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class NoticeResourceDecode { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package com.ccsens.wechatutil.bean.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class NativePayNoticeVo { |
||||
|
/**"code"*/ |
||||
|
private String code; |
||||
|
/**消息*/ |
||||
|
private String message; |
||||
|
|
||||
|
public NativePayNoticeVo() { |
||||
|
} |
||||
|
|
||||
|
public NativePayNoticeVo(String code, String message) { |
||||
|
this.code = code; |
||||
|
this.message = message; |
||||
|
} |
||||
|
} |
@ -0,0 +1,120 @@ |
|||||
|
package com.ccsens.wechatutil.bean.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class WxNativePay { |
||||
|
/** |
||||
|
* 应用ID 是 |
||||
|
*/ |
||||
|
private String appid; |
||||
|
/** |
||||
|
* 直连商户号 是 |
||||
|
*/ |
||||
|
private String mchid; |
||||
|
/** |
||||
|
* 商品描述 是 |
||||
|
*/ |
||||
|
private String description; |
||||
|
/** |
||||
|
* 商户订单号 是 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
||||
|
*/ |
||||
|
private String out_trade_no; |
||||
|
/** |
||||
|
* 交易结束时间 否 |
||||
|
*/ |
||||
|
private String time_expire; |
||||
|
/** |
||||
|
* 附加数据 否 |
||||
|
*/ |
||||
|
private String attach; |
||||
|
/** |
||||
|
* 通知地址 是 |
||||
|
*/ |
||||
|
private String notify_url; |
||||
|
/** |
||||
|
* 订单优惠标记 否 |
||||
|
*/ |
||||
|
private String goods_tag; |
||||
|
/** |
||||
|
* 订单金额 是 |
||||
|
*/ |
||||
|
private Amount amount; |
||||
|
/** |
||||
|
* 优惠功能 |
||||
|
*/ |
||||
|
private Detail detail; |
||||
|
/** |
||||
|
* 场景信息 |
||||
|
*/ |
||||
|
private SceneInfo scene_info; |
||||
|
/** |
||||
|
* 结算信息 |
||||
|
*/ |
||||
|
private SceneInfo settle_info; |
||||
|
|
||||
|
@Data |
||||
|
public static class Amount{ |
||||
|
//总金额 是 单位为分。
|
||||
|
private int total; |
||||
|
//货币类型
|
||||
|
private String currency; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class Detail{ |
||||
|
//订单原价 单位为分。
|
||||
|
private int cost_price; |
||||
|
//商品小票ID
|
||||
|
private String invoice_id; |
||||
|
//单品列表
|
||||
|
private List<GoodsDetail> goods_detail; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class GoodsDetail{ |
||||
|
//商户侧商品编码 是
|
||||
|
private String merchant_goods_id; |
||||
|
//微信支付商品编码 否
|
||||
|
private String wechatpay_goods_id; |
||||
|
//商品名称
|
||||
|
private String goods_name; |
||||
|
//商品数量 是
|
||||
|
private int quantity; |
||||
|
//商品单价 是
|
||||
|
private int unit_price; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class SceneInfo{ |
||||
|
//用户终端IP
|
||||
|
private String payer_client_ip; |
||||
|
//商户端设备号
|
||||
|
private String device_id; |
||||
|
//商户门店信息
|
||||
|
private StoreInfo store_info; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class StoreInfo{ |
||||
|
//门店编号 是
|
||||
|
private String id; |
||||
|
//门店名称
|
||||
|
private String name; |
||||
|
//地区编码
|
||||
|
private String area_code; |
||||
|
//详细地址
|
||||
|
private String address; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class SettleInfo{ |
||||
|
//是否指定分账
|
||||
|
private boolean profit_sharing; |
||||
|
} |
||||
|
} |
@ -0,0 +1,47 @@ |
|||||
|
package com.ccsens.wechatutil.payutil.wxnative; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.ccsens.util.QrCodeUtil; |
||||
|
import com.ccsens.wechatutil.bean.vo.WxNativePay; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import okhttp3.HttpUrl; |
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class NativePay { |
||||
|
|
||||
|
/** |
||||
|
* 生成订单,返回收款二维码 |
||||
|
* @param serialNo 证书序列号 |
||||
|
* @param pathPem 秘钥文件地址 |
||||
|
* @param wxNativePay 订单信息 |
||||
|
* @param filePath 二维码存放地址(前缀路径) |
||||
|
* @return 返回二维码访问路径(不包含前缀) |
||||
|
*/ |
||||
|
public static String generateOrder( String serialNo, String pathPem, WxNativePay wxNativePay, String filePath){ |
||||
|
String path = ""; |
||||
|
try { |
||||
|
HttpUrl httpurl = HttpUrl.parse(NativeUtils.NATIVE_URL); |
||||
|
String body = String.valueOf(JSON.parseObject(JSON.toJSONString(wxNativePay))); |
||||
|
String authorization = NativeUtils.schema + " " + |
||||
|
NativeUtils.getToken("POST", httpurl, body, wxNativePay.getOut_trade_no(), wxNativePay.getMchid(), serialNo, pathPem); |
||||
|
//下单调用的接口,JSON格式
|
||||
|
String codeUrl = NativeUtils.nativePostBody(NativeUtils.NATIVE_URL, body, authorization); |
||||
|
log.info("调用微信下单返回:{}", codeUrl); |
||||
|
JSONObject jsonObject = JSONObject.parseObject(codeUrl); |
||||
|
String url = jsonObject.getString("code_url"); |
||||
|
if(StrUtil.isNotBlank(url)){ |
||||
|
path = QrCodeUtil.getQrCodeWithUtf8(url,filePath); |
||||
|
}else { |
||||
|
path = String.valueOf(jsonObject); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
return path; |
||||
|
} |
||||
|
} |
@ -0,0 +1,224 @@ |
|||||
|
package com.ccsens.wechatutil.payutil.wxnative; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.google.zxing.BarcodeFormat; |
||||
|
import com.google.zxing.EncodeHintType; |
||||
|
import com.google.zxing.MultiFormatWriter; |
||||
|
import com.google.zxing.WriterException; |
||||
|
import com.google.zxing.client.j2se.MatrixToImageWriter; |
||||
|
import com.google.zxing.common.BitMatrix; |
||||
|
import okhttp3.HttpUrl; |
||||
|
import org.apache.commons.lang3.RandomStringUtils; |
||||
|
|
||||
|
import javax.crypto.Cipher; |
||||
|
import javax.crypto.NoSuchPaddingException; |
||||
|
import javax.crypto.spec.GCMParameterSpec; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
import java.io.*; |
||||
|
import java.net.HttpURLConnection; |
||||
|
import java.net.URL; |
||||
|
import java.nio.file.FileSystems; |
||||
|
import java.nio.file.Files; |
||||
|
import java.nio.file.Path; |
||||
|
import java.nio.file.Paths; |
||||
|
import java.security.*; |
||||
|
import java.security.spec.InvalidKeySpecException; |
||||
|
import java.security.spec.PKCS8EncodedKeySpec; |
||||
|
import java.util.Base64; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* @author 逗 |
||||
|
*/ |
||||
|
public class NativeUtils { |
||||
|
|
||||
|
static String schema = "WECHATPAY2-SHA256-RSA2048"; |
||||
|
/** |
||||
|
* 请求地址 |
||||
|
*/ |
||||
|
static String NATIVE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/native"; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 获取签名信息所有值 |
||||
|
* |
||||
|
* @param method 请求方法 |
||||
|
* @param url URL地址 |
||||
|
* @param body BOdy参数 |
||||
|
* @return |
||||
|
*/ |
||||
|
static String getToken(String method, HttpUrl url, String body, String orderId, String mchId, String serialNo, String pathPem) throws Exception { |
||||
|
//获得系统时间,把毫秒换算成秒 /1000
|
||||
|
long timestamp = System.currentTimeMillis() / 1000; |
||||
|
String message = buildMessage(method, url, timestamp, orderId, body); |
||||
|
String signature = sign(message.getBytes("utf-8"), pathPem); |
||||
|
return "mchid=\"" + mchId + "\"," |
||||
|
+ "nonce_str=\"" + orderId + "\"," |
||||
|
+ "timestamp=\"" + timestamp + "\"," |
||||
|
+ "serial_no=\"" + serialNo + "\"," |
||||
|
+ "signature=\"" + signature + "\""; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 拼接明文数值 |
||||
|
* |
||||
|
* @param method 请求方法 GET or POST |
||||
|
* @param url 网络请求方法地址 取除域名项 |
||||
|
* @param timestamp 时间戳 |
||||
|
* @param nonceStr 随机数 |
||||
|
* @param body GET请求不需要Body参数,POST需要Body |
||||
|
* @return |
||||
|
*/ |
||||
|
static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { |
||||
|
String canonicalUrl = url.encodedPath(); |
||||
|
//get请求自动做了校验,会把空字符串进行识别,注意是空字符串!!!!!
|
||||
|
if (url.encodedQuery() != null) { |
||||
|
canonicalUrl += "?" + url.encodedQuery(); |
||||
|
} |
||||
|
//官方的方法自动做了换行的所有动作,注意唤起支付的参数不一样需要更换(这里是统一下单所以直接照搬即可)
|
||||
|
return method + "\n" |
||||
|
+ canonicalUrl + "\n" |
||||
|
+ timestamp + "\n" |
||||
|
+ nonceStr + "\n" |
||||
|
+ body + "\n"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 签名加密 |
||||
|
* |
||||
|
* @param message |
||||
|
* @return |
||||
|
* @throws NoSuchAlgorithmException |
||||
|
* @throws SignatureException |
||||
|
* @throws IOException |
||||
|
* @throws InvalidKeyException |
||||
|
*/ |
||||
|
static String sign(byte[] message, String pathPem) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { |
||||
|
//加密方式
|
||||
|
Signature sign = Signature.getInstance("SHA256withRSA"); |
||||
|
//私钥,通过getPrivateKey来获取,这是个方法可以接调用 ,需要的是_key.pem文件的绝对路径配上文件名
|
||||
|
sign.initSign(getPrivateKey(pathPem)); |
||||
|
sign.update(message); |
||||
|
|
||||
|
return Base64.getEncoder().encodeToString(sign.sign()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取私钥。 |
||||
|
* |
||||
|
* @param filename 私钥文件路径 (required) |
||||
|
* @return 私钥对象 |
||||
|
* <p> |
||||
|
* 完全不需要修改,注意此方法也是去掉了头部和尾部,注意文件路径名 |
||||
|
*/ |
||||
|
public static PrivateKey getPrivateKey(String filename) throws IOException { |
||||
|
|
||||
|
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8"); |
||||
|
try { |
||||
|
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") |
||||
|
.replace("-----END PRIVATE KEY-----", "") |
||||
|
.replaceAll("\\s+", ""); |
||||
|
|
||||
|
KeyFactory kf = KeyFactory.getInstance("RSA"); |
||||
|
return kf.generatePrivate( |
||||
|
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); |
||||
|
} catch (NoSuchAlgorithmException e) { |
||||
|
throw new RuntimeException("当前Java环境不支持RSA", e); |
||||
|
} catch (InvalidKeySpecException e) { |
||||
|
throw new RuntimeException("无效的密钥格式"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Post请求带Body参数 |
||||
|
* |
||||
|
* @param actionUrl |
||||
|
* @param params |
||||
|
* @param requestString |
||||
|
* @return |
||||
|
* @throws IOException |
||||
|
*/ |
||||
|
public static String nativePostBody(String actionUrl, String params, String requestString) |
||||
|
throws IOException { |
||||
|
String serverURL = actionUrl; |
||||
|
StringBuffer sbf = new StringBuffer(); |
||||
|
String strRead = null; |
||||
|
URL url = new URL(serverURL); |
||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
||||
|
//请求post方式
|
||||
|
connection.setRequestMethod("POST"); |
||||
|
connection.setDoInput(true); |
||||
|
connection.setDoOutput(true); |
||||
|
//header内的的参数在这里set
|
||||
|
connection.setRequestProperty("Content-Type", "application/json"); |
||||
|
//Native支付需要的参数表头参数
|
||||
|
connection.setRequestProperty("Authorization", requestString); |
||||
|
connection.connect(); |
||||
|
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); |
||||
|
//body参数放这里
|
||||
|
writer.write(params); |
||||
|
writer.flush(); |
||||
|
InputStream is = connection.getInputStream(); |
||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); |
||||
|
while ((strRead = reader.readLine()) != null) { |
||||
|
sbf.append(strRead); |
||||
|
sbf.append("\r\n"); |
||||
|
} |
||||
|
reader.close(); |
||||
|
connection.disconnect(); |
||||
|
String results = sbf.toString(); |
||||
|
return results; |
||||
|
} |
||||
|
|
||||
|
// public static void main(String[] args) throws Exception {
|
||||
|
// String apiV3 = "Bb507Ek1mr95aAvvv9OMp2i9eWxZSv1G";
|
||||
|
// String nonce = "bexg7pfYD50D";
|
||||
|
// String ciphertext="i01zjaHjODHIkNkIXal6lKrfxrYtp2IZFe2NeUL/tU7si3p/G52XXqxTRQLuM03D1MTImmzLXocxVOgjNmAT3wb5/2RB6ty0HT2RJzGDnAkR0OxMQseUyiOUNRW3skJtj2/somJ/O1yHI2uktJLd1hitfu2TqoFe18ueBZdFYFHqnSKoayc3qTCKFsNLQ/gArvM0ODG3qCa2x9PypmcpDEx2U0KKdNmn/kLqDjIE6RdW9RpAhG+5wqOw3717sD5uvIg66PvUp7MdGMDiDi1SdcUqRm8x7Fl43uSIq9bAcsJxW4IT877tzkDHNjSY37iOFr9vuFpjMwLnhkZfClCfivh2R9ShrmFVlR1sXikr7rYQQGn0coSQZJ09UVd0UuwSxd5SNiLISkB/MzuLfvFSe2YTFvM/MR4iJy+JLQIEBZc7ZNhv4qrcRUOnjQlWO7G9+iUpFrviItzC7eN1KTkyOBs6QPI28uUDOt/OV548i+0Pf6xmD+rOS+6vvl1WR9F4LFUvCWr3w5uMmejyCRJmWoQY12boiwT+FxsdF0NJnoSheiY+bBZfcDJ/udpo2PBHuryJ";
|
||||
|
// String associated_data = "transaction";
|
||||
|
//
|
||||
|
// byte[] key = apiV3.getBytes("UTF-8");
|
||||
|
// WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
|
||||
|
// String decryptToString = aesUtil.decryptToString(associated_data.getBytes("UTF-8"),nonce.getBytes("UTF-8"),ciphertext);
|
||||
|
// System.out.println(decryptToString);
|
||||
|
// }
|
||||
|
|
||||
|
/** |
||||
|
* 解密微信ApiV3消息 |
||||
|
*/ |
||||
|
public static class WxAPIV3AesUtil { |
||||
|
|
||||
|
static final int KEY_LENGTH_BYTE = 32; |
||||
|
static final int TAG_LENGTH_BIT = 128; |
||||
|
private final byte[] aesKey; |
||||
|
|
||||
|
public WxAPIV3AesUtil(byte[] key) { |
||||
|
if (key.length != KEY_LENGTH_BYTE) { |
||||
|
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); |
||||
|
} |
||||
|
this.aesKey = key; |
||||
|
} |
||||
|
|
||||
|
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) |
||||
|
throws GeneralSecurityException, IOException { |
||||
|
try { |
||||
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); |
||||
|
|
||||
|
SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); |
||||
|
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); |
||||
|
|
||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec); |
||||
|
cipher.updateAAD(associatedData); |
||||
|
|
||||
|
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); |
||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
||||
|
throw new IllegalStateException(e); |
||||
|
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { |
||||
|
throw new IllegalArgumentException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue