Compare commits
11 Commits
v4.7.9
...
copilot/de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28e0c16e94 | ||
|
|
2ab4caf5e1 | ||
|
|
ba9473268f | ||
|
|
4e46486f2c | ||
|
|
219a8f4f36 | ||
|
|
cd4317ab3e | ||
|
|
085125960b | ||
|
|
524762704c | ||
|
|
f7a196c129 | ||
|
|
3bf3595dc1 | ||
|
|
4d0617f7bf |
@@ -46,13 +46,20 @@ public class WxPayAutoConfiguration {
|
||||
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
|
||||
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
|
||||
payConfig.setUseSandboxEnv(this.properties.isUseSandboxEnv());
|
||||
payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));
|
||||
//以下是apiv3以及支付分相关
|
||||
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
|
||||
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
|
||||
payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(this.properties.getPayScorePermissionNotifyUrl()));
|
||||
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
|
||||
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
|
||||
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
|
||||
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
|
||||
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
||||
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
||||
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
||||
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
||||
|
||||
wxPayService.setConfig(payConfig);
|
||||
return wxPayService;
|
||||
|
||||
@@ -82,4 +82,40 @@ public class WxPayProperties {
|
||||
*/
|
||||
private boolean useSandboxEnv;
|
||||
|
||||
/**
|
||||
* 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 微信支付分授权回调地址
|
||||
*/
|
||||
private String payScorePermissionNotifyUrl;
|
||||
|
||||
/**
|
||||
* 公钥ID
|
||||
*/
|
||||
private String publicKeyId;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String publicKeyPath;
|
||||
|
||||
/**
|
||||
* 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com
|
||||
* 例如:http://proxy.company.com:8080
|
||||
*/
|
||||
private String apiHostUrl;
|
||||
|
||||
/**
|
||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加
|
||||
*/
|
||||
private boolean strictlyNeedWechatPaySerial = false;
|
||||
|
||||
/**
|
||||
* 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
|
||||
*/
|
||||
private boolean fullPublicKeyModel = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@
|
||||
<artifactId>okhttp</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
<artifactId>okhttp</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType;
|
||||
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
|
||||
@@ -35,6 +36,9 @@ public class WxMpServiceAutoConfiguration {
|
||||
case HttpClient:
|
||||
wxMpService = newWxMpServiceHttpClientImpl();
|
||||
break;
|
||||
case HttpComponents:
|
||||
wxMpService = newWxMpServiceHttpComponentsImpl();
|
||||
break;
|
||||
default:
|
||||
wxMpService = newWxMpServiceImpl();
|
||||
break;
|
||||
@@ -60,4 +64,8 @@ public class WxMpServiceAutoConfiguration {
|
||||
return new WxMpServiceJoddHttpImpl();
|
||||
}
|
||||
|
||||
private WxMpService newWxMpServiceHttpComponentsImpl() {
|
||||
return new WxMpServiceHttpComponentsImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,4 +19,8 @@ public enum HttpClientType {
|
||||
* JoddHttp.
|
||||
*/
|
||||
JoddHttp,
|
||||
/**
|
||||
* HttpComponents (Apache HttpClient 5.x).
|
||||
*/
|
||||
HttpComponents,
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ public class WxPayAutoConfiguration {
|
||||
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
||||
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
||||
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
||||
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
||||
|
||||
wxPayService.setConfig(payConfig);
|
||||
return wxPayService;
|
||||
|
||||
@@ -106,4 +106,14 @@ public class WxPayProperties {
|
||||
*/
|
||||
private String apiHostUrl;
|
||||
|
||||
/**
|
||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加
|
||||
*/
|
||||
private boolean strictlyNeedWechatPaySerial = false;
|
||||
|
||||
/**
|
||||
* 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用
|
||||
*/
|
||||
private boolean fullPublicKeyModel = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ public class WxChannelServiceOkHttpImpl extends BaseWxChannelServiceImpl<OkHttpC
|
||||
this.httpProxy = OkHttpProxyInfo.httpProxy(this.config.getHttpProxyHost(), this.config.getHttpProxyPort(), this.config.getHttpProxyUsername(), this.config.getHttpProxyPassword());
|
||||
okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder();
|
||||
clientBuilder.proxy(this.getRequestHttpProxy().getProxy());
|
||||
clientBuilder.authenticator(new Authenticator() {
|
||||
clientBuilder.proxyAuthenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
String credential = Credentials.basic(WxChannelServiceOkHttpImpl.this.httpProxy.getProxyUsername(), WxChannelServiceOkHttpImpl.this.httpProxy.getProxyPassword());
|
||||
return response.request().newBuilder().header("Authorization", credential).build();
|
||||
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
|
||||
}
|
||||
});
|
||||
this.httpClient = clientBuilder.build();
|
||||
|
||||
@@ -86,12 +86,12 @@ public class WxCpServiceOkHttpImpl extends BaseWxCpServiceImpl<OkHttpClient, OkH
|
||||
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
|
||||
clientBuilder.proxy(getRequestHttpProxy().getProxy());
|
||||
//设置授权
|
||||
clientBuilder.authenticator(new Authenticator() {
|
||||
clientBuilder.proxyAuthenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
|
||||
return response.request().newBuilder()
|
||||
.header("Authorization", credential)
|
||||
.header("Proxy-Authorization", credential)
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -202,6 +202,12 @@ public class WxCpChatModel implements Serializable {
|
||||
@SerializedName("sphfeed")
|
||||
private SphFeed sphFeed;
|
||||
|
||||
/**
|
||||
* 音视频通话消息
|
||||
*/
|
||||
@SerializedName("voiptext")
|
||||
private VoipText voipText;
|
||||
|
||||
/**
|
||||
* From json wx cp chat model.
|
||||
*
|
||||
@@ -1333,4 +1339,40 @@ public class WxCpChatModel implements Serializable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 音视频通话消息
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public static class VoipText implements Serializable {
|
||||
private static final long serialVersionUID = -5028321625140879571L;
|
||||
|
||||
@SerializedName("callduration")
|
||||
private Integer callDuration;
|
||||
|
||||
@SerializedName("invitetype")
|
||||
private Integer inviteType;
|
||||
|
||||
/**
|
||||
* From json voip text.
|
||||
*
|
||||
* @param json the json
|
||||
* @return the voip text
|
||||
*/
|
||||
public static VoipText fromJson(String json) {
|
||||
return WxCpGsonBuilder.create().fromJson(json, VoipText.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* To json string.
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String toJson() {
|
||||
return WxCpGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -108,12 +108,12 @@ public class WxCpTpServiceOkHttpImpl extends BaseWxCpTpServiceImpl<OkHttpClient,
|
||||
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
|
||||
clientBuilder.proxy(getRequestHttpProxy().getProxy());
|
||||
//设置授权
|
||||
clientBuilder.authenticator(new Authenticator() {
|
||||
clientBuilder.proxyAuthenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
|
||||
return response.request().newBuilder()
|
||||
.header("Authorization", credential)
|
||||
.header("Proxy-Authorization", credential)
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalDetailResult;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpApprovalInfo;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpOaApplyEventRequest;
|
||||
import me.chanjar.weixin.cp.bean.oa.WxCpOaApprovalTemplateResult;
|
||||
import me.chanjar.weixin.cp.bean.oa.applydata.ApplyDataContent;
|
||||
import me.chanjar.weixin.cp.bean.oa.applydata.ContentValue;
|
||||
|
||||
@@ -86,14 +87,14 @@ public class WxCpApprovalWorkflowDemo {
|
||||
|
||||
System.out.println("审批单号: " + detail.getSpNo());
|
||||
System.out.println("审批名称: " + detail.getSpName());
|
||||
System.out.println("审批状态: " + detail.getSpStatus().getCode());
|
||||
System.out.println("申请人: " + detail.getApplyer().getUserId());
|
||||
System.out.println("审批状态: " + detail.getSpStatus());
|
||||
System.out.println("申请人: " + detail.getApplier().getUserId());
|
||||
System.out.println("申请时间: " + detail.getApplyTime());
|
||||
|
||||
// 打印审批记录
|
||||
if (detail.getSpRecord() != null) {
|
||||
detail.getSpRecord().forEach(record -> {
|
||||
System.out.println("审批节点状态: " + record.getSpStatus());
|
||||
if (detail.getSpRecords() != null) {
|
||||
Arrays.stream(detail.getSpRecords()).forEach(record -> {
|
||||
System.out.println("审批节点状态: " + record.getStatus());
|
||||
System.out.println("审批人: " + record.getDetails());
|
||||
});
|
||||
}
|
||||
@@ -112,7 +113,7 @@ public class WxCpApprovalWorkflowDemo {
|
||||
WxCpApprovalInfo approvalInfo = wxCpService.getOaService()
|
||||
.getApprovalInfo(startTime, endTime, "0", 100, null);
|
||||
|
||||
System.out.println("获取到的审批单数量: " + approvalInfo.getCount());
|
||||
System.out.println("获取到的审批单数量: " + (approvalInfo.getSpNoList() != null ? approvalInfo.getSpNoList().size() : 0));
|
||||
|
||||
// 遍历审批单号
|
||||
if (approvalInfo.getSpNoList() != null) {
|
||||
@@ -130,7 +131,7 @@ public class WxCpApprovalWorkflowDemo {
|
||||
public void templateManagement() throws Exception {
|
||||
// 获取模板详情
|
||||
String templateId = "3Tka1eD6v6JfzhDMqPd3aMkFdxqtJMc2ZRioUBGCNS";
|
||||
var templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
|
||||
WxCpOaApprovalTemplateResult templateResult = wxCpService.getOaService().getTemplateDetail(templateId);
|
||||
|
||||
System.out.println("模板名称: " + templateResult.getTemplateNames());
|
||||
System.out.println("模板内容: " + templateResult.getTemplateContent());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
|
||||
@@ -87,4 +88,18 @@ public interface WxMaUserService {
|
||||
* @return .
|
||||
*/
|
||||
boolean checkUserInfo(String sessionKey, String rawData, String signature);
|
||||
|
||||
/**
|
||||
* 多端登录验证接口.
|
||||
* <p>
|
||||
* 通过 code 换取用户登录态信息,用于多端登录场景(如手表端)。
|
||||
* </p>
|
||||
* 文档地址:<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/miniapp/openapi/code2Verifyinfo.html">多端登录</a>
|
||||
*
|
||||
* @param code 登录时获取的 code
|
||||
* @param checkcode 手表授权页面返回的 checkcode
|
||||
* @return 登录验证结果,包含 session_key、openid、unionid 和 is_limit 字段
|
||||
* @throws WxErrorException 调用微信接口失败时抛出
|
||||
*/
|
||||
WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException;
|
||||
}
|
||||
|
||||
@@ -912,6 +912,10 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
String rndStr = UUID.randomUUID().toString().replace("-", "").substring(0, 30);
|
||||
String aesKey = this.getWxMaConfig().getApiSignatureAesKey();
|
||||
String aesKeySn = this.getWxMaConfig().getApiSignatureAesKeySn();
|
||||
String rsaKeySn = this.getWxMaConfig().getApiSignatureRsaPrivateKeySn();
|
||||
if (rsaKeySn == null || rsaKeySn.isEmpty()) {
|
||||
throw new SecurityException("ApiSignatureRsaPrivateKeySn不能为空,请检查配置");
|
||||
}
|
||||
|
||||
jsonObject.addProperty("_n", rndStr);
|
||||
jsonObject.addProperty("_appid", appId);
|
||||
@@ -956,7 +960,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
String requestJson = reqData.toString();
|
||||
|
||||
// 计算签名 RSA
|
||||
String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + requestJson;
|
||||
String payload = urlPath + "\n" + appId + "\n" + timestamp + "\n" + rsaKeySn + "\n" + requestJson;
|
||||
byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8);
|
||||
RSAPrivateKey priKey;
|
||||
try {
|
||||
@@ -985,6 +989,7 @@ public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestH
|
||||
header.put("Wechatmp-Signature", signatureString);
|
||||
header.put("Wechatmp-Appid", appId);
|
||||
header.put("Wechatmp-TimeStamp", String.valueOf(timestamp));
|
||||
header.put("Wechatmp-Serial", rsaKeySn);
|
||||
log.debug("发送请求uri:{}, headers:{}, postData:{}", url, header, requestJson);
|
||||
WxMaApiResponse response =
|
||||
this.execute(ApiSignaturePostRequestExecutor.create(this), url, header, requestJson);
|
||||
|
||||
@@ -32,12 +32,12 @@ public class WxMaServiceOkHttpImpl extends BaseWxMaServiceImpl<OkHttpClient, OkH
|
||||
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
|
||||
clientBuilder.proxy(getRequestHttpProxy().getProxy());
|
||||
//设置授权
|
||||
clientBuilder.authenticator(new Authenticator() {
|
||||
clientBuilder.proxyAuthenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
|
||||
return response.request().newBuilder()
|
||||
.header("Authorization", credential)
|
||||
.header("Proxy-Authorization", credential)
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaUserService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaCode2VerifyInfoResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
|
||||
@@ -18,6 +19,7 @@ import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.CODE_2_VERIFY_INFO_URL;
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.GET_PHONE_NUMBER_URL;
|
||||
import static cn.binarywang.wx.miniapp.constant.WxMaApiUrlConstants.User.SET_USER_STORAGE;
|
||||
|
||||
@@ -86,4 +88,13 @@ public class WxMaUserServiceImpl implements WxMaUserService {
|
||||
return generatedSignature.equals(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaCode2VerifyInfoResult getCode2VerifyInfo(String code, String checkcode) throws WxErrorException {
|
||||
JsonObject param = new JsonObject();
|
||||
param.addProperty("code", code);
|
||||
param.addProperty("checkcode", checkcode);
|
||||
String responseContent = this.service.post(CODE_2_VERIFY_INFO_URL, param.toString());
|
||||
return WxMaCode2VerifyInfoResult.fromJson(responseContent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 多端登录验证接口的响应
|
||||
* 文档地址:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/miniapp/openapi/code2Verifyinfo.html
|
||||
*
|
||||
* 微信返回报文:{"errcode": 0, "errmsg": "ok", "session_key":"xxx", "openid":"xxx", "unionid":"xxx", "is_limit": false}
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class WxMaCode2VerifyInfoResult implements Serializable {
|
||||
private static final long serialVersionUID = -2468325025088437364L;
|
||||
|
||||
@SerializedName("session_key")
|
||||
private String sessionKey;
|
||||
|
||||
@SerializedName("openid")
|
||||
private String openid;
|
||||
|
||||
@SerializedName("unionid")
|
||||
private String unionid;
|
||||
|
||||
/**
|
||||
* 是否为受限用户
|
||||
*/
|
||||
@SerializedName("is_limit")
|
||||
private Boolean isLimit;
|
||||
|
||||
public static WxMaCode2VerifyInfoResult fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaCode2VerifyInfoResult.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -364,6 +364,8 @@ public class WxMaApiUrlConstants {
|
||||
String SET_USER_STORAGE =
|
||||
"https://api.weixin.qq.com/wxa/set_user_storage?appid=%s&signature=%s&openid=%s&sig_method=%s";
|
||||
String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
|
||||
/** 多端登录验证接口 */
|
||||
String CODE_2_VERIFY_INFO_URL = "https://api.weixin.qq.com/wxa/sec/checkcode2verifyinfo";
|
||||
}
|
||||
|
||||
public interface Ocr {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:二维码链接(NATIVE支付 会返回)
|
||||
* 变量名:h5_url
|
||||
* 变量名:code_url
|
||||
* 是否必填:是
|
||||
* 类型:string[1,512]
|
||||
* 描述:
|
||||
@@ -81,6 +81,19 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
|
||||
private String packageValue;
|
||||
private String signType;
|
||||
private String paySign;
|
||||
/**
|
||||
* <pre>
|
||||
* 字段名:预支付交易会话标识
|
||||
* 变量名:prepay_id
|
||||
* 是否必填:否(用户可选存储)
|
||||
* 类型:string[1,64]
|
||||
* 描述:
|
||||
* 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
|
||||
* 此字段用于支持用户存储prepay_id,以便复用和重新生成支付签名
|
||||
* 示例值:wx201410272009395522657a690389285100
|
||||
* </pre>
|
||||
*/
|
||||
private String prepayId;
|
||||
|
||||
private String getSignStr() {
|
||||
return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue);
|
||||
@@ -113,6 +126,7 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
|
||||
JsapiResult jsapiResult = new JsapiResult();
|
||||
jsapiResult.setAppId(appId).setTimeStamp(timestamp)
|
||||
.setPackageValue("prepay_id=" + this.prepayId).setNonceStr(nonceStr)
|
||||
.setPrepayId(this.prepayId)
|
||||
//签名类型,默认为RSA,仅支持RSA。
|
||||
.setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey));
|
||||
return (T) jsapiResult;
|
||||
@@ -132,4 +146,79 @@ public class WxPayUnifiedOrderV3Result implements Serializable {
|
||||
throw new WxRuntimeException("不支持的支付类型");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 根据已有的prepay_id生成JSAPI支付所需的参数对象(解耦版本)
|
||||
* 应用场景:
|
||||
* 1. 用户已经通过createPartnerOrderV3或unifiedPartnerOrderV3获取了prepay_id
|
||||
* 2. 用户希望存储prepay_id用于后续复用
|
||||
* 3. 支付失败后,使用存储的prepay_id重新生成支付签名信息
|
||||
*
|
||||
* 使用示例:
|
||||
* // 步骤1:创建订单并获取prepay_id
|
||||
* WxPayUnifiedOrderV3Result result = wxPayService.unifiedPartnerOrderV3(TradeTypeEnum.JSAPI, request);
|
||||
* String prepayId = result.getPrepayId();
|
||||
* // 存储prepayId到数据库...
|
||||
*
|
||||
* // 步骤2:需要支付时,使用存储的prepay_id生成支付信息
|
||||
* WxPayUnifiedOrderV3Result.JsapiResult payInfo = WxPayUnifiedOrderV3Result.getJsapiPayInfo(
|
||||
* prepayId, appId, wxPayService.getConfig().getPrivateKey()
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
* @param prepayId 预支付交易会话标识
|
||||
* @param appId 应用ID
|
||||
* @param privateKey 商户私钥,用于签名
|
||||
* @return JSAPI支付所需的参数对象
|
||||
*/
|
||||
public static JsapiResult getJsapiPayInfo(String prepayId, String appId, PrivateKey privateKey) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String nonceStr = SignUtils.genRandomStr();
|
||||
JsapiResult jsapiResult = new JsapiResult();
|
||||
jsapiResult.setAppId(appId).setTimeStamp(timestamp)
|
||||
.setPackageValue("prepay_id=" + prepayId).setNonceStr(nonceStr)
|
||||
.setPrepayId(prepayId)
|
||||
//签名类型,默认为RSA,仅支持RSA。
|
||||
.setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey));
|
||||
return jsapiResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 根据已有的prepay_id生成APP支付所需的参数对象(解耦版本)
|
||||
* 应用场景:
|
||||
* 1. 用户已经通过createPartnerOrderV3或unifiedPartnerOrderV3获取了prepay_id
|
||||
* 2. 用户希望存储prepay_id用于后续复用
|
||||
* 3. 支付失败后,使用存储的prepay_id重新生成支付签名信息
|
||||
*
|
||||
* 使用示例:
|
||||
* // 步骤1:创建订单并获取prepay_id
|
||||
* WxPayUnifiedOrderV3Result result = wxPayService.unifiedPartnerOrderV3(TradeTypeEnum.APP, request);
|
||||
* String prepayId = result.getPrepayId();
|
||||
* // 存储prepayId到数据库...
|
||||
*
|
||||
* // 步骤2:需要支付时,使用存储的prepay_id生成支付信息
|
||||
* WxPayUnifiedOrderV3Result.AppResult payInfo = WxPayUnifiedOrderV3Result.getAppPayInfo(
|
||||
* prepayId, appId, mchId, wxPayService.getConfig().getPrivateKey()
|
||||
* );
|
||||
* </pre>
|
||||
*
|
||||
* @param prepayId 预支付交易会话标识
|
||||
* @param appId 应用ID
|
||||
* @param mchId 商户号
|
||||
* @param privateKey 商户私钥,用于签名
|
||||
* @return APP支付所需的参数对象
|
||||
*/
|
||||
public static AppResult getAppPayInfo(String prepayId, String appId, String mchId, PrivateKey privateKey) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String nonceStr = SignUtils.genRandomStr();
|
||||
AppResult appResult = new AppResult();
|
||||
appResult.setAppid(appId).setPrepayId(prepayId).setPartnerId(mchId)
|
||||
.setNoncestr(nonceStr).setTimestamp(timestamp)
|
||||
//暂填写固定值Sign=WXPay
|
||||
.setPackageValue("Sign=WXPay")
|
||||
.setSign(SignUtils.sign(appResult.getSignStr(), privateKey));
|
||||
return appResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ public class PublicCertificateVerifier implements Verifier{
|
||||
|
||||
@Override
|
||||
public boolean verify(String serialNumber, byte[] message, String signature) {
|
||||
// 如果序列号不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证
|
||||
if (!serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
|
||||
// 如果序列号不为空且不包含"PUB_KEY_ID"且有证书验证器,先尝试证书验证
|
||||
if (serialNumber != null && !serialNumber.contains("PUB_KEY_ID") && this.certificateVerifier != null) {
|
||||
try {
|
||||
if (this.certificateVerifier.verify(serialNumber, message, signature)) {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.github.binarywang.wxpay.bean.result;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.v3.util.SignUtils;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* WxPayUnifiedOrderV3Result 测试类
|
||||
* 主要测试prepayId字段和静态工厂方法的解耦功能
|
||||
* </pre>
|
||||
*
|
||||
* @author copilot
|
||||
*/
|
||||
public class WxPayUnifiedOrderV3ResultTest {
|
||||
|
||||
/**
|
||||
* 生成测试用的RSA密钥对
|
||||
*/
|
||||
private KeyPair generateKeyPair() throws Exception {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(2048);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试JsapiResult中的prepayId字段是否正确设置
|
||||
*/
|
||||
@Test
|
||||
public void testJsapiResultWithPrepayId() throws Exception {
|
||||
// 准备测试数据
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 创建WxPayUnifiedOrderV3Result对象
|
||||
WxPayUnifiedOrderV3Result result = new WxPayUnifiedOrderV3Result();
|
||||
result.setPrepayId(testPrepayId);
|
||||
|
||||
// 调用getPayInfo生成JsapiResult
|
||||
WxPayUnifiedOrderV3Result.JsapiResult jsapiResult =
|
||||
result.getPayInfo(TradeTypeEnum.JSAPI, testAppId, null, privateKey);
|
||||
|
||||
// 验证prepayId字段是否正确设置
|
||||
Assert.assertNotNull(jsapiResult.getPrepayId(), "prepayId不应为null");
|
||||
Assert.assertEquals(jsapiResult.getPrepayId(), testPrepayId, "prepayId应该与设置的值相同");
|
||||
|
||||
// 验证其他字段
|
||||
Assert.assertEquals(jsapiResult.getAppId(), testAppId);
|
||||
Assert.assertNotNull(jsapiResult.getTimeStamp());
|
||||
Assert.assertNotNull(jsapiResult.getNonceStr());
|
||||
Assert.assertEquals(jsapiResult.getPackageValue(), "prepay_id=" + testPrepayId);
|
||||
Assert.assertEquals(jsapiResult.getSignType(), "RSA");
|
||||
Assert.assertNotNull(jsapiResult.getPaySign());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试使用静态工厂方法生成JsapiResult(解耦场景)
|
||||
*/
|
||||
@Test
|
||||
public void testGetJsapiPayInfoStaticMethod() throws Exception {
|
||||
// 准备测试数据
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 使用静态工厂方法生成JsapiResult
|
||||
WxPayUnifiedOrderV3Result.JsapiResult jsapiResult =
|
||||
WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
|
||||
|
||||
// 验证prepayId字段
|
||||
Assert.assertNotNull(jsapiResult.getPrepayId(), "prepayId不应为null");
|
||||
Assert.assertEquals(jsapiResult.getPrepayId(), testPrepayId, "prepayId应该与输入的值相同");
|
||||
|
||||
// 验证其他字段
|
||||
Assert.assertEquals(jsapiResult.getAppId(), testAppId);
|
||||
Assert.assertNotNull(jsapiResult.getTimeStamp());
|
||||
Assert.assertNotNull(jsapiResult.getNonceStr());
|
||||
Assert.assertEquals(jsapiResult.getPackageValue(), "prepay_id=" + testPrepayId);
|
||||
Assert.assertEquals(jsapiResult.getSignType(), "RSA");
|
||||
Assert.assertNotNull(jsapiResult.getPaySign());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试使用静态工厂方法生成AppResult(解耦场景)
|
||||
*/
|
||||
@Test
|
||||
public void testGetAppPayInfoStaticMethod() throws Exception {
|
||||
// 准备测试数据
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
String testMchId = "1900000109";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 使用静态工厂方法生成AppResult
|
||||
WxPayUnifiedOrderV3Result.AppResult appResult =
|
||||
WxPayUnifiedOrderV3Result.getAppPayInfo(testPrepayId, testAppId, testMchId, privateKey);
|
||||
|
||||
// 验证prepayId字段
|
||||
Assert.assertNotNull(appResult.getPrepayId(), "prepayId不应为null");
|
||||
Assert.assertEquals(appResult.getPrepayId(), testPrepayId, "prepayId应该与输入的值相同");
|
||||
|
||||
// 验证其他字段
|
||||
Assert.assertEquals(appResult.getAppid(), testAppId);
|
||||
Assert.assertEquals(appResult.getPartnerId(), testMchId);
|
||||
Assert.assertNotNull(appResult.getTimestamp());
|
||||
Assert.assertNotNull(appResult.getNoncestr());
|
||||
Assert.assertEquals(appResult.getPackageValue(), "Sign=WXPay");
|
||||
Assert.assertNotNull(appResult.getSign());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试解耦场景:先获取prepayId,后续再生成支付信息
|
||||
*/
|
||||
@Test
|
||||
public void testDecoupledScenario() throws Exception {
|
||||
// 模拟场景:先创建订单获取prepayId
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 步骤1:模拟从创建订单接口获取prepayId
|
||||
WxPayUnifiedOrderV3Result orderResult = new WxPayUnifiedOrderV3Result();
|
||||
orderResult.setPrepayId(testPrepayId);
|
||||
|
||||
// 获取prepayId用于存储
|
||||
String storedPrepayId = orderResult.getPrepayId();
|
||||
Assert.assertEquals(storedPrepayId, testPrepayId);
|
||||
|
||||
// 步骤2:后续支付失败时,使用存储的prepayId重新生成支付信息
|
||||
WxPayUnifiedOrderV3Result.JsapiResult newPayInfo =
|
||||
WxPayUnifiedOrderV3Result.getJsapiPayInfo(storedPrepayId, testAppId, privateKey);
|
||||
|
||||
// 验证重新生成的支付信息
|
||||
Assert.assertEquals(newPayInfo.getPrepayId(), storedPrepayId);
|
||||
Assert.assertEquals(newPayInfo.getPackageValue(), "prepay_id=" + storedPrepayId);
|
||||
Assert.assertNotNull(newPayInfo.getPaySign());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试多次生成支付信息,签名应该不同(因为timestamp和nonceStr每次都不同)
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleGenerationsHaveDifferentSignatures() throws Exception {
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 生成第一次支付信息
|
||||
WxPayUnifiedOrderV3Result.JsapiResult result1 =
|
||||
WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
|
||||
|
||||
// 等待一秒确保timestamp不同
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 生成第二次支付信息
|
||||
WxPayUnifiedOrderV3Result.JsapiResult result2 =
|
||||
WxPayUnifiedOrderV3Result.getJsapiPayInfo(testPrepayId, testAppId, privateKey);
|
||||
|
||||
// prepayId应该相同
|
||||
Assert.assertEquals(result1.getPrepayId(), result2.getPrepayId());
|
||||
|
||||
// 但是timestamp、nonceStr和签名应该不同
|
||||
Assert.assertNotEquals(result1.getTimeStamp(), result2.getTimeStamp(), "timestamp应该不同");
|
||||
Assert.assertNotEquals(result1.getNonceStr(), result2.getNonceStr(), "nonceStr应该不同");
|
||||
Assert.assertNotEquals(result1.getPaySign(), result2.getPaySign(), "签名应该不同");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试AppResult中的prepayId字段
|
||||
*/
|
||||
@Test
|
||||
public void testAppResultWithPrepayId() throws Exception {
|
||||
String testPrepayId = "wx201410272009395522657a690389285100";
|
||||
String testAppId = "wx8888888888888888";
|
||||
String testMchId = "1900000109";
|
||||
KeyPair keyPair = generateKeyPair();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
WxPayUnifiedOrderV3Result result = new WxPayUnifiedOrderV3Result();
|
||||
result.setPrepayId(testPrepayId);
|
||||
|
||||
// 调用getPayInfo生成AppResult
|
||||
WxPayUnifiedOrderV3Result.AppResult appResult =
|
||||
result.getPayInfo(TradeTypeEnum.APP, testAppId, testMchId, privateKey);
|
||||
|
||||
// 验证prepayId字段
|
||||
Assert.assertNotNull(appResult.getPrepayId(), "prepayId不应为null");
|
||||
Assert.assertEquals(appResult.getPrepayId(), testPrepayId, "prepayId应该与设置的值相同");
|
||||
}
|
||||
}
|
||||
@@ -80,11 +80,11 @@ public class WxQidianServiceOkHttpImpl extends BaseWxQidianServiceImpl<OkHttpCli
|
||||
clientBuilder.proxy(getRequestHttpProxy().getProxy());
|
||||
|
||||
// 设置授权
|
||||
clientBuilder.authenticator(new Authenticator() {
|
||||
clientBuilder.proxyAuthenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
|
||||
return response.request().newBuilder().header("Authorization", credential).build();
|
||||
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
|
||||
}
|
||||
});
|
||||
httpClient = clientBuilder.build();
|
||||
|
||||
Reference in New Issue
Block a user