-
Notifications
You must be signed in to change notification settings - Fork 255
增加微信支付通知处理器 #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
增加微信支付通知处理器 #105
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
17 changes: 17 additions & 0 deletions
17
src/main/java/com/wechat/pay/contrib/apache/httpclient/exception/ParseException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.wechat.pay.contrib.apache.httpclient.exception; | ||
|
|
||
| /** | ||
| * @author lianup | ||
| */ | ||
| public class ParseException extends WechatPayException { | ||
|
|
||
| private static final long serialVersionUID = 4300538230471368120L; | ||
|
|
||
| public ParseException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| public ParseException(String message, Throwable cause) { | ||
| super(message, cause); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/wechat/pay/contrib/apache/httpclient/exception/ValidationException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.wechat.pay.contrib.apache.httpclient.exception; | ||
|
|
||
| /** | ||
| * @author lianup | ||
| */ | ||
| public class ValidationException extends WechatPayException { | ||
|
|
||
|
|
||
| private static final long serialVersionUID = -3473204321736989263L; | ||
|
|
||
|
|
||
| public ValidationException(String message) { | ||
| super(message); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
src/main/java/com/wechat/pay/contrib/apache/httpclient/notification/Notification.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package com.wechat.pay.contrib.apache.httpclient.notification; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
|
||
| /** | ||
| * 请求体解析结果 | ||
| * | ||
| * @author lianup | ||
| */ | ||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public class Notification { | ||
|
|
||
| @JsonProperty("id") | ||
| private String id; | ||
| @JsonProperty("create_time") | ||
| private String createTime; | ||
| @JsonProperty("event_type") | ||
| private String eventType; | ||
| @JsonProperty("resource_type") | ||
| private String resourceType; | ||
| @JsonProperty("summary") | ||
| private String summary; | ||
| @JsonProperty("resource") | ||
| private Resource resource; | ||
| private String decryptData; | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "Notification{" + | ||
| "id='" + id + '\'' + | ||
| ", createTime='" + createTime + '\'' + | ||
| ", eventType='" + eventType + '\'' + | ||
| ", resourceType='" + resourceType + '\'' + | ||
| ", decryptData='" + decryptData + '\'' + | ||
| ", summary='" + summary + '\'' + | ||
| ", resource=" + resource + | ||
| '}'; | ||
| } | ||
|
|
||
| public String getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public String getCreateTime() { | ||
| return createTime; | ||
| } | ||
|
|
||
| public String getEventType() { | ||
| return eventType; | ||
| } | ||
|
|
||
| public String getDecryptData() { | ||
| return decryptData; | ||
| } | ||
|
|
||
| public String getSummary() { | ||
| return summary; | ||
| } | ||
|
|
||
| public String getResourceType() { | ||
| return resourceType; | ||
| } | ||
|
|
||
| public Resource getResource() { | ||
| return resource; | ||
| } | ||
|
|
||
| public void setDecryptData(String decryptData) { | ||
| this.decryptData = decryptData; | ||
| } | ||
|
|
||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||
| public class Resource { | ||
|
|
||
| @JsonProperty("algorithm") | ||
| private String algorithm; | ||
| @JsonProperty("ciphertext") | ||
| private String ciphertext; | ||
| @JsonProperty("associated_data") | ||
| private String associatedData; | ||
| @JsonProperty("nonce") | ||
| private String nonce; | ||
| @JsonProperty("original_type") | ||
| private String originalType; | ||
|
|
||
| public String getAlgorithm() { | ||
| return algorithm; | ||
| } | ||
|
|
||
| public String getCiphertext() { | ||
| return ciphertext; | ||
| } | ||
|
|
||
| public String getAssociatedData() { | ||
| return associatedData; | ||
| } | ||
|
|
||
| public String getNonce() { | ||
| return nonce; | ||
| } | ||
|
|
||
| public String getOriginalType() { | ||
| return originalType; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 使用 ObjectMapper 来代替手写?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 此处用到toString的地方为拼接错误信息,在错误中抛出,商户测试和使用时也可以直接用toString打印。 |
||
| return "Resource{" + | ||
| "algorithm='" + algorithm + '\'' + | ||
| ", ciphertext='" + ciphertext + '\'' + | ||
| ", associatedData='" + associatedData + '\'' + | ||
| ", nonce='" + nonce + '\'' + | ||
| ", originalType='" + originalType + '\'' + | ||
| '}'; | ||
| } | ||
| } | ||
|
|
||
| } | ||
172 changes: 172 additions & 0 deletions
172
src/main/java/com/wechat/pay/contrib/apache/httpclient/notification/NotificationHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| package com.wechat.pay.contrib.apache.httpclient.notification; | ||
|
|
||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.fasterxml.jackson.databind.ObjectReader; | ||
| import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; | ||
| import com.wechat.pay.contrib.apache.httpclient.exception.ParseException; | ||
| import com.wechat.pay.contrib.apache.httpclient.exception.ValidationException; | ||
| import com.wechat.pay.contrib.apache.httpclient.notification.Notification.Resource; | ||
| import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; | ||
| import java.io.IOException; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.GeneralSecurityException; | ||
|
|
||
| /** | ||
| * @author lianup | ||
| */ | ||
| public class NotificationHandler { | ||
|
|
||
| private final Verifier verifier; | ||
| private final byte[] apiV3Key; | ||
| private static final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
|
||
| public NotificationHandler(Verifier verifier, byte[] apiV3Key) { | ||
| if (verifier == null) { | ||
| throw new IllegalArgumentException("verifier为空"); | ||
| } | ||
| if (apiV3Key == null || apiV3Key.length == 0) { | ||
| throw new IllegalArgumentException("apiV3Key为空"); | ||
| } | ||
| this.verifier = verifier; | ||
| this.apiV3Key = apiV3Key; | ||
| } | ||
|
|
||
| /** | ||
| * 解析微信支付通知请求结果 | ||
| * | ||
| * @param request 微信支付通知请求 | ||
| * @return 微信支付通知报文解密结果 | ||
| * @throws ValidationException 1.输入参数不合法 2.参数被篡改导致验签失败 3.请求和验证的平台证书不一致导致验签失败 | ||
| * @throws ParseException 1.解析请求体为Json失败 2.请求体无对应参数 3.AES解密失败 | ||
| */ | ||
| public Notification parse(Request request) | ||
Eric-Lee-Handyman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| throws ValidationException, ParseException { | ||
| // 验签 | ||
| validate(request); | ||
| // 解析请求体 | ||
| return parseBody(request.getBody()); | ||
| } | ||
|
|
||
| private void validate(Request request) throws ValidationException { | ||
Eric-Lee-Handyman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (request == null) { | ||
| throw new ValidationException("request为空"); | ||
| } | ||
| String serialNumber = request.getSerialNumber(); | ||
| byte[] message = request.getMessage(); | ||
| String signature = request.getSignature(); | ||
| if (serialNumber == null || serialNumber.isEmpty()) { | ||
| throw new ValidationException("serialNumber为空"); | ||
| } | ||
| if (message == null || message.length == 0) { | ||
| throw new ValidationException("message为空"); | ||
| } | ||
| if (signature == null || signature.isEmpty()) { | ||
| throw new ValidationException("signature为空"); | ||
| } | ||
| if (!verifier.verify(serialNumber, message, signature)) { | ||
| String errorMessage = String | ||
| .format("验签失败:serial=[%s] message=[%s] sign=[%s]", serialNumber, new String(message), signature); | ||
| throw new ValidationException(errorMessage); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 解析请求体 | ||
| * | ||
| * @param body 请求体 | ||
| * @return 解析结果 | ||
| * @throws ParseException 解析body失败 | ||
| */ | ||
| private Notification parseBody(String body) throws ParseException { | ||
| ObjectReader objectReader = objectMapper.reader(); | ||
| Notification notification; | ||
| try { | ||
| notification = objectReader.readValue(body, Notification.class); | ||
| } catch (IOException ioException) { | ||
| throw new ParseException("解析body失败,body:" + body, ioException); | ||
| } | ||
| validateNotification(notification); | ||
| setDecryptData(notification); | ||
Eric-Lee-Handyman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return notification; | ||
| } | ||
|
|
||
| /** | ||
| * 校验解析后的通知结果 | ||
| * | ||
| * @param notification 通知结果 | ||
| * @throws ParseException 参数不合法 | ||
| */ | ||
| private void validateNotification(Notification notification) throws ParseException { | ||
| if (notification == null) { | ||
| throw new ParseException("body解析为空"); | ||
| } | ||
| String id = notification.getId(); | ||
| if (id == null || id.isEmpty()) { | ||
| throw new ParseException("body不合法,id为空。body:" + notification.toString()); | ||
| } | ||
| String createTime = notification.getCreateTime(); | ||
| if (createTime == null || createTime.isEmpty()) { | ||
| throw new ParseException("body不合法,createTime为空。body:" + notification.toString()); | ||
| } | ||
| String eventType = notification.getEventType(); | ||
| if (eventType == null || eventType.isEmpty()) { | ||
| throw new ParseException("body不合法,eventType为空。body:" + notification.toString()); | ||
| } | ||
| String summary = notification.getSummary(); | ||
| if (summary == null || summary.isEmpty()) { | ||
| throw new ParseException("body不合法,summary为空。body:" + notification.toString()); | ||
| } | ||
| String resourceType = notification.getResourceType(); | ||
| if (resourceType == null || resourceType.isEmpty()) { | ||
| throw new ParseException("body不合法,resourceType为空。body:" + notification.toString()); | ||
| } | ||
| Resource resource = notification.getResource(); | ||
| if (resource == null) { | ||
| throw new ParseException("body不合法,resource为空。notification:" + notification.toString()); | ||
| } | ||
| String algorithm = resource.getAlgorithm(); | ||
| if (algorithm == null || algorithm.isEmpty()) { | ||
| throw new ParseException("body不合法,algorithm为空。body:" + notification.toString()); | ||
| } | ||
| String originalType = resource.getOriginalType(); | ||
| if (originalType == null || originalType.isEmpty()) { | ||
| throw new ParseException("body不合法,original_type为空。body:" + notification.toString()); | ||
| } | ||
| String ciphertext = resource.getCiphertext(); | ||
| if (ciphertext == null || ciphertext.isEmpty()) { | ||
| throw new ParseException("body不合法,ciphertext为空。body:" + notification.toString()); | ||
| } | ||
| String nonce = resource.getNonce(); | ||
| if (nonce == null || nonce.isEmpty()) { | ||
| throw new ParseException("body不合法,nonce为空。body:" + notification.toString()); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 获取解密数据 | ||
| * | ||
| * @param notification 解析body得到的通知结果 | ||
| * @throws ParseException 解析body失败 | ||
| */ | ||
| private void setDecryptData(Notification notification) throws ParseException { | ||
|
|
||
| Resource resource = notification.getResource(); | ||
| String getAssociateddData = ""; | ||
| if (resource.getAssociatedData() != null) { | ||
| getAssociateddData = resource.getAssociatedData(); | ||
| } | ||
| byte[] associatedData = getAssociateddData.getBytes(StandardCharsets.UTF_8); | ||
| byte[] nonce = resource.getNonce().getBytes(StandardCharsets.UTF_8); | ||
| String ciphertext = resource.getCiphertext(); | ||
| AesUtil aesUtil = new AesUtil(apiV3Key); | ||
| String decryptData; | ||
| try { | ||
| decryptData = aesUtil.decryptToString(associatedData, nonce, ciphertext); | ||
| } catch (GeneralSecurityException e) { | ||
| throw new ParseException("AES解密失败,resource:" + resource.toString(), e); | ||
| } | ||
| notification.setDecryptData(decryptData); | ||
| } | ||
|
|
||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
使用 ObjectMapper 来代替手写?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
此处用到toString的地方为拼接错误信息,在错误中抛出,商户测试和使用时也可以直接用toString打印。