14 Commits

Author SHA1 Message Date
felord.cn
bc9496e1ad docs: 修改1.0.5相关的注释 2021-02-02 10:40:44 +08:00
felord.cn
145f182641 fix: JCE不兼容
- 修复JCE不兼容的问题
2021-02-02 10:23:57 +08:00
felord.cn
5404c91402 fix: JCE不兼容
- 修复JCE不兼容的问题
2021-02-02 10:23:57 +08:00
felord.cn
57a7ba71fb fix: 修复关单接口异常
- 修复关单参数异常
2021-02-02 10:23:57 +08:00
felord.cn
e595855554 fix: 修复关单接口异常
- 修复关单参数异常
2021-02-02 10:23:57 +08:00
felord.cn
1f21f14c17 refactor: 修改支付中的场景类型
- 为了更加清晰的指明支付场景的类型,对H5Info中的type进行了优化,由字符串改为枚举类型

BREAKING CHANGE: 涉及到合单支付和直连支付的H5支付场景信息需要改入参的类型
2021-02-02 10:23:57 +08:00
felord.cn
0d93361f6b fix: 修复中文乱码
- 修复企业付款必传字段desc中文乱码的问题

Issues #4
Closes #4
2021-02-02 10:23:57 +08:00
felord.cn
49bf3d3d50 feat: 增加V2退款
- 由于V3没有退款 所以暂时用V2退款代替
2021-02-02 10:23:57 +08:00
felord.cn
dc9933e1e8 refactor: 降低支付相关的Spring Bean初始化优先级
- 降低配置初始化的优先级,以适应Apollo等配置中心
2021-02-02 10:23:57 +08:00
felord.cn
0bab0b987d feat: 基于V2实现现金红包以及企业付款到零钱功能
- 实现现金红包接口
- 实现企业付款到零钱功能
- 增加V2需要的依赖
2021-02-02 10:23:57 +08:00
felord.cn
82b7293e32 Update README.md 2021-01-18 16:40:36 +08:00
felord.cn
ab63beb7d9 docs: readme
readme
2021-01-18 16:39:13 +08:00
felord.cn
f96c03884d docs: changelog
changelog
2021-01-18 14:51:19 +08:00
felord.cn
23eee4094e docs: changelog
changelog
2021-01-18 12:48:18 +08:00
36 changed files with 999 additions and 152 deletions

View File

@@ -11,7 +11,7 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
</dependency>
```
@@ -45,4 +45,4 @@
## QQ交流群
为了交流解惑新建QQ群可通过扫码进入。
![QQ交流群](./docs/img/qqun.png)
![QQ交流群](./docs/img/qqun.png)

View File

@@ -26,6 +26,7 @@
- 实现微信支付V3 代金券
- 实现微信支付V3 微信支付分
- 实现微信支付V3 先享卡
- 实现微信支付V3 商家券
## Maven 中央仓库坐标
> 推荐使用最新版本
@@ -33,7 +34,7 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
</dependency>
```
## 采用技术

View File

