新增微信支付所需要的基础代码
This commit is contained in:
parent
938d8d4208
commit
b1bafbc6a3
@ -0,0 +1,96 @@
|
||||
package ink.wgink.module.wechat.enums;
|
||||
|
||||
/**
|
||||
* @ClassName: PayErrorMsgEnum
|
||||
* @Description: 支付错误码
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/19 9:21 上午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public enum PayErrorMsgEnum {
|
||||
|
||||
USERPAYING_200(202, "USERPAYING", "用户支付中,需要输入密码", "等待5秒,然后调用被扫订单结果查询API,查询当前订单的不同状态,决定下一步的操作"),
|
||||
|
||||
APPID_MCHID_NOT_MATCH_400(400, "APPID_MCHID_NOT_MATCH", "appid和mch_id不匹配", "请确认appid和mch_id是否匹配"),
|
||||
PARAM_ERROR_400(400, "PARAM_ERROR", "业务规则限制", "请根据接口返回的详细信息检查请求参数"),
|
||||
ORDER_CLOSED(400, "ORDER_CLOSED", "订单已关闭", "当前订单已关闭,请重新下单"),
|
||||
MCH_NOT_EXISTS_400(400, "MCH_NOT_EXISTS", "商户号不存在", "请检查商户号是否正确"),
|
||||
INVALID_REQUEST_400(400, "INVALID_REQUEST", "无效请求", "请根据接口返回的详细信息检查"),
|
||||
PHONE_NOT_EXIST_400(400, "PHONE_NOT_EXIST", "手机号不存在", "请检查手机号码是否正确"),
|
||||
AUTH_CODE_INVALID_400(400, "AUTH_CODE_INVALID", "收银员扫描的不是微信支付的条码", "请扫描微信支付被扫条码/二维码"),
|
||||
NO_STATEMENT_EXIST_400(400, "NO_STATEMENT_EXIST", "账单文件不存在", "请检查当前商户号是否在指定日期有交易或退款发生"),
|
||||
STATEMENT_CREATING_400(400, "STATEMENT_CREATING", "账单生成中", "请先检查当前商户号在指定日期内是否有成功的交易或退款,若有,则在T+1日上午8点后再重新下载"),
|
||||
RESOURCE_ALREADY_EXISTS_400(400, "RESOURCE_ALREADY_EXISTS", "用户已签约该商户,不可重复签约", "请通过查询用户接口获取用户的签约信息"),
|
||||
ALREADY_EXISTS_400(400, "ALREADY_EXISTS", "资源已存在", "尝试创建的资源已存在,无需重复创建"),
|
||||
USER_NOT_REGISTERED_400(400, "USER_NOT_REGISTERED", "服务未开通或账号未注册", "该用户尚未注册或开通当前服务,请开通后再试"),
|
||||
ORDER_PAID_400(400, "ORDER_PAID", "订单已支付", "请确认该订单号是否重复支付,如果是新单,请使用新订单号提交"),
|
||||
ORDER_REVERSED_400(400, "ORDER_REVERSED", "订单已撤销", "当前订单状态为“订单已撤销”,请提示用户重新支付"),
|
||||
ORDERCLOSED_400(400, "ORDERCLOSED", "订单已关闭", "商户订单号异常,请重新下单支付"),
|
||||
ORDERPAID_400(400, "ORDERPAID", "订单已支付", "请确认该订单号是否重复支付,如果是新单,请使用新订单号提交"),
|
||||
ORDERREVERSED_400(400, "ORDERREVERSED", "订单已撤销", "当前订单状态为“订单已撤销”,请提示用户重新支付"),
|
||||
|
||||
SIGN_ERROR_401(401, "SIGN_ERROR", "签名错误", "请检查签名参数和方法是否都符合签名算法要求"),
|
||||
|
||||
TRADE_ERROR_403(403, "TRADE_ERROR", "交易错误", "因业务原因交易失败,请查看接口返回的详细信息"),
|
||||
OUT_TRADE_NO_USED_403(403, "OUT_TRADE_NO_USED", "商户订单号重复", "请核实商户订单号是否重复提交"),
|
||||
ACCOUNT_ERROR_403(403, "ACCOUNT_ERROR", "账号异常", "用户账号异常,无需更多操作"),
|
||||
RULE_LIMIT_403(403, "RULE_LIMIT", "业务规则限制", "因业务规则限制请求频率,请查看接口返回的详细信息"),
|
||||
NOT_ENOUGH_403(403, "NOT_ENOUGH", "余额不足", "用户账号余额不足,请用户充值或更换支付卡后再支付"),
|
||||
NO_AUTH_403(403, "NO_AUTH", "商户无权限", "请商户前往申请此接口相关权限"),
|
||||
REQUEST_BLOCKED_403(403, "REQUEST_BLOCKED", "请求受阻", "此状态代表退款申请失败,商户可根据具体的错误提示做相应的处理"),
|
||||
CONTRACT_NOT_EXIST_403(403, "CONTRACT_NOT_EXIST", "签约协议不存在", "请检查签约协议号是否正确,是否已解约"),
|
||||
CONTRACT_EXISTED_403(403, "CONTRACT_EXISTED", "协议已存在", "已开通自动扣费服务功能,无需重复开通"),
|
||||
AUTH_CODE_EXPIRE_403(403, "AUTH_CODE_EXPIRE", "用户的条码已经过期", "请收银员提示用户,请用户在微信上刷新条码,然后请收银员重新扫码。 直接将错误展示给收银员"),
|
||||
USER_ACCOUNT_ABNORMAL_403(403, "USER_ACCOUNT_ABNORMAL", "用户账户异常", "该确认用户账号是否正常,商家可联系微信支付或让用户联系微信支付客服处理"),
|
||||
CONTRACT_ERROR_403(403, "CONTRACT_ERROR", "当前用户签约状态失效", "请通过查询用户接口核实签约状态"),
|
||||
CONTRACT_NOT_CONFIRMED_403(403, "CONTRACT_NOT_CONFIRMED", "二级商户未开启手动提现权限", "二级商户号提现权限已关闭,无法发起提现"),
|
||||
ACCOUNT_NOT_VERIFIED_403(403, "ACCOUNT_NOT_VERIFIED", "二级商户下行打款未成功", "二级商户号结算银行卡信息有误,修改后重试"),
|
||||
|
||||
REFUND_NOT_EXISTS_404(404, "REFUND_NOT_EXISTS", "订单号错误或订单状态不正确", "请检查订单号是否有误以及订单状态是否正确,如:未支付、已支付未退款"),
|
||||
ORDER_NO_TEXIST_404(404, "ORDER_NO_TEXIST", "订单不存在", "请检查订单是否发起过交易"),
|
||||
ORDER_NOT_EXIST_404(404, "ORDER_NOT_EXIST", "请求的资源不存在", "请商户检查需要查询的id或者请求URL是否正确"),
|
||||
USER_NOT_EXIST_404(404, "USER_NOT_EXIST", "用户账户注销", "请检查用户账户是否正确"),
|
||||
USER_NOT_EXISTS_404(404, "USER_NOT_EXISTS", "openid不正确", "请确认传入的openid是否正确"),
|
||||
MCH_NOT_EXISTS_404(404, "MCH_NOT_EXISTS", "商户号不存在", "请确认传入的商户号是否正确"),
|
||||
RESOURCE_NOT_EXISTS_404(404, "RESOURCE_NOT_EXISTS", "查询的资源不存在", "请检查查询资源的对应id是否填写正确"),
|
||||
NOT_FOUND_404(404, "NOT_FOUND", "请求的资源不存在", "请商户检查需要查询的id或者请求URL是否正确"),
|
||||
|
||||
FREQUENCY_LIMITED_429(429, "FREQUENCY_LIMITED", "频率超限", "请求量不要超过接口调用频率限制"),
|
||||
RATELIMIT_EXCEEDED_429(429, "RATELIMIT_EXCEEDED", "频率限制", "请降低频率后重试"),
|
||||
FREQUENCY_LIMIT_EXCEED_429(429, "FREQUENCY_LIMIT_EXCEED", "接口限频", "请降低调用频率"),
|
||||
|
||||
ERROR_500(500, "ERROR", "业务错误", "该错误都会返回具体的错误原因,请根据实际返回做相应处理"),
|
||||
OPENID_MISMATCH_500(500, "OPENID_MISMATCH", "openid和appid不匹配", "请确认openid和appid是否匹配"),
|
||||
BANK_ERROR_500(500, "BANK_ERROR", "银行系统异常", "银行系统异常,请用相同参数重新调用"),
|
||||
INVALID_TRANSACTIONID_500(500, "INVALID_TRANSACTIONID", "订单号非法", "请检查微信支付订单号是否正确"),
|
||||
BIZ_ERR_NEED_RETRY_500(500, "BIZ_ERR_NEED_RETRY", "退款业务流程错误", "请不要更换商户退款单号,请使用相同参数再次调用API"),
|
||||
SYSTEM_ERROR_500(500, "SYSTEM_ERROR", "系统错误", "系统异常,请用相同参数重新调用");
|
||||
|
||||
private int code;
|
||||
private String errorCode;
|
||||
private String summary;
|
||||
private String solution;
|
||||
|
||||
PayErrorMsgEnum(int code, String errorCode, String summary, String solution) {
|
||||
this.code = code;
|
||||
this.errorCode = errorCode;
|
||||
this.summary = summary;
|
||||
this.solution = solution;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode == null ? "" : errorCode.trim();
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary == null ? "" : summary.trim();
|
||||
}
|
||||
|
||||
public String getSolution() {
|
||||
return solution == null ? "" : solution.trim();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package ink.wgink.module.wechat.manager.pay.v3.miniapp;
|
||||
|
||||
/**
|
||||
* @ClassName: PayMiniappManager
|
||||
* @Description: 小程序支付管理
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/19 11:10 上午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PayMiniappManager {
|
||||
|
||||
private static final PayMiniappManager payMiniappManager = PayMiniappManagerBuilder.payMiniappManager;
|
||||
|
||||
private PayMiniappManager() {
|
||||
}
|
||||
|
||||
public PayMiniappManager getInstance() {
|
||||
return payMiniappManager;
|
||||
}
|
||||
|
||||
private static class PayMiniappManagerBuilder {
|
||||
public static PayMiniappManager payMiniappManager = new PayMiniappManager();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,494 @@
|
||||
package ink.wgink.module.wechat.pojo.bos.pay.v3;
|
||||
|
||||
import ink.wgink.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName: PlaceOrderBO
|
||||
* @Description: 下单
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/19 1:52 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PlaceOrderBO {
|
||||
|
||||
@CheckEmptyAnnotation(name = "应用ID")
|
||||
private String appid;
|
||||
@CheckEmptyAnnotation(name = "直连商户号")
|
||||
private String mchid;
|
||||
@CheckEmptyAnnotation(name = "商品描述")
|
||||
private String description;
|
||||
@CheckEmptyAnnotation(name = "商户订单号")
|
||||
private String outTradeNo;
|
||||
/**
|
||||
* 交易结束时间
|
||||
*/
|
||||
private String timeExpire;
|
||||
/**
|
||||
* 附加数据
|
||||
*/
|
||||
private String attach;
|
||||
@CheckEmptyAnnotation(name = "通知地址")
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 订单优惠标记
|
||||
*/
|
||||
private String goodsTag;
|
||||
@CheckNullAnnotation(name = "订单金额")
|
||||
@CheckBeanAnnotation
|
||||
private Amount amount;
|
||||
@CheckNullAnnotation(name = "支付者")
|
||||
@CheckBeanAnnotation
|
||||
private Payer payer;
|
||||
/**
|
||||
* 优惠功能
|
||||
*/
|
||||
@CheckBeanAnnotation
|
||||
private Detail detail;
|
||||
/**
|
||||
* 场景信息
|
||||
*/
|
||||
@CheckBeanAnnotation
|
||||
private SceneInfo sceneInfo;
|
||||
/**
|
||||
* 结算信息
|
||||
*/
|
||||
@CheckBeanAnnotation
|
||||
private SettleInfo settleInfo;
|
||||
|
||||
public String getAppid() {
|
||||
return appid == null ? "" : appid.trim();
|
||||
}
|
||||
|
||||
public void setAppid(String appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
|
||||
public String getMchid() {
|
||||
return mchid == null ? "" : mchid.trim();
|
||||
}
|
||||
|
||||
public void setMchid(String mchid) {
|
||||
this.mchid = mchid;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description == null ? "" : description.trim();
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getOutTradeNo() {
|
||||
return outTradeNo == null ? "" : outTradeNo.trim();
|
||||
}
|
||||
|
||||
public void setOutTradeNo(String outTradeNo) {
|
||||
this.outTradeNo = outTradeNo;
|
||||
}
|
||||
|
||||
public String getTimeExpire() {
|
||||
return timeExpire == null ? "" : timeExpire.trim();
|
||||
}
|
||||
|
||||
public void setTimeExpire(String timeExpire) {
|
||||
this.timeExpire = timeExpire;
|
||||
}
|
||||
|
||||
public String getAttach() {
|
||||
return attach == null ? "" : attach.trim();
|
||||
}
|
||||
|
||||
public void setAttach(String attach) {
|
||||
this.attach = attach;
|
||||
}
|
||||
|
||||
public String getNotifyUrl() {
|
||||
return notifyUrl == null ? "" : notifyUrl.trim();
|
||||
}
|
||||
|
||||
public void setNotifyUrl(String notifyUrl) {
|
||||
this.notifyUrl = notifyUrl;
|
||||
}
|
||||
|
||||
public String getGoodsTag() {
|
||||
return goodsTag == null ? "" : goodsTag.trim();
|
||||
}
|
||||
|
||||
public void setGoodsTag(String goodsTag) {
|
||||
this.goodsTag = goodsTag;
|
||||
}
|
||||
|
||||
public Amount getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Amount amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Payer getPayer() {
|
||||
return payer;
|
||||
}
|
||||
|
||||
public void setPayer(Payer payer) {
|
||||
this.payer = payer;
|
||||
}
|
||||
|
||||
public Detail getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(Detail detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public SceneInfo getSceneInfo() {
|
||||
return sceneInfo;
|
||||
}
|
||||
|
||||
public void setSceneInfo(SceneInfo sceneInfo) {
|
||||
this.sceneInfo = sceneInfo;
|
||||
}
|
||||
|
||||
public SettleInfo getSettleInfo() {
|
||||
return settleInfo;
|
||||
}
|
||||
|
||||
public void setSettleInfo(SettleInfo settleInfo) {
|
||||
this.settleInfo = settleInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"appid\":\"")
|
||||
.append(appid).append('\"');
|
||||
sb.append(",\"mchid\":\"")
|
||||
.append(mchid).append('\"');
|
||||
sb.append(",\"description\":\"")
|
||||
.append(description).append('\"');
|
||||
sb.append(",\"outTradeNo\":\"")
|
||||
.append(outTradeNo).append('\"');
|
||||
sb.append(",\"timeExpire\":\"")
|
||||
.append(timeExpire).append('\"');
|
||||
sb.append(",\"attach\":\"")
|
||||
.append(attach).append('\"');
|
||||
sb.append(",\"notifyUrl\":\"")
|
||||
.append(notifyUrl).append('\"');
|
||||
sb.append(",\"goodsTag\":\"")
|
||||
.append(goodsTag).append('\"');
|
||||
sb.append(",\"amount\":")
|
||||
.append(amount);
|
||||
sb.append(",\"payer\":")
|
||||
.append(payer);
|
||||
sb.append(",\"detail\":")
|
||||
.append(detail);
|
||||
sb.append(",\"sceneInfo\":")
|
||||
.append(sceneInfo);
|
||||
sb.append(",\"settleInfo\":")
|
||||
.append(settleInfo);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单金额
|
||||
*/
|
||||
public static class Amount {
|
||||
@CheckNumberAnnotation(name = "总金额", min = 0)
|
||||
private Integer total;
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private String currency;
|
||||
|
||||
public Integer getTotal() {
|
||||
return total == null ? 0 : total;
|
||||
}
|
||||
|
||||
public void setTotal(Integer total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency == null ? "" : currency.trim();
|
||||
}
|
||||
|
||||
public void setCurrency(String currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"total\":")
|
||||
.append(total);
|
||||
sb.append(",\"currency\":\"")
|
||||
.append(currency).append('\"');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付者
|
||||
*/
|
||||
public static class Payer {
|
||||
@CheckEmptyAnnotation(name = "用户标识")
|
||||
private String openid;
|
||||
|
||||
public String getOpenid() {
|
||||
return openid == null ? "" : openid.trim();
|
||||
}
|
||||
|
||||
public void setOpenid(String openid) {
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"openid\":\"")
|
||||
.append(openid).append('\"');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠功能
|
||||
*/
|
||||
public static class Detail {
|
||||
/**
|
||||
* 订单原价
|
||||
*/
|
||||
private Integer costPrice;
|
||||
/**
|
||||
* 商品小票ID
|
||||
*/
|
||||
private String invoiceId;
|
||||
@CheckListBeanAnnotation
|
||||
private List<Goods> goodsDetail;
|
||||
|
||||
public Integer getCostPrice() {
|
||||
return costPrice == null ? 0 : costPrice;
|
||||
}
|
||||
|
||||
public void setCostPrice(Integer costPrice) {
|
||||
this.costPrice = costPrice;
|
||||
}
|
||||
|
||||
public String getInvoiceId() {
|
||||
return invoiceId == null ? "" : invoiceId.trim();
|
||||
}
|
||||
|
||||
public void setInvoiceId(String invoiceId) {
|
||||
this.invoiceId = invoiceId;
|
||||
}
|
||||
|
||||
public List<Goods> getGoodsDetail() {
|
||||
return goodsDetail;
|
||||
}
|
||||
|
||||
public void setGoodsDetail(List<Goods> goodsDetail) {
|
||||
this.goodsDetail = goodsDetail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"costPrice\":")
|
||||
.append(costPrice);
|
||||
sb.append(",\"invoiceId\":\"")
|
||||
.append(invoiceId).append('\"');
|
||||
sb.append(",\"goodsDetail\":")
|
||||
.append(goodsDetail);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static class Goods {
|
||||
@CheckEmptyAnnotation(name = "商户侧商品编码")
|
||||
private String merchantGoodsId;
|
||||
/**
|
||||
* 微信侧商品编码
|
||||
*/
|
||||
private String wechatpayGoodsId;
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String goodsName;
|
||||
@CheckNumberAnnotation(name = "商品数量", min = 0)
|
||||
private Integer quantity;
|
||||
@CheckNumberAnnotation(name = "商品单价", min = 0)
|
||||
private Integer unitPrice;
|
||||
|
||||
public String getMerchantGoodsId() {
|
||||
return merchantGoodsId == null ? "" : merchantGoodsId.trim();
|
||||
}
|
||||
|
||||
public void setMerchantGoodsId(String merchantGoodsId) {
|
||||
this.merchantGoodsId = merchantGoodsId;
|
||||
}
|
||||
|
||||
public String getWechatpayGoodsId() {
|
||||
return wechatpayGoodsId == null ? "" : wechatpayGoodsId.trim();
|
||||
}
|
||||
|
||||
public void setWechatpayGoodsId(String wechatpayGoodsId) {
|
||||
this.wechatpayGoodsId = wechatpayGoodsId;
|
||||
}
|
||||
|
||||
public String getGoodsName() {
|
||||
return goodsName == null ? "" : goodsName.trim();
|
||||
}
|
||||
|
||||
public void setGoodsName(String goodsName) {
|
||||
this.goodsName = goodsName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity == null ? 0 : quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Integer getUnitPrice() {
|
||||
return unitPrice == null ? 0 : unitPrice;
|
||||
}
|
||||
|
||||
public void setUnitPrice(Integer unitPrice) {
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"merchantGoodsId\":\"")
|
||||
.append(merchantGoodsId).append('\"');
|
||||
sb.append(",\"wechatpayGoodsId\":\"")
|
||||
.append(wechatpayGoodsId).append('\"');
|
||||
sb.append(",\"goodsName\":\"")
|
||||
.append(goodsName).append('\"');
|
||||
sb.append(",\"quantity\":")
|
||||
.append(quantity);
|
||||
sb.append(",\"unitPrice\":")
|
||||
.append(unitPrice);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景信息
|
||||
*/
|
||||
public static class SceneInfo {
|
||||
@CheckEmptyAnnotation(name = "用户终端IP")
|
||||
private String payerClientIp;
|
||||
/**
|
||||
* 商户端设备号
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 商户门店信息
|
||||
*/
|
||||
public static class StoreInfo {
|
||||
@CheckEmptyAnnotation(name = "门店编号")
|
||||
private String id;
|
||||
/**
|
||||
* 门店名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 地区编码
|
||||
*/
|
||||
private String areaCode;
|
||||
/**
|
||||
* 详细地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
public String getId() {
|
||||
return id == null ? "" : id.trim();
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name == null ? "" : name.trim();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAreaCode() {
|
||||
return areaCode == null ? "" : areaCode.trim();
|
||||
}
|
||||
|
||||
public void setAreaCode(String areaCode) {
|
||||
this.areaCode = areaCode;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address == null ? "" : address.trim();
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"id\":\"")
|
||||
.append(id).append('\"');
|
||||
sb.append(",\"name\":\"")
|
||||
.append(name).append('\"');
|
||||
sb.append(",\"areaCode\":\"")
|
||||
.append(areaCode).append('\"');
|
||||
sb.append(",\"address\":\"")
|
||||
.append(address).append('\"');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算信息
|
||||
*/
|
||||
public static class SettleInfo {
|
||||
/**
|
||||
* 是否指定分账
|
||||
*/
|
||||
private Boolean profitSharing;
|
||||
|
||||
public Boolean getProfitSharing() {
|
||||
return profitSharing;
|
||||
}
|
||||
|
||||
public void setProfitSharing(Boolean profitSharing) {
|
||||
this.profitSharing = profitSharing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("{");
|
||||
sb.append("\"profitSharing\":")
|
||||
.append(profitSharing);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -100,4 +100,20 @@ public interface IMiniappUserService extends IMiniappUserBaseService {
|
||||
*/
|
||||
MiniappUserPO getPO(String appId, String openId);
|
||||
|
||||
/**
|
||||
* 用户码获取用户列表
|
||||
*
|
||||
* @param userCode 用户码
|
||||
* @return
|
||||
*/
|
||||
MiniappUserPO getPOByUserCode(String userCode);
|
||||
|
||||
/**
|
||||
* 用户码获取用户列表
|
||||
*
|
||||
* @param userCodes 用户码列表
|
||||
* @return
|
||||
*/
|
||||
List<MiniappUserPO> listPOByUserCodes(List<String> userCodes);
|
||||
|
||||
}
|
||||
|
@ -159,6 +159,20 @@ public class MiniappUserServiceImpl extends DefaultBaseService implements IMinia
|
||||
return getPO(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MiniappUserPO getPOByUserCode(String userCode) {
|
||||
Map<String, Object> params = getHashMap(2);
|
||||
params.put("userCode", userCode);
|
||||
return getPO(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MiniappUserPO> listPOByUserCodes(List<String> userCodes) {
|
||||
Map<String, Object> params = getHashMap(2);
|
||||
params.put("userCodes", userCodes);
|
||||
return listPO(params);
|
||||
}
|
||||
|
||||
private void updateUserCode(String userCode, String openId) {
|
||||
Map<String, Object> params = getHashMap(6);
|
||||
params.put("userCode", userCode);
|
||||
|
@ -120,9 +120,17 @@ public interface IOfficialAccountUserService extends IOfficialAccountBaseService
|
||||
/**
|
||||
* 用户码获取用户
|
||||
*
|
||||
* @param userCode
|
||||
* @param userCode 用户码
|
||||
* @return
|
||||
*/
|
||||
OfficialAccountUserPO getPOByUserCode(String userCode);
|
||||
|
||||
/**
|
||||
* 用户码获取用户列表
|
||||
*
|
||||
* @param userCodes 用户码列表
|
||||
* @return
|
||||
*/
|
||||
List<OfficialAccountUserPO> listPOByUserCodes(List<String> userCodes);
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ink.wgink.module.wechat.service.official.account.impl;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import ink.wgink.common.base.DefaultBaseService;
|
||||
import ink.wgink.interfaces.consts.ISystemConstant;
|
||||
import ink.wgink.module.wechat.dao.official.account.IOfficialAccountUserDao;
|
||||
@ -166,7 +168,10 @@ public class OfficialAccountUserServiceImpl extends DefaultBaseService implement
|
||||
|
||||
@Override
|
||||
public SuccessResultList<List<OfficialAccountUserDTO>> listPage(ListPage page) {
|
||||
return null;
|
||||
PageHelper.startPage(page.getPage(), page.getRows());
|
||||
List<OfficialAccountUserDTO> officialAccountUserDTOs = officialAccountUserDao.list(page.getParams());
|
||||
PageInfo<OfficialAccountUserDTO> pageInfo = new PageInfo<>(officialAccountUserDTOs);
|
||||
return new SuccessResultList<>(officialAccountUserDTOs, pageInfo.getPageNum(), pageInfo.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -182,6 +187,13 @@ public class OfficialAccountUserServiceImpl extends DefaultBaseService implement
|
||||
return getPO(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OfficialAccountUserPO> listPOByUserCodes(List<String> userCodes) {
|
||||
Map<String, Object> params = getHashMap(2);
|
||||
params.put("userCodes", userCodes);
|
||||
return listPO(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户码(邀请码)
|
||||
*
|
||||
|
@ -0,0 +1,11 @@
|
||||
package ink.wgink.module.wechat.service.pay.v3.jsapi;
|
||||
|
||||
/**
|
||||
* @ClassName: IJsapiService
|
||||
* @Description: Jsapi业务
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/19 10:42 上午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public interface IJsapiService {
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package ink.wgink.module.wechat.service.pay.v3.jsapi.impl;
|
||||
|
||||
import ink.wgink.common.base.DefaultBaseService;
|
||||
import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @ClassName: JsapiServiceImpl
|
||||
* @Description: Jsapi业务
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/19 10:42 上午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
@Service
|
||||
public class JsapiServiceImpl extends DefaultBaseService implements IJsapiService {
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @ClassName: PayRequestUtil
|
||||
* @Description: 支付请求
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 9:41 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public abstract class AbstractPayRequest {
|
||||
|
||||
public static final String PAY_SERIAL = "Wechatpay-Serial";
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public AbstractPayRequest() {
|
||||
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
|
||||
simpleClientHttpRequestFactory.setConnectTimeout(20 * 1000);
|
||||
simpleClientHttpRequestFactory.setReadTimeout(60 * 1000);
|
||||
this.restTemplate = new RestTemplate(simpleClientHttpRequestFactory);
|
||||
}
|
||||
|
||||
public abstract void response(ResponseEntity<String> responseEntity);
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
*/
|
||||
public void get(String url, String authorization, String serialNumber) {
|
||||
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
|
||||
response(responseEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
* @param jsonBody 请求参数
|
||||
*/
|
||||
public void post(String url, String authorization, String serialNumber, String jsonBody) {
|
||||
HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
|
||||
response(responseEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
* @param jsonBody 请求参数
|
||||
*/
|
||||
public void put(String url, String authorization, String serialNumber, String jsonBody) {
|
||||
HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
|
||||
response(responseEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete 请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
*/
|
||||
public void delete(String url, String authorization, String serialNumber) {
|
||||
HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
|
||||
response(responseEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
* @param jsonData 请求参数
|
||||
* @param uploadFile 上传文件
|
||||
*/
|
||||
public void upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) {
|
||||
FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
|
||||
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
|
||||
form.add("file", fileSystemResource);
|
||||
form.add("meta", jsonData);
|
||||
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, getUploadHeaders(authorization, serialNumber));
|
||||
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
|
||||
response(responseEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*
|
||||
* @param authorization 授权信息
|
||||
* @return
|
||||
*/
|
||||
private HttpHeaders getHttpBaseHeaders(String authorization) {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
httpHeaders.add("Authorization", authorization);
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON请求头
|
||||
*
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
* @return
|
||||
*/
|
||||
private HttpHeaders getHttpHeaders(String authorization, String serialNumber) {
|
||||
HttpHeaders httpHeaders = getHttpBaseHeaders(authorization);
|
||||
httpHeaders.add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
if (!StringUtils.isBlank(serialNumber)) {
|
||||
httpHeaders.add(PAY_SERIAL, serialNumber);
|
||||
}
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件请求头
|
||||
*
|
||||
* @param authorization 授权信息
|
||||
* @param serialNumber 公钥证书序列号
|
||||
* @return
|
||||
*/
|
||||
private HttpHeaders getUploadHeaders(String authorization, String serialNumber) {
|
||||
HttpHeaders httpHeaders = getHttpBaseHeaders(authorization);
|
||||
httpHeaders.add("Content-Type", "multipart/form-data;boundary=\"boundary\"");
|
||||
if (!StringUtils.isBlank(serialNumber)) {
|
||||
httpHeaders.add(PAY_SERIAL, serialNumber);
|
||||
}
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aes 解密
|
||||
*/
|
||||
public static class AesUtil {
|
||||
static final int KEY_LENGTH_BYTE = 32;
|
||||
static final int TAG_LENGTH_BIT = 128;
|
||||
private final byte[] aesKey;
|
||||
|
||||
/**
|
||||
* @param key APIv3秘钥
|
||||
*/
|
||||
public AesUtil(byte[] key) {
|
||||
if (key.length != KEY_LENGTH_BYTE) {
|
||||
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
|
||||
}
|
||||
this.aesKey = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param associatedData 关联数据
|
||||
* @param nonce 加密使用的随机串初始化向量
|
||||
* @param ciphertext 密文
|
||||
* @return
|
||||
* @throws GeneralSecurityException
|
||||
* @throws IOException
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.*;
|
||||
|
||||
/**
|
||||
* @ClassName: PayCertificateUtil
|
||||
* @Description: 证书工具
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 8:38 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PayCertificateUtil {
|
||||
|
||||
/**
|
||||
* 获取证书
|
||||
*
|
||||
* @param inputStream
|
||||
* @return
|
||||
*/
|
||||
public static X509Certificate getCertificate(InputStream inputStream) {
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
|
||||
cert.checkValidity();
|
||||
return cert;
|
||||
} catch (CertificateExpiredException e) {
|
||||
throw new RuntimeException("证书已过期", e);
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
throw new RuntimeException("证书尚未生效", e);
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("无效的证书", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
X509Certificate certificate = PayCertificateUtil.getCertificate(new FileInputStream(new File("/Users/wanggeng/tencent/tenlion/cert/1609461201_20210818_cert/apiclient_cert.pem")));
|
||||
|
||||
System.out.println("输出证书信息:\n" + certificate.toString());
|
||||
System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
|
||||
System.out.println("版本号:" + certificate.getVersion());
|
||||
System.out.println("签发者:" + certificate.getIssuerDN());
|
||||
System.out.println("有效起始日期:" + certificate.getNotBefore());
|
||||
System.out.println("有效终止日期:" + certificate.getNotAfter());
|
||||
System.out.println("主体名:" + certificate.getSubjectDN());
|
||||
System.out.println("签名算法:" + certificate.getSigAlgName());
|
||||
System.out.println("签名:" + certificate.getSignature().toString());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import ink.wgink.interfaces.consts.ISystemConstant;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* @ClassName: PayPrivateKeyUtil
|
||||
* @Description: 商户私钥工具
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 8:43 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PayPrivateKeyUtil {
|
||||
|
||||
/**
|
||||
* 算法
|
||||
*/
|
||||
public static final String KEY_ALGORITHM = "RSA";
|
||||
|
||||
/**
|
||||
* 获取商户私钥
|
||||
*
|
||||
* @param keyPath 商户私钥证书路径
|
||||
* @return 商户私钥
|
||||
* @throws Exception 解析 key 异常
|
||||
*/
|
||||
public static String getPrivateKey(String keyPath) throws Exception {
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(keyPath));
|
||||
StringBuilder apiClientKeySB = new StringBuilder();
|
||||
for (String line; (line = bufferedReader.readLine()) != null; ) {
|
||||
apiClientKeySB.append(line);
|
||||
}
|
||||
String originalKey = apiClientKeySB.toString();
|
||||
System.out.println(originalKey);
|
||||
String privateKey = originalKey
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
return PayPrivateKeyUtil.getPrivateKeyStr(PayPrivateKeyUtil.loadPrivateKey(privateKey));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 私钥字符串
|
||||
*
|
||||
* @param privateKey
|
||||
* @return
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String getPrivateKeyStr(PrivateKey privateKey) throws UnsupportedEncodingException {
|
||||
return new String(Base64.encode(privateKey.getEncoded()), ISystemConstant.CHARSET_UTF8);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从字符串中加载私钥
|
||||
*
|
||||
* @param privateKeyStr 私钥
|
||||
* @return {@link PrivateKey}
|
||||
* @throws Exception 异常信息
|
||||
*/
|
||||
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
|
||||
try {
|
||||
byte[] buffer = Base64.decode(privateKeyStr);
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
return keyFactory.generatePrivate(keySpec);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new Exception("无此算法");
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new Exception("私钥非法");
|
||||
} catch (NullPointerException e) {
|
||||
throw new Exception("私钥数据为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 私钥签名
|
||||
*
|
||||
* @param data 需要加密的数据
|
||||
* @param privateKey 私钥
|
||||
* @return 加密后的数据
|
||||
* @throws Exception 异常信息
|
||||
*/
|
||||
public static String encryptByPrivateKey(String data, String privateKey) throws Exception {
|
||||
PKCS8EncodedKeySpec priPkcs8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
PrivateKey priKey = keyFactory.generatePrivate(priPkcs8);
|
||||
java.security.Signature signature = Signature.getInstance("SHA256WithRSA");
|
||||
|
||||
signature.initSign(priKey);
|
||||
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] signed = signature.sign();
|
||||
return new String(Base64.encode(signed), ISystemConstant.CHARSET_UTF8);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import ink.wgink.interfaces.consts.ISystemConstant;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @ClassName: PaySignAuthorizationUtil
|
||||
* @Description: 支付签名和认证工具
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 5:45 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PaySignAuthorizationUtil {
|
||||
|
||||
// 商户号:1609461201
|
||||
// APIv3秘钥:_TSKJ_0471_TSkj_0471_tsKJ_0471__
|
||||
|
||||
/**
|
||||
* @param requestMethod 请求方法
|
||||
* @param url 请求接口
|
||||
* @param timestamp 时间戳
|
||||
* @param nonceStr 随机字符串
|
||||
* @param body 请求主体
|
||||
* @return
|
||||
*/
|
||||
public static String getSign(RequestMethod requestMethod, String url, long timestamp, String nonceStr, String body) {
|
||||
return new StringBuilder()
|
||||
.append(requestMethod.toString())
|
||||
.append("\n")
|
||||
.append(url)
|
||||
.append("\n")
|
||||
.append(timestamp)
|
||||
.append("\n")
|
||||
.append(nonceStr)
|
||||
.append("\n")
|
||||
.append(body)
|
||||
.append("\n")
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method 方法
|
||||
* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接
|
||||
* @param mchId 商户Id
|
||||
* @param serialNo 商户 API 证书序列号
|
||||
* @param keyPath key.pem 证书路径
|
||||
* @param body 接口请求参数
|
||||
* @param nonceStr 随机字符库
|
||||
* @param timestamp 时间戳
|
||||
* @param authType 认证类型
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId,
|
||||
String serialNo, String keyPath, String body, String nonceStr,
|
||||
long timestamp, String authType) throws Exception {
|
||||
// 构建签名参数
|
||||
String buildSignMessage = getSign(method, urlSuffix, timestamp, nonceStr, body);
|
||||
// 获取商户私钥
|
||||
String key = PayPrivateKeyUtil.getPrivateKey(keyPath);
|
||||
// 生成签名
|
||||
String signature = PayPrivateKeyUtil.encryptByPrivateKey(buildSignMessage, key);
|
||||
// 根据平台规则生成请求头 authorization
|
||||
return getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mchId 方法
|
||||
* @param serialNo 商户 API 证书序列号
|
||||
* @param nonceStr 随机字符库
|
||||
* @param timestamp 时间戳
|
||||
* @param signature 签名值
|
||||
* @param authType 认证类型
|
||||
* @return
|
||||
*/
|
||||
private static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) throws UnsupportedEncodingException {
|
||||
Map<String, String> params = new HashMap<>(5);
|
||||
params.put("mchid", mchId);
|
||||
params.put("serial_no", serialNo);
|
||||
params.put("nonce_str", nonceStr);
|
||||
params.put("timestamp", timestamp);
|
||||
params.put("signature", signature);
|
||||
return authType.concat(" ").concat(createLinkString(params, ",", false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接字符串
|
||||
*
|
||||
* @param params map
|
||||
* @param connStr 链接值
|
||||
* @param encode 是否编码
|
||||
* @param quotes 是否加引号
|
||||
* @return
|
||||
*/
|
||||
private static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) throws UnsupportedEncodingException {
|
||||
List<String> keys = new ArrayList<>(params.keySet());
|
||||
Collections.sort(keys);
|
||||
StringBuilder content = 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) {
|
||||
if (quotes) {
|
||||
content.append(key).append("=").append('"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('"');
|
||||
} else {
|
||||
content.append(key).append("=").append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value);
|
||||
}
|
||||
} else {
|
||||
if (quotes) {
|
||||
content.append(key).append("=").append('"').append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append('"').append(connStr);
|
||||
} else {
|
||||
content.append(key).append("=").append(encode ? URLEncoder.encode(value, ISystemConstant.CHARSET_UTF8) : value).append(connStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* @ClassName: PayVerifyUtil
|
||||
* @Description: 验证工具
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 11:20 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class PayVerifyUtil {
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*
|
||||
* @param signature
|
||||
* @param body
|
||||
* @param nonce
|
||||
* @param timestamp
|
||||
* @param certInputStream
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
|
||||
String buildSignMessage = buildSignMessage(timestamp, nonce, body);
|
||||
// 获取证书
|
||||
X509Certificate certificate = PayCertificateUtil.getCertificate(certInputStream);
|
||||
PublicKey publicKey = certificate.getPublicKey();
|
||||
return checkByPublicKey(buildSignMessage, signature, publicKey);
|
||||
}
|
||||
|
||||
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
|
||||
java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
||||
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
public static String buildSignMessage(String timestamp, String nonceStr, String body) {
|
||||
return new StringBuilder()
|
||||
.append(timestamp)
|
||||
.append("\n")
|
||||
.append(nonceStr)
|
||||
.append("\n")
|
||||
.append(body)
|
||||
.append("\n")
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package ink.wgink.module.wechat.utils.pay;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @ClassName: SensitiveDataUtil
|
||||
* @Description: 敏感信息工具
|
||||
* @Author: wanggeng
|
||||
* @Date: 2021/8/18 11:54 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class SensitiveDataUtil {
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param message 加密内容
|
||||
* @param certificate 证书
|
||||
* @return
|
||||
* @throws IllegalBlockSizeException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String rsaEncryptOAEP(String message, X509Certificate certificate) throws IllegalBlockSizeException, IOException {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
|
||||
|
||||
byte[] data = message.getBytes("utf-8");
|
||||
byte[] cipherdata = cipher.doFinal(data);
|
||||
return Base64.getEncoder().encodeToString(cipherdata);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException("无效的证书", e);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param ciphertext 密文
|
||||
* @param privateKey 私钥
|
||||
* @return
|
||||
* @throws BadPaddingException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey) throws BadPaddingException, IOException {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
byte[] data = Base64.getDecoder().decode(ciphertext);
|
||||
return new String(cipher.doFinal(data), "utf-8");
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException("无效的私钥", e);
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||
throw new BadPaddingException("解密失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -121,7 +121,14 @@
|
||||
<if test="userIds != null and userIds.size > 0">
|
||||
user_id IN
|
||||
<foreach collection="userIds" index="index" open="(" separator="," close=")">
|
||||
#{userIds[${index}]}
|
||||
#{userIds[${index}]}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="userCodes != null and userCodes.size > 0">
|
||||
AND
|
||||
user_code IN
|
||||
<foreach collection="userCodes" index="index" open="(" separator="," close=")">
|
||||
#{userCodes[${index}]}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
@ -152,6 +159,10 @@
|
||||
AND
|
||||
user_id = #{userId}
|
||||
</if>
|
||||
<if test="userCode != null and userCode != ''">
|
||||
AND
|
||||
user_code = #{userCode}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
|
@ -220,6 +220,13 @@
|
||||
<if test="status != null and status != ''">
|
||||
status = #{status}
|
||||
</if>
|
||||
<if test="userCodes != null and userCodes.size > 0">
|
||||
AND
|
||||
user_code IN
|
||||
<foreach collection="userCodes" index="index" open="(" separator="," close=")">
|
||||
#{userCodes[${index}]}
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user