@@ -1,11 +1,22 @@
## 1.0.3.RELEASE
## 1.0.5.RELEASE
- 微信支付
- feat: 支持微信支付商家券
- feat: 代金券功能增加发放消费卡接口`WechatMarketingBusiFavorApi`
- refactor: 一些代码优化
- refactor: 现在app支付、小程序支付返回所有客户端拉起支付的参数不再需要用户再进行签名操作了
- build: SDK开发环境 Spring Boot 版本升级到2.4.2
- fix: 支付分RiskFund下枚举无法使用的问题(#2)
- feat:增加V2 退款接口
- feat:增加V2 企业付款到零钱接口
- feat:增加V2 红包接口
- refactor:优化了一些底层功能
- refactor:整了支付配置Spring注入的顺序
- refactor:JCE由SPI提供不再使用JDK内置
- fix: 关单接口调用异常[(#4)](https://github.com/NotFound403/payment-spring-boot/issues/4)
## 1.0.4.RELEASE
- 微信支付
- feat: 增加微信支付商家券相关接口`WechatMarketingBusiFavorApi`,商家券请阅读相关产品文档。
- feat: 代金券功能增加发放消费卡接口。
- refactor: 现在app支付、小程序支付返回所有客户端拉起支付的参数不再需要用户再进行签名操作了。
- refactor: 其它一些代码优化。
- build: SDK开发环境 Spring Boot 版本升级到2.4.2。
- fix: 支付分`RiskFund`下枚举无法使用的问题[(#2)](https://github.com/NotFound403/payment-spring-boot/issues/2)。
## 1.0.3.RELEASE
- 微信支付
- feat: 完善合单支付账单

View File

@@ -4,10 +4,10 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
</dependency>
```
> 基于 **Spring Boot 2.4.1**
> 基于 **Spring Boot 2.x**
## Spring Boot 版本适配
@@ -50,7 +50,7 @@ wechat:
<tentantID>:
# 应用appId 必填
app-id: xxxxxxxx
# api 密钥 可不
# v2 api 密钥 1.0.5版本以后如果用到V2的接口时必
app-secret: xxxxxxxxxxx
# api v3 密钥 必填
app-v3-secret: xxxxxxxx

View File

@@ -5,11 +5,11 @@
<parent>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
</parent>
<artifactId>payment-spring-boot-autoconfigure</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
@@ -56,6 +56,16 @@
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -34,7 +34,7 @@ import java.util.Map;
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@Conditional(WechatPayConfiguredCondition.class)
@EnableConfigurationProperties(WechatPayProperties.class)
public class WechatPayConfiguration {

View File

@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
@@ -37,6 +38,7 @@ import java.util.stream.Collectors;
* @author felord.cn
* @since 1.0.3.RELEASE
*/
@Order
public class WechatPayConfiguredCondition extends SpringBootCondition {
/**
@@ -57,7 +59,8 @@ public class WechatPayConfiguredCondition extends SpringBootCondition {
}
private Map<String, WechatPayProperties.V3> getV3(Environment environment) {
return Binder.get(environment).bind("wechat.pay.v3", STRING_WECHAT_V3_MAP)
return Binder.get(environment)
.bind("wechat.pay.v3", STRING_WECHAT_V3_MAP)
.orElse(Collections.emptyMap());
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.v2.model.GroupRedpackModel;
import cn.felord.payment.wechat.v2.model.RedpackInfoModel;
import cn.felord.payment.wechat.v2.model.RedpackModel;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.HttpMethod;
/**
* 现金红包
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
public class WechatPayRedpackApi {
private final WechatV2Client wechatV2Client;
/**
* Instantiates a new Wechat pay redpack api.
*
* @param wechatV2Client the wechat v 2 client
*/
public WechatPayRedpackApi(WechatV2Client wechatV2Client) {
this.wechatV2Client = wechatV2Client;
}
/**
* 发放随机红包
*
* @param redpackModel the redpack model
* @return the json node
*/
public JsonNode sendRedpack(RedpackModel redpackModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
redpackModel.setWxappid(v3.getAppId());
redpackModel.setMchId(v3.getMchId());
return wechatV2Client.wechatPayRequest(redpackModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack");
}
/**
* 发放裂变红包
*
* @param groupRedpackModel the group redpack model
* @return the json node
*/
public JsonNode sendRedpack(GroupRedpackModel groupRedpackModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
groupRedpackModel.setWxappid(v3.getAppId());
groupRedpackModel.setMchId(v3.getMchId());
return wechatV2Client.wechatPayRequest(groupRedpackModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack");
}
/**
* 查询红包信息
*
* @param redpackInfoModel the redpack info model
* @return the json node
*/
public JsonNode redpackInfo(RedpackInfoModel redpackInfoModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
redpackInfoModel.setAppid(v3.getAppId());
redpackInfoModel.setMchId(v3.getMchId());
return wechatV2Client.wechatPayRequest(redpackInfoModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo");
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.v2.model.RefundModel;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.HttpMethod;
/**
* 退款相关API.
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
public class WechatPayRefundApi {
private final WechatV2Client wechatV2Client;
/**
* Instantiates a new Wechat pay refund api.
*
* @param wechatV2Client the wechat v 2 client
*/
public WechatPayRefundApi(WechatV2Client wechatV2Client) {
this.wechatV2Client = wechatV2Client;
}
/**
* 退款
*
* @param refundModel the refund model
* @return json node
*/
public JsonNode transfer(RefundModel refundModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
refundModel.setAppid(v3.getAppId());
refundModel.setMchId(v3.getMchId());
return wechatV2Client.wechatPayRequest(refundModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/secapi/pay/refund");
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.v2.model.TransferInfoModel;
import cn.felord.payment.wechat.v2.model.TransferModel;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.HttpMethod;
/**
* 企业付款
* <p>
* TODO 暂时没有企业付款到银行卡
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
public class WechatPayTransfersApi {
private final WechatV2Client wechatV2Client;
/**
* Instantiates a new Wechat pay transfers api.
*
* @param wechatV2Client the wechat v 2 client
*/
public WechatPayTransfersApi(WechatV2Client wechatV2Client) {
this.wechatV2Client = wechatV2Client;
}
/**
* 企业付款到零钱
*
* @param transferModel the transfer model
* @return json node
*/
public JsonNode transfer(TransferModel transferModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
transferModel.setMchAppid(v3.getAppId());
transferModel.setMchid(v3.getMchId());
return wechatV2Client.wechatPayRequest(transferModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers");
}
/**
* 查询企业付款
*
* @param transferModel the transfer model
* @return the json node
*/
public JsonNode transferInfo(TransferInfoModel transferModel) {
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
transferModel.setAppid(v3.getAppId());
transferModel.setMchId(v3.getMchId());
return wechatV2Client.wechatPayRequest(transferModel,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo");
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.v2.model.BaseModel;
import cn.felord.payment.wechat.v3.WechatMetaBean;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.HttpMethod;
/**
* 微信支付V2 客户端
* <p>
* V3接口不完善的临时性解决方案
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
public class WechatV2Client {
private final WechatMetaBean wechatMetaBean;
public WechatV2Client(WechatMetaBean wechatMetaBean) {
this.wechatMetaBean = wechatMetaBean;
}
public <M extends BaseModel> JsonNode wechatPayRequest(M model, HttpMethod method, String url) {
WechatPayProperties.V3 v3 = wechatMetaBean.getV3();
return model
.appSecret(v3.getAppSecret())
.request(v3.getMchId(), method, url);
}
public WechatMetaBean getWechatMetaBean() {
return wechatMetaBean;
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
import cn.felord.payment.PayException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.Assert;
import org.springframework.util.IdGenerator;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
/**
* The type Base model.
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@Getter
public abstract class BaseModel {
private static final XmlMapper XML_MAPPER = new XmlMapper();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static {
// 忽略null
XML_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL)
// 属性使用 驼峰首字母小写
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
OBJECT_MAPPER.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
}
private static final IdGenerator ID_GENERATOR = new AlternativeJdkIdGenerator();
private final String nonceStr = ID_GENERATOR.generateId()
.toString()
.replaceAll("-", "");
private String sign;
@JsonIgnore
private String appSecret;
public BaseModel appSecret(String appSecret) {
this.appSecret = appSecret;
return this;
}
/**
* Xml string.
*
* @return the string
*/
@SneakyThrows
private String xml() {
String link = link(this);
this.sign = this.md5(link);
return XML_MAPPER.writer()
.withRootName("xml")
.writeValueAsString(this);
}
/**
* md5摘要.
*
* @param src the src
* @return the string
*/
private String md5(String src) {
Digest digest = new MD5Digest();
byte[] bytes = src.getBytes(StandardCharsets.UTF_8);
digest.update(bytes, 0, bytes.length);
byte[] md5Bytes = new byte[digest.getDigestSize()];
digest.doFinal(md5Bytes, 0);
return Hex.toHexString(md5Bytes).toUpperCase();
}
/**
* 按照格式拼接参数以生成签名
*
* @param <T> the type parameter
* @param t the t
* @return the map
*/
@SneakyThrows
private <T> String link(T t) {
Assert.hasText(appSecret, "wechat pay appSecret is required");
return OBJECT_MAPPER
.writer()
.writeValueAsString(t)
.replaceAll("\":\"", "=")
.replaceAll("\",\"", "&")
.replaceAll("\\{\"", "")
.replaceAll("\"}", "")
.concat("&key=").concat(this.appSecret);
}
@SneakyThrows
public JsonNode request(String mchId, HttpMethod method, String url) {
String xml = this.xml();
RequestEntity<String> body = RequestEntity.method(method, UriComponentsBuilder.fromHttpUrl(url)
.build()
.toUri())
.contentType(MediaType.valueOf("application/x-www-form-urlencoded;charset=UTF-8"))
.body(xml);
ResponseEntity<String> responseEntity = this.getRestTemplateClientAuthentication(mchId)
.exchange(url, method, body, String.class);
if (!responseEntity.getStatusCode().is2xxSuccessful()) {
throw new PayException("wechat pay v2 error ");
}
String result = responseEntity.getBody();
return XML_MAPPER.readTree(result);
}
private RestTemplate getRestTemplateClientAuthentication(String mchId)
throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
ClassPathResource resource = new ClassPathResource("wechat/apiclient_cert.p12");
char[] pem = mchId.toCharArray();
KeyStore store = KeyStore.getInstance("PKCS12");
store.load(resource.getInputStream(), pem);
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContextBuilder.create()
.loadKeyMaterial(store, pem)
.build();
// Allow TLSv1 protocol only
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"},
null, hostnameVerifier);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpclient);
return new RestTemplate(clientHttpRequestFactory);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class GroupRedpackModel extends BaseModel {
private String wxappid;
private String mchId;
private String mchBillno;
private String sendName;
private String reOpenid;
private String totalAmount;
private String totalNum;
private String amtType = "ALL_RAND";
private String wishing;
private String clientIp;
private String actName;
private String remark;
private String sceneId;
private String riskInfo;
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RedpackInfoModel extends BaseModel {
private String appid;
private String mchId;
private String mchBillno;
private String billType = "MCHT";
}

View File

@@ -0,0 +1,28 @@
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RedpackModel extends BaseModel {
private String wxappid;
private String mchId;
private String mchBillno;
private String sendName;
private String reOpenid;
private String totalAmount;
private String totalNum;
private String wishing;
private String clientIp;
private String actName;
private String remark;
private String sceneId;
private String riskInfo;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.4.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RefundModel extends BaseModel {
private String appid;
private String mchId;
private String signType="MD5";
private String transactionId;
private String outTradeNo;
private String outRefundNo;
private Integer totalFee;
private Integer refundFee;
private String refundFeeType="CNY";
private String refundDesc;
private String refundAccount;
private String notifyUrl;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
/**
* @author felord.cn
* @since 1.0.4.RELEASE
*/
public class RefundQueryModel {
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2019-2021 felord.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
* Website:
* https://felord.cn
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class TransferInfoModel extends BaseModel {
private String appid;
private String mchId;
private String partnerTradeNo;
}

View File

@@ -0,0 +1,23 @@
package cn.felord.payment.wechat.v2.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author felord.cn
* @since 1.0.5.RELEASE
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class TransferModel extends BaseModel{
private String mchAppid;
private String mchid;
private String deviceInfo;
private String partnerTradeNo;
private String openid;
private String checkName;
private String reUserName;
private String amount;
private String desc;
private String spbillCreateIp;
}

View File

@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.http.*;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.Base64Utils;
@@ -85,6 +86,12 @@ public class SignatureProvider {
* 微信平台证书容器 key = 序列号 value = 证书对象
*/
private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();
/**
* 加密算法提供方 - BouncyCastle
*/
private static final String BC_PROVIDER = "BC";
/**
* The Rest operations.
*/
@@ -100,6 +107,8 @@ public class SignatureProvider {
* @param wechatMetaContainer the wechat meta container
*/
public SignatureProvider(WechatMetaContainer wechatMetaContainer) {
Provider bouncyCastleProvider = new BouncyCastleProvider();
Security.addProvider(bouncyCastleProvider);
this.wechatMetaContainer = wechatMetaContainer;
wechatMetaContainer.getTenantIds().forEach(this::refreshCertificate);
}
@@ -144,7 +153,7 @@ public class SignatureProvider {
*/
@SneakyThrows
public String doRequestSign(PrivateKey privateKey, String... orderedComponents) {
Signature signer = Signature.getInstance("SHA256withRSA");
Signature signer = Signature.getInstance("SHA256withRSA",BC_PROVIDER);
signer.initSign(privateKey);
final String signatureStr = createSign(orderedComponents);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
@@ -243,7 +252,7 @@ public class SignatureProvider {
*/
public String decryptResponseBody(String tenantId, String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", BC_PROVIDER);
String apiV3Key = wechatMetaContainer.getWechatMeta(tenantId).getV3().getAppV3Secret();
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
@@ -258,8 +267,7 @@ public class SignatureProvider {
throw new PayException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException |
InvalidKeyException | InvalidAlgorithmParameterException e) {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
throw new PayException(e);
}
}

View File

@@ -18,6 +18,10 @@
*/
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.v2.WechatPayRedpackApi;
import cn.felord.payment.wechat.v2.WechatPayTransfersApi;
import cn.felord.payment.wechat.v2.WechatV2Client;
/**
* 微信支付工具.
*
@@ -118,4 +122,34 @@ public class WechatApiProvider {
return new WechatPayCallback(wechatPayClient.signatureProvider(), tenantId);
}
/**
* 现金红包基于V2
*
* @param tenantId the tenant id
* @return wechat pay redpack api
* @since 1.0.5.RELEASE
*/
public WechatPayRedpackApi redpackApi(String tenantId) {
WechatMetaBean wechatMeta = wechatPayClient.signatureProvider()
.wechatMetaContainer()
.getWechatMeta(tenantId);
WechatV2Client wechatV2Client = new WechatV2Client(wechatMeta);
return new WechatPayRedpackApi(wechatV2Client);
}
/**
* 企业付款到零钱目前不包括到银行卡基于V2
*
* @param tenantId the tenant id
* @return wechat pay redpack api
* @since 1.0.5.RELEASE
*/
public WechatPayTransfersApi transfersApi(String tenantId) {
WechatMetaBean wechatMeta = wechatPayClient.signatureProvider()
.wechatMetaContainer()
.getWechatMeta(tenantId);
WechatV2Client wechatV2Client = new WechatV2Client(wechatMeta);
return new WechatPayTransfersApi(wechatV2Client);
}
}

View File

@@ -34,6 +34,8 @@ import java.net.URI;
import java.security.PrivateKey;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
@@ -253,11 +255,10 @@ public class WechatDirectPayApi extends AbstractApi {
private RequestEntity<?> closeByOutTradeNoFunction(WechatPayV3Type type, String outTradeNo) {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("mchid", v3.getMchId());
Map<String, String> queryParams = new HashMap<>(1);
queryParams.put("mchid", v3.getMchId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(outTradeNo)
.toUri();

View File

@@ -262,7 +262,7 @@ public class WechatPayClient {
responseConsumer.accept(responseEntity);
}
} else {
throw new PayException("wechat pay signature failed, Request-ID " + requestId);
throw new PayException("wechat pay signature verify failed, Request-ID " + requestId);
}
}

View File

@@ -29,11 +29,11 @@ import lombok.Data;
@Data
public class Amount {
/**
* The Total.
* 金额,单位【分】。
*/
private int total;
/**
* The Currency.
* 货币单位,固定为 CNY 。
*/
private String currency = "CNY";
}

View File

@@ -29,54 +29,58 @@ import lombok.Data;
@Data
public class CallbackParams {
/**
* The Id.
* 通知Id
*/
private String id;
/**
* The Create time.
* 通知创建时间
*/
private String createTime;
/**
* The Event type.
* 通知类型
* @see cn.felord.payment.wechat.v3.WechatPayCallback
*/
private String eventType;
/**
* The Resource type.
* 通知数据类型
*/
private String resourceType;
/**
* The Summary.
* 回调摘要
*/
private String summary;
/**
* The Resource.
* 通知数据
*/
private Resource resource;
/**
* The type Resource.
* 通知数据
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class Resource {
/**
* The Algorithm.
* 对开启结果数据进行加密的加密算法目前只支持AEAD_AES_256_GCM。
*/
private String algorithm;
/**
* The Ciphertext.
* Base64编码后的开启/停用结果数据密文。
*/
private String ciphertext;
/**
* The Associated data.
* 附加数据。
*/
private String associatedData;
/**
* The Nonce.
* 加密使用的随机串。
*/
private String nonce;
/**
* The Original type.
* 原始回调类型。
*/
private String originalType;
}

View File

@@ -32,22 +32,22 @@ import java.util.List;
public class ConsumeInformation {
/**
* The Consume mchid.
* 核销商户号
*/
private String consumeMchid;
/**
* The Consume time.
* 核销时间 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
*/
private String consumeTime;
/**
* The Goods detail.
* 商户下单接口传的单品信息
*/
private List<GoodsDetail> goodsDetail;
/**
* The Transaction id.
* 核销订单号
*/
private String transactionId;

View File

@@ -18,6 +18,7 @@
*/
package cn.felord.payment.wechat.v3.model;
import cn.felord.payment.wechat.enumeration.CouponStatus;
import lombok.Data;
/**
@@ -30,67 +31,68 @@ import lombok.Data;
public class CouponConsumeData {
/**
* The Available begin time.
* 可用开始时间 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
*/
private String availableBeginTime;
/**
* The Available end time.
* 可用结束时间 YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE
*/
private String availableEndTime;
/**
* The Consume information.
* 实扣代金券信息
*/
private ConsumeInformation consumeInformation;
/**
* The Coupon id.
* 代金券id
*/
private String couponId;
/**
* The Coupon name.
* 代金券名称
*/
private String couponName;
/**
* The Coupon type.
* 代金券类型
*/
private CouponType couponType;
/**
* The Create time.
* 领券时间
*/
private String createTime;
/**
* The Description.
* 代金券描述
*/
private String description;
/**
* The Discount to.
* 减至优惠特定信息
*/
private DiscountTo discountTo;
/**
* The No cash.
* 是否无资金流
*/
private Boolean noCash;
/**
* The Normal coupon information.
* 普通满减券信息
*/
private NormalCouponInformation normalCouponInformation;
/**
* The Singleitem.
* 是否单品优惠
*/
private Boolean singleitem;
/**
* The Singleitem discount off.
* 单品优惠特定信息
*/
private SingleitemDiscountOff singleitemDiscountOff;
/**
* The Status.
* 代金券状态
* @see CouponStatus
*/
private String status;
private CouponStatus status;
/**
* The Stock creator mchid.
* 创建批次的商户号
*/
private String stockCreatorMchid;
/**
* The Stock id.
* 批次号
*/
private String stockId;

View File

@@ -41,9 +41,9 @@ public class Goods {
/**
* 商品数量
*/
private int quantity;
private Integer quantity;
/**
* 商品单价
*/
private int unitPrice;
private Integer unitPrice;
}

View File

@@ -30,19 +30,19 @@ import lombok.Data;
public class GoodsDetail {
/**
* The Discount amount.
* 优惠金额,单位【分】
*/
private Long discountAmount;
/**
* The Goods id.
* 单品券创建时录入的单品编码。
*/
private String goodsId;
/**
* The Price.
* 单品单价
*/
private Long price;
/**
* The Quantity.
* 单品数量
*/
private Long quantity;

View File

@@ -31,7 +31,7 @@ public class H5Info {
/**
* 场景类型
*/
private String type;
private H5SceneType type;
/**
* 应用名称
*/
@@ -48,4 +48,26 @@ public class H5Info {
* Android 平台 PackageName
*/
private String packageName;
/**
* H5 场景类型
*
* @author felord.cn
* @since 1.0.5.RELEASE
*/
public enum H5SceneType {
/**
* IOS
*/
iOS,
/**
* Android
*/
Android,
/**
* Wap
*/
Wap
}
}

View File

@@ -30,11 +30,11 @@ import lombok.Data;
public class NormalCouponInformation {
/**
* The Coupon amount.
* 面额,单位:分。
*/
private Long couponAmount;
/**
* The Transaction minimum.
* 使用券金额门槛,单位:分。
*/
private Long transactionMinimum;

View File

@@ -24,7 +24,7 @@ import lombok.Data;
import java.util.List;
/**
* The type Promotion detail.
* 优惠功能
*
* @author felord.cn
* @since 1.0.0.RELEASE
@@ -33,52 +33,60 @@ import java.util.List;
public class PromotionDetail {
/**
* The Amount.
* 优惠券面额,单位【分】
*/
private Long amount;
/**
* The Coupon id.
* 券ID
*/
private String couponId;
/**
* The Currency.
* 优惠币种,内商户号仅支持人民币 CNY
*/
private String currency;
/**
* The Goods detail.
* 单品列表信息
*/
private List<GoodsDetail> goodsDetail;
/**
* The Merchant contribute.
* 商户出资,单位为分
*/
private Long merchantContribute;
/**
* The Name.
* 优惠名称
*/
private String name;
/**
* The Other contribute.
* 其他出资,单位为分
*/
private Long otherContribute;
/**
* The Scope.
* 优惠范围
* <ul>
* <li>GLOBAL全场代金券</li>
* <li>SINGLE单品优惠</li>
* </ul>
*/
private String scope;
/**
* The Stock id.
* 活动ID
*/
private String stockId;
/**
* The Type.
* 优惠类型
* <ul>
* <li>CASH充值</li>
* <li>NOCASH预充值</li>
* </ul>
*/
private String type;
/**
* The Wechatpay contribute.
* 微信出资,单位为分
*/
private Long wechatpayContribute;
/**
* The type Goods detail.
* 单品列表信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
@@ -87,23 +95,23 @@ public class PromotionDetail {
public static class GoodsDetail {
/**
* The Goods id.
* 商品编码
*/
private String goodsId;
/**
* The Quantity.
* 商品数量
*/
private Long quantity;
/**
* The Unit price.
* 商品单价
*/
private Long unitPrice;
/**
* The Discount amount.
* 商品优惠金额,单位【分】
*/
private Long discountAmount;
/**
* The Goods remark.
* 商品备注
*/
private String goodsRemark;

View File

@@ -29,6 +29,9 @@ import lombok.Data;
@Data
public class SingleitemDiscountOff {
/**
* 单品最高优惠价格,单位:分。
*/
private Long singlePriceMax;
}

View File

@@ -25,6 +25,8 @@ import lombok.Data;
import java.util.List;
/**
* 支付成功通知解密
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@@ -32,67 +34,71 @@ import java.util.List;
public class TransactionConsumeData {
/**
* The Amount.
* 订单金额
*/
private Amount amount;
/**
* The Appid.
* 应用ID
*/
private String appid;
/**
* The Attach.
* 附加数据在查询API和支付通知中原样返回可作为自定义参数使用
*/
private String attach;
/**
* The Bank type.
* 银行类型,采用字符串类型的银行标识。银行标识请参考 <a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/terms_definition/chapter1_1_3.shtml#part-6">《银行类型对照表》</a>
*/
private String bankType;
/**
* The Mchid.
* 商户号
*/
private String mchid;
/**
* The Out trade no.
* 商户订单号
*/
private String outTradeNo;
/**
* The Payer.
* 支付者信息
*/
private Payer payer;
/**
* The Promotion detail.
* 优惠功能,享受优惠时返回该字段。
*/
private List<PromotionDetail> promotionDetail;
/**
* The Scene info.
* 支付场景信息描述
*/
private SceneInfo sceneInfo;
/**
* The Success time.
* 支付完成时间 YYYY-MM-DDTHH:mm:ss+TIMEZONE
*/
private String successTime;
/**
* 在 1.0.0.RELEASE 直接返回了枚举字符串1.0.2.RELEASE 中变更为枚举
*
* @since 1.0.0.RELEASE
*/
private TradeState tradeState;
/**
* The Trade state desc.
* 交易状态描述
*/
private String tradeStateDesc;
/**
* 交易类型
* <p>
* 在 1.0.0.RELEASE 直接返回了枚举字符串1.0.2.RELEASE 中变更为枚举
*
* @since 1.0.0.RELEASE
*/
private TradeType tradeType;
/**
* The Transaction id.
* 微信支付订单号
*/
private String transactionId;
/**
* The type Payer.
* 支付者信息
*
* @author felord.cn
* @since 1.0.0.RELEASE
@@ -100,13 +106,13 @@ public class TransactionConsumeData {
@Data
public static class Payer {
/**
* The Openid.
* 用户在直连商户appid下的唯一标识。
*/
private String openid;
}
/**
* The type Scene info.
* 支付场景信息描述
*
* @author felord.cn
* @since 1.0.0.RELEASE
@@ -114,13 +120,13 @@ public class TransactionConsumeData {
@Data
public static class SceneInfo {
/**
* The Device id.
* 商户端设备号门店号或收银设备ID
*/
private String deviceId;
}
/**
* The type Amount.
* 订单金额
*
* @author felord.cn
* @since 1.0.0.RELEASE
@@ -128,19 +134,19 @@ public class TransactionConsumeData {
@Data
public static class Amount {
/**
* The Total.
* 订单总金额,单位为分。
*/
private Integer total;
/**
* The Payer total.
* 用户支付金额,单位为分。
*/
private Integer payerTotal;
/**
* The Currency.
* CNY人民币境内商户号仅支持人民币。
*/
private String currency;
/**
* The Payer currency.
* 用户支付币种
*/
private String payerCurrency;
}

View File

@@ -5,11 +5,11 @@
<parent>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
</parent>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
@@ -35,6 +35,14 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
</dependencies>
</project>

116
pom.xml
View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.4.RELEASE</version>
<version>1.0.5.RELEASE</version>
<packaging>pom</packaging>
<modelVersion>4.0.0</modelVersion>
@@ -30,7 +30,7 @@
</developers>
<scm>
<tag>payment-spring-boot-1.0.4.RELEASE</tag>
<tag>payment-spring-boot-1.0.5.RELEASE</tag>
<url>https://github.com/NotFound403/payment-spring-boot</url>
<connection>scm:git:https://github.com/NotFound403/payment-spring-boot.git</connection>
<developerConnection>scm:git:https://github.com/NotFound403/payment-spring-boot.git</developerConnection>
@@ -54,6 +54,8 @@
<lombok.verison>1.18.12</lombok.verison>
<jackson.version>2.9.10</jackson.version>
<bcprov.version>1.66</bcprov.version>
<jackson.version>2.11.4</jackson.version>
<httpclient.version>4.5.13</httpclient.version>
</properties>
<distributionManagement>
@@ -116,66 +118,96 @@
<artifactId>payment-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
<charset>UTF-8</charset>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>sonatype-nexus-staging</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
<charset>UTF-8</charset>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>sonatype-nexus-staging</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
</configuration>
</plugin>
</plugins>
</build>