23 Commits

Author SHA1 Message Date
felord
554a93d41a Merge branch '1.0.11.SNAPSHOT' into release 2021-06-05 14:37:44 +08:00
felord
c1d2cfce13 docs: Update pathParams 2021-06-05 14:37:01 +08:00
felord.cn
ee8d7d0d92 Merge pull request #36 from NotFound403/1.0.11.SNAPSHOT
1.0.11.RELEASE
2021-06-05 14:04:36 +08:00
felord
60fe80e615 refactor: Update pathParams 2021-06-05 13:28:37 +08:00
felord
53a34e6a71 feat: (微信支付更新)基础支付《申请退款API》、《查询退款API》新增字段 from(退款出资账户及金额) 2021-06-01 16:00:02 +08:00
felord
398df66ec5 feat: 服务商V3分账 2021-06-01 15:37:21 +08:00
felord
e387e91e38 feat: 直连商户V3分账 2021-06-01 15:37:20 +08:00
felord
10a91c51be Merge branch 'release' into 1.0.11.SNAPSHOT 2021-05-28 17:29:40 +08:00
felord.cn
453844bd9a Merge pull request #33 from AmazingDM/release
fix :批量转账到零钱:微信明细单号查询明细单API,商家明细单号查询明细单API 参数错误
2021-05-28 17:27:53 +08:00
felord
3871e05ea7 feat: 完善批量转账到零钱API
- 转账明细电子回单受理API
- 查询转账明细电子回单受理结果API
- 查询账户实时余额API
- 查询账户日终余额API
- 商户银行来账查询API
2021-05-28 17:01:04 +08:00
felord
16440d01cf refactor: 签名逻辑修改,全部使用换行符 2021-05-28 17:00:52 +08:00
felord
91dc8e8f8a refactor: 现在下载交易账单和资金账单更加方便了
- 直接返回资源流,可以直接返回给前端,会自动下载成txt或者gzip文件
2021-05-27 18:37:16 +08:00
felord
8cd3f63d68 refactor: Style 2021-05-26 10:19:53 +08:00
felord
a46590d0a9 Merge remote-tracking branch 'origin/1.0.11.SNAPSHOT' into 1.0.11.SNAPSHOT 2021-05-25 12:38:42 +08:00
felord
0c230547a9 refactor: 交易账单和资金账单现在能够正常的下载文件了 2021-05-25 12:38:13 +08:00
felord.cn
591eec7f65 Merge remote-tracking branch 'origin/1.0.11.SNAPSHOT' into 1.0.11.SNAPSHOT
# Conflicts:
#	payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java
2021-05-16 23:45:27 +08:00
felord.cn
ed190cc875 refactor: 增加解密参数的判断 2021-05-16 23:44:08 +08:00
AmazingDM
82941dffcb fix :批量转账到零钱:微信明细单号查询明细单API,商家明细单号查询明细单API 参数错误 2021-05-14 18:27:49 +08:00
felord
9d01a0fa3a refactor: 分账标记默认改为 不分账 2021-05-13 16:20:47 +08:00
felord
78cbce016d refactor: 优化证书刷新
https://gitee.com/felord/payment-spring-boot/issues/I3NGSB

Closes I3NGSB
2021-05-10 10:56:43 +08:00
felord.cn
7470787441 feat: 日常维护
- 支付宝增加证书路径的选择方式
- 微信支付 《支付通知API》新增优惠功能(promotion_detail)字段
2021-05-09 22:13:10 +08:00
felord.cn
65cfbaf3b2 Merge pull request #29 from NotFound403/dependabot/maven/org.bouncycastle-bcprov-jdk15to18-1.67
build(deps): bump bcprov-jdk15to18 from 1.66 to 1.67
2021-05-07 11:54:52 +08:00
dependabot[bot]
3be70e7d61 build(deps): bump bcprov-jdk15to18 from 1.66 to 1.67
Bumps [bcprov-jdk15to18](https://github.com/bcgit/bc-java) from 1.66 to 1.67.
- [Release notes](https://github.com/bcgit/bc-java/releases)
- [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-30 20:22:34 +00:00
47 changed files with 2064 additions and 156 deletions

View File

@@ -11,7 +11,7 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
</dependency>
```

View File

@@ -35,7 +35,7 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
</dependency>
```
## 采用技术

View File

@@ -4,7 +4,7 @@
<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
</dependency>
```
> 基于 **Spring Boot 2.x**
@@ -92,7 +92,7 @@ wechat:
### 支付宝
在Spring Boot项目中的`application.yaml`中配置`ali.pay.v1`相关参数。
在Spring Boot项目中的`application.yaml`中配置`ali.pay.v1`相关参数。证书细节参见【日常踩坑】
```yaml
ali:
pay:

View File

@@ -5,11 +5,11 @@
<parent>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
</parent>
<artifactId>payment-spring-boot-autoconfigure</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>

View File

@@ -33,9 +33,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.function.Function;
/**
* @author felord.cn
@@ -56,34 +56,33 @@ public class AliPayConfiguration {
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
propertyMapper.from(v1::getServerUrl).to(certAlipayRequest::setServerUrl);
propertyMapper.from(v1::getAppId).to(certAlipayRequest::setAppId);
propertyMapper.from(v1::getAppPrivateKeyPath).as(this::appRSAPrivateKey).to(certAlipayRequest::setPrivateKey);
propertyMapper.from(v1::getAppPrivateKeyPath)
.as(this::loadFile)
.to(certAlipayRequest::setPrivateKey);
propertyMapper.from(v1::getFormat).to(certAlipayRequest::setFormat);
propertyMapper.from(v1::getCharset).to(certAlipayRequest::setCharset);
propertyMapper.from(v1::getSignType).to(certAlipayRequest::setSignType);
propertyMapper.from(v1::getAppCertPublicKeyPath).as(this::getContentFromClassPath).to(certAlipayRequest::setCertContent);
propertyMapper.from(v1::getAlipayPublicCertPath).as(this::getContentFromClassPath).to(certAlipayRequest::setAlipayPublicCertContent);
propertyMapper.from(v1::getAlipayRootCertPath).as(this::getContentFromClassPath).to(certAlipayRequest::setRootCertContent);
Function<String,String> certStrategyFunc = v1.isClasspathUsed()?this::loadFile:s -> s;
propertyMapper.from(v1::getAppCertPublicKeyPath)
.as(certStrategyFunc)
.to(certAlipayRequest::setCertContent);
propertyMapper.from(v1::getAlipayPublicCertPath)
.as(certStrategyFunc)
.to(certAlipayRequest::setAlipayPublicCertContent);
propertyMapper.from(v1::getAlipayRootCertPath)
.as(certStrategyFunc)
.to(certAlipayRequest::setRootCertContent);
return new DefaultAlipayClient(certAlipayRequest);
}
private String getContentFromClassPath(String classPath) {
ClassPathResource resource = new ClassPathResource(classPath);
try (InputStreamReader in = new InputStreamReader(resource.getInputStream())) {
return IOUtils.toString(in);
} catch (IOException e) {
log.error("ali pay root cert is invalid ,{}", e.getMessage());
throw new PayException("ali pay root cert path is invalid");
}
}
private String appRSAPrivateKey(String classPath) {
private String loadFile(String classPath) {
ClassPathResource resource = new ClassPathResource(classPath);
try (InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream())) {
try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
return bufferedReader.readLine();
}
return IOUtils.toString(inputStreamReader);
} catch (IOException e) {
log.error("ali pay app private key is required ,{}", e.getMessage());
throw new PayException("ali pay app private key is required");

View File

@@ -70,6 +70,10 @@ public class AliPayProperties {
* charset default utf-8
*/
private String charset = "utf-8";
/**
* use classpath or not ,default true
*/
private boolean classpathUsed = true;
/**
* alipay public cert path
*/

View File

@@ -0,0 +1,23 @@
package cn.felord.payment.wechat.enumeration;
/**
* 分账接收方类型
*
* @author n1
* @since 2021/5/31 17:47
*/
public enum ReceiverType {
/**
* 商户号
*/
MERCHANT_ID,
/**
* 个人openid由父商户APPID转换得到
*/
PERSONAL_OPENID,
/**
* 个人sub_openid由子商户APPID转换得到服务商模式
*/
PERSONAL_SUB_OPENID
}

View File

@@ -0,0 +1,22 @@
package cn.felord.payment.wechat.enumeration;
/**
* 批量转账到零钱 - 电子回单受理类型
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
public enum TransferAcceptType {
/**
* 批量转账明细电子回单
*/
BATCH_TRANSFER,
/**
* 企业付款至零钱电子回单
*/
TRANSFER_TO_POCKET,
/**
* 企业付款至银行卡电子回单
*/
TRANSFER_TO_BANK
}

View File

@@ -47,13 +47,13 @@ public enum WechatPayV3Type {
*
* @since 1.0.3.RELEASE
*/
TRADEBILL(HttpMethod.GET, "%s/v3/bill/tradebill"),
TRADE_BILL(HttpMethod.GET, "%s/v3/bill/tradebill"),
/**
* 申请资金账单API.
*
* @since 1.0.3.RELEASE
*/
FUNDFLOWBILL(HttpMethod.GET, "%s/v3/bill/fundflowbill"),
FUND_FLOW_BILL(HttpMethod.GET, "%s/v3/bill/fundflowbill"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -487,6 +487,37 @@ public enum WechatPayV3Type {
* @since 1.0.6.RELEASES
*/
BATCH_TRANSFER_DOWNLOAD_BILL(HttpMethod.GET, "%s/v3/transfer/bill-receipt/{out_batch_no}"),
/**
* 转账明细电子回单受理API.
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_ELECTRONIC(HttpMethod.POST, "%s/v3/transfer-detail/electronic-receipts"),
/**
* 查询转账明细电子回单受理结果API.
* 请求方式同{@link WechatPayV3Type#BATCH_TRANSFER_ELECTRONIC}不同
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_ELECTRONIC_DETAIL(HttpMethod.GET, "%s/v3/transfer-detail/electronic-receipts"),
/**
* 查询账户实时余额API
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_FUND_BALANCE(HttpMethod.GET, "%s/v3/merchant/fund/balance/{account_type}"),
/**
* 查询账户日终余额API
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_FUND_DAY_BALANCE(HttpMethod.GET, "%s/v3/merchant/fund/dayendbalance/{account_type}"),
/**
* 商户银行来账查询API
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_FUND_INCOME_RECORDS(HttpMethod.GET, "%s/v3/merchantfund/merchant/income-records"),
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -536,7 +567,62 @@ public enum WechatPayV3Type {
* @since 1.0.0.RELEASE
*/
TRANSACTION_OUT_TRADE_NO_PARTNER(HttpMethod.GET, "%s/v3/pay/partner/transactions/out-trade-no/{out_trade_no}"),
;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 请求分账API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_ORDERS(HttpMethod.POST, "%s/v3/profitsharing/orders"),
/**
* 查询分账结果API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_ORDERS_RESULT(HttpMethod.GET, "%s/v3/profitsharing/orders/{out_order_no}"),
/**
* 请求分账回退API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_RETURN_ORDERS(HttpMethod.POST, "%s/v3/profitsharing/return-orders"),
/**
* 查询分账回退结果API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_RETURN_ORDERS_RESULT(HttpMethod.GET, "%s/v3/profitsharing/return-orders"),
/**
* 解冻剩余资金API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_ORDERS_UNFREEZE(HttpMethod.POST, "%s/v3/profitsharing/orders/unfreeze"),
/**
* 查询剩余待分金额API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_AMOUNTS(HttpMethod.GET, "%s/v3/profitsharing/transactions/{transaction_id}/amounts"),
/**
* 服务商专用-查询最大分账比例API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_MCH_CONFIG(HttpMethod.GET, "%s/v3/profitsharing/merchant-configs/{sub_mchid}"),
/**
* 添加分账接收方API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_RECEIVERS_ADD(HttpMethod.POST, "%s/v3/profitsharing/receivers/add"),
/**
* 删除分账接收方API.
*
* @since 1.0.11.RELEASE
*/
PROFITSHARING_RECEIVERS_DELETE(HttpMethod.POST, "%s/v3/profitsharing/receivers/add");
/**
* The Pattern.
*

View File

@@ -36,6 +36,7 @@ import java.util.List;
* 微信支付分账
* <p>
*
* @author felord.cn
* @since 1.0.10.RELEASE
*/
@Slf4j
@@ -72,20 +73,20 @@ public class WechatAllocationApi {
*/
@SneakyThrows
public JsonNode profitSharing(ProfitSharingModel profitSharingModel) {
ProfitSharingSModel profitSharingSModel = new ProfitSharingSModel();
ProfitSharingSModel model = new ProfitSharingSModel();
List<Receiver> receivers = profitSharingModel.getReceivers();
profitSharingSModel.setReceivers(MAPPER.writeValueAsString(receivers));
model.setReceivers(MAPPER.writeValueAsString(receivers));
WechatPayProperties.V3 v3 = wechatV2Client.getWechatMetaBean().getV3();
profitSharingSModel.setAppid(v3.getAppId());
profitSharingSModel.setMchId(v3.getMchId());
model.setAppid(v3.getAppId());
model.setMchId(v3.getMchId());
profitSharingSModel.setTransactionId(profitSharingModel.getTransactionId());
profitSharingSModel.setOutOrderNo(profitSharingModel.getOutOrderNo());
model.setTransactionId(profitSharingModel.getTransactionId());
model.setOutOrderNo(profitSharingModel.getOutOrderNo());
profitSharingSModel.certPath(v3.getCertPath());
profitSharingSModel.signType(BaseModel.HMAC_SHA256);
return wechatV2Client.wechatPayRequest(profitSharingSModel,
model.certPath(v3.getCertPath());
model.signType(BaseModel.HMAC_SHA256);
return wechatV2Client.wechatPayRequest(model,
HttpMethod.POST,
"https://api.mch.weixin.qq.com/secapi/pay/profitsharing");
}

View File

@@ -27,8 +27,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
@@ -89,7 +91,7 @@ public abstract class AbstractApi {
*/
private void applyObjectMapper(ObjectMapper mapper) {
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
// empty string error
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
@@ -217,13 +219,15 @@ public abstract class AbstractApi {
throw new PayException("wechat app pay json failed");
}
}
/**
* 对账单内容下载,非流文件。
* 对账单CSV内容下载,非流文件。
*
* @param link the link
* @return 对账单内容 ,有可能为空字符 “”
* @see AbstractApi#billResource(String) AbstractApi#billResource(String)AbstractApi#billResource(String)AbstractApi#billResource(String)
*/
protected String billDownload(String link) {
protected String billCsvDownload(String link) {
return this.client().withType(WechatPayV3Type.FILE_DOWNLOAD, link)
.function((type, downloadUrl) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(downloadUrl)
@@ -234,26 +238,12 @@ public abstract class AbstractApi {
.download();
}
/**
* 对账单下载,流文件。
*
* @param link the link
* @return response entity
*/
protected ResponseEntity<Resource> billResource(String link) {
return this.client().withType(WechatPayV3Type.FILE_DOWNLOAD, link)
.function((type, downloadUrl) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(downloadUrl)
.build()
.toUri();
return Get(uri);
})
.resource();
}
/**
* 申请交易账单API
* <p>
* 返回值直接返回前端会下载tradebill-前缀加上日期的txt文件默认或者gzip文件。
* <p>
* 微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址。文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况。
* <p>
* 注意:
@@ -265,10 +255,13 @@ public abstract class AbstractApi {
* </ul>
*
* @param tradeBillParams tradeBillParams
* @return the response entity
* @since 1.0.3.RELEASE
*/
public final void downloadTradeBill(TradeBillParams tradeBillParams) {
this.client().withType(WechatPayV3Type.TRADEBILL, tradeBillParams)
public ResponseEntity<Resource> downloadTradeBill(TradeBillParams tradeBillParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.TRADE_BILL, tradeBillParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
LocalDate billDate = params.getBillDate();
@@ -291,8 +284,15 @@ public abstract class AbstractApi {
.build().toUri();
return Get(uri);
})
.consumer(response -> this.billDownload(Objects.requireNonNull(response.getBody()).get("download_url").asText()))
.consumer(wechatResponseEntity::convert)
.request();
String downloadUrl = Objects.requireNonNull(wechatResponseEntity.getBody())
.get("download_url")
.asText();
String ext = Objects.equals(TarType.GZIP, tradeBillParams.getTarType()) ? ".gzip" : ".txt";
String filename = "tradebill-" + tradeBillParams.getBillDate().toString() + ext;
return downloadBillResponse(downloadUrl, filename);
}
/**
@@ -307,10 +307,12 @@ public abstract class AbstractApi {
* </ul>
*
* @param fundFlowBillParams fundFlowBillParams
* @return the response entity
* @since 1.0.3.RELEASE
*/
public final void downloadFundFlowBill(FundFlowBillParams fundFlowBillParams) {
this.client().withType(WechatPayV3Type.FUNDFLOWBILL, fundFlowBillParams)
public ResponseEntity<Resource> downloadFundFlowBill(FundFlowBillParams fundFlowBillParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.FUND_FLOW_BILL, fundFlowBillParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
LocalDate billDate = params.getBillDate();
@@ -328,7 +330,46 @@ public abstract class AbstractApi {
.build().toUri();
return Get(uri);
})
.consumer(response -> this.billDownload(Objects.requireNonNull(response.getBody()).get("download_url").asText()))
.consumer(wechatResponseEntity::convert)
.request();
String downloadUrl = Objects.requireNonNull(wechatResponseEntity.getBody())
.get("download_url")
.asText();
String ext = Objects.equals(TarType.GZIP, fundFlowBillParams.getTarType()) ? ".gzip" : ".txt";
String filename = "fundflowbill-" + fundFlowBillParams.getBillDate().toString() + ext;
return this.downloadBillResponse(downloadUrl, filename);
}
/**
* 调用{@code /v3/billdownload/file}直接下载为文件.
*
* @param downloadUrl 格式为 {@code https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx}
* @param filename 文件名称包含扩展名
* @return the response entity
*/
public ResponseEntity<Resource> downloadBillResponse(String downloadUrl, String filename) {
ResponseEntity<Resource> responseEntity = this.billResource(downloadUrl);
HttpHeaders httpHeaders = new HttpHeaders();
// utf is not support
httpHeaders.setContentDisposition(ContentDisposition.attachment().filename(filename).build());
return ResponseEntity.ok().headers(httpHeaders).body(responseEntity.getBody());
}
/**
* 调用{@code /v3/billdownload/file}下载返回的原始文件流
*
* @param link the link
* @return response entity
*/
protected ResponseEntity<Resource> billResource(String link) {
return this.client().withType(WechatPayV3Type.FILE_DOWNLOAD, link)
.function((type, downloadUrl) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(downloadUrl)
.build()
.toUri();
return Get(uri);
})
.resource();
}
}

View File

@@ -30,9 +30,7 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.http.*;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.Base64Utils;
import org.springframework.util.IdGenerator;
import org.springframework.util.*;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
@@ -120,7 +118,6 @@ public class SignatureProvider {
/**
* 我方请求前用 SHA256withRSA 加签使用API证书.
*
* @param newLine the new line
* @param tenantId the properties key
* @param method the method
* @param canonicalUrl the canonical url
@@ -128,7 +125,7 @@ public class SignatureProvider {
* @return the string
*/
@SneakyThrows
public String requestSign(boolean newLine,String tenantId, String method, String canonicalUrl, String body) {
public String requestSign(String tenantId, String method, String canonicalUrl, String body) {
long timestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
String nonceStr = nonceStrGenerator.generateId()
@@ -136,7 +133,7 @@ public class SignatureProvider {
.replaceAll("-", "");
WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId);
PrivateKey privateKey = wechatMetaBean.getKeyPair().getPrivate();
String encode = this.doRequestSign(newLine,privateKey, method, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
String encode = this.doRequestSign(privateKey, method, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
// 序列号
String serialNo = wechatMetaBean.getSerialNumber();
// 生成token
@@ -150,17 +147,16 @@ public class SignatureProvider {
/**
* Do request sign.
*
* @param newLine the has suffix
* @param privateKey the private key
* @param orderedComponents the orderedComponents
* @return the string
* @since 1.0.4.RELEASE
*/
@SneakyThrows
public String doRequestSign(boolean newLine,PrivateKey privateKey, String... orderedComponents) {
public String doRequestSign(PrivateKey privateKey, String... orderedComponents) {
Signature signer = Signature.getInstance("SHA256withRSA", BC_PROVIDER);
signer.initSign(privateKey);
final String signatureStr = createSign(newLine,orderedComponents);
final String signatureStr = createSign(true, orderedComponents);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(signer.sign());
}
@@ -180,7 +176,7 @@ public class SignatureProvider {
}
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
final String signatureStr = createSign(true,params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody());
final String signatureStr = createSign(true, params.getWechatpayTimestamp(), params.getWechatpayNonce(), params.getBody());
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initVerify(certificate);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
@@ -208,7 +204,7 @@ public class SignatureProvider {
}
// 签名
HttpMethod httpMethod = WechatPayV3Type.CERT.method();
String authorization = requestSign(true,tenantId, httpMethod.name(), canonicalUrl, "");
String authorization = requestSign(tenantId, httpMethod.name(), canonicalUrl, "");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
@@ -224,8 +220,8 @@ public class SignatureProvider {
}
ArrayNode certificates = bodyObjectNode.withArray("data");
if (certificates.isArray() && certificates.size() > 0) {
CERTIFICATE_MAP.clear();
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X509",BC_PROVIDER);
CERTIFICATE_MAP.remove(tenantId);
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", BC_PROVIDER);
certificates.forEach(objectNode -> {
JsonNode encryptCertificate = objectNode.get("encrypt_certificate");
String associatedData = encryptCertificate.get("associated_data").asText();
@@ -256,6 +252,15 @@ public class SignatureProvider {
* @return the string
*/
public String decryptResponseBody(String tenantId, String associatedData, String nonce, String ciphertext) {
try {
Assert.hasText(associatedData, "associatedData is invalid");
Assert.hasText(nonce, "nonce is invalid");
Assert.hasText(ciphertext, "ciphertext is invalid");
} catch (Exception e) {
throw new PayException(e.getMessage());
}
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", BC_PROVIDER);
String apiV3Key = wechatMetaContainer.getWechatMeta(tenantId).getV3().getAppV3Secret();
@@ -285,14 +290,14 @@ public class SignatureProvider {
* @return encrypt message
* @since 1.0.6.RELEASE
*/
public String encryptRequestMessage(String message,Certificate certificate) {
public String encryptRequestMessage(String message, Certificate certificate) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", BC_PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] cipherData = cipher.doFinal(data);
return Base64Utils.encodeToString(cipherData);
return Base64Utils.encodeToString(cipherData);
} catch (Exception e) {
throw new PayException(e);
@@ -304,14 +309,14 @@ public class SignatureProvider {
*
* @return the x 509 wechat certificate info
*/
public X509WechatCertificateInfo getCertificate(){
public X509WechatCertificateInfo getCertificate() {
for (String serial : CERTIFICATE_MAP.keySet()) {
X509Certificate x509Cert = (X509Certificate) CERTIFICATE_MAP.get(serial);
try {
x509Cert.checkValidity();
X509WechatCertificateInfo x509WechatCertificateInfo = new X509WechatCertificateInfo();
x509WechatCertificateInfo.setWechatPaySerial(serial);
x509WechatCertificateInfo.setX509Certificate(x509Cert);
x509WechatCertificateInfo.setWechatPaySerial(serial);
x509WechatCertificateInfo.setX509Certificate(x509Cert);
return x509WechatCertificateInfo;
} catch (Exception e) {
log.warn("the wechat certificate is invalid , {}", e.getMessage());
@@ -348,9 +353,9 @@ public class SignatureProvider {
* @param components the components
* @return string string
*/
private static String createSign(boolean newLine,String... components) {
private static String createSign(boolean newLine, String... components) {
String suffix = newLine? "\n":"";
String suffix = newLine ? "\n" : "";
return Arrays.stream(components)
.collect(Collectors.joining("\n", "", suffix));
}

View File

@@ -123,7 +123,7 @@ public class WechatApiProvider {
}
/**
* 批量转账到零钱.
* 批量转账到零钱.
* <p>
* 批量转账到零钱提供商户同时向多个用户微信零钱转账的能力。商户可以使用批量转账到零钱用于费用报销、员工福利发放、合作伙伴货款或服务款项支付等场景,提高转账效率。
*
@@ -193,4 +193,23 @@ public class WechatApiProvider {
return new WechatAllocationApi(wechatV2Client);
}
/**
* 直连商户微信支付分账基于V3
*
* @param tenantId the tenant id
* @return the wechat profitsharing api
*/
public WechatProfitsharingApi profitsharingApi(String tenantId) {
return new WechatProfitsharingApi(wechatPayClient, tenantId);
}
/**
* 服务商微信支付分账基于V3
*
* @param tenantId the tenant id
* @return the wechat partner profitsharing api
*/
public WechatPartnerProfitsharingApi partnerProfitsharingApi(String tenantId) {
return new WechatPartnerProfitsharingApi(wechatPayClient, tenantId);
}
}

View File

@@ -17,11 +17,10 @@
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.enumeration.FundFlowAccountType;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.batchtransfer.CreateBatchTransferParams;
import cn.felord.payment.wechat.v3.model.batchtransfer.QueryBatchTransferDetailParams;
import cn.felord.payment.wechat.v3.model.batchtransfer.QueryBatchTransferParams;
import cn.felord.payment.wechat.v3.model.batchtransfer.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
@@ -38,6 +37,7 @@ import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -77,11 +77,11 @@ public class WechatBatchTransferApi extends AbstractApi {
List<CreateBatchTransferParams.TransferDetailListItem> transferDetailList = createBatchTransferParams.getTransferDetailList();
SignatureProvider signatureProvider = this.client().signatureProvider();
final X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
final X509Certificate x509Certificate = certificate.getX509Certificate();
List<CreateBatchTransferParams.TransferDetailListItem> encrypted = transferDetailList.stream()
.peek(transferDetailListItem -> {
String userName = transferDetailListItem.getUserName();
X509Certificate x509Certificate = certificate.getX509Certificate();
String encryptedUserName = signatureProvider.encryptRequestMessage(userName, x509Certificate);
transferDetailListItem.setUserName(encryptedUserName);
String userIdCard = transferDetailListItem.getUserIdCard();
@@ -140,13 +140,13 @@ public class WechatBatchTransferApi extends AbstractApi {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_DETAIL_WECHAT, queryBatchTransferDetailParams)
.function((type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("batch_id", params.getBatchIdOrOutBatchNo());
queryParams.add("detail_id", params.getDetailIdOrOutDetailNo());
Map<String, String> pathParams = new HashMap<>(2);
pathParams.put("batch_id", params.getBatchIdOrOutBatchNo());
pathParams.put("detail_id", params.getDetailIdOrOutDetailNo());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(queryParams)
.expand(pathParams)
.toUri();
return Get(uri);
})
@@ -195,13 +195,13 @@ public class WechatBatchTransferApi extends AbstractApi {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_DETAIL_MCH, queryBatchTransferDetailParams)
.function((type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("out_batch_no", params.getBatchIdOrOutBatchNo());
queryParams.add("out_detail_no", params.getDetailIdOrOutDetailNo());
Map<String, String> pathParams = new HashMap<>(2);
pathParams.put("batch_id", params.getBatchIdOrOutBatchNo());
pathParams.put("detail_id", params.getDetailIdOrOutDetailNo());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(queryParams)
.expand(pathParams)
.toUri();
return Get(uri);
})
@@ -256,4 +256,150 @@ public class WechatBatchTransferApi extends AbstractApi {
Assert.hasText(downloadUrl, "download url has no text");
return this.billResource(downloadUrl);
}
/**
* 转账明细电子回单受理API
* <p>
* 受理转账明细电子回单接口,商户通过该接口可以申请受理转账明细单电子回单服务。
* <p>
* 返回的下载链接可调用{@link #downloadBillResponse(String, String)}下载文件
*
* @param params the params
* @return the wechat response entity
* @since 1.0.11.RELEASE
*/
public WechatResponseEntity<ObjectNode> transferElectronic(TransferDetailElectronicParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_ELECTRONIC, params)
.function((type, transferDetailElectronic) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, transferDetailElectronic);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询转账明细电子回单受理结果API
* <p>
* 查询转账明细电子回单受理结果接口,商户通过该接口可以查询电子回单受理进度信息,
* 包括电子回单据信息电子回单文件的hash值电子回单文件的下载地址等。
* <p>
* 返回的下载链接可调用{@link #downloadBillResponse(String, String)}下载文件
*
* @param params the params
* @return the wechat response entity
* @since 1.0.11.RELEASE
*/
public WechatResponseEntity<ObjectNode> queryTransferElectronicResult(TransferDetailElectronicParams params) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_ELECTRONIC_DETAIL, params)
.function((type, transferDetailElectronic) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("accept_type", transferDetailElectronic.getAcceptType().name());
queryParams.add("out_batch_no", transferDetailElectronic.getOutBatchNo());
queryParams.add("out_detail_no", transferDetailElectronic.getOutDetailNo());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询账户实时余额API
* <p>
* 商户通过此接口可以查询本商户号的账号余额情况。
*
* @param accountType the account type
* @return the wechat response entity
* @since 1.0.11.RELEASE
*/
public WechatResponseEntity<ObjectNode> queryFundBalance(FundFlowAccountType accountType) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_FUND_BALANCE, accountType)
.function((type, flowAccountType) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.build()
.expand(flowAccountType.name())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询账户日终余额API
* <p>
* 通过此接口可以查询本商户号指定日期当天24点的账户余额。
*
* @param queryDayBalanceParams the transfer day balance params
* @return the wechat response entity
* @since 1.0.11.RELEASE
*/
public WechatResponseEntity<ObjectNode> queryDayFundBalance(QueryDayBalanceParams queryDayBalanceParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_FUND_DAY_BALANCE, queryDayBalanceParams)
.function((type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParam("date", params.getDate().toString())
.build()
.expand(params.getAccountType().name())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 商户银行来账查询API
* <p>
* 商户通过本接口查询指定日期内本商户银行来账记录列表。
* 列表内包含本商户银行来账相关的业务单号、金额、完成时间等信息,用于查询和核对。
* <p>
* 注意:
* <ol>
* <li>如需检索,请在前端缓存所有银行来账记录数据并自行完成检索功能;</li>
* <li>调用该接口前,商户需提前开通“来账识别”产品权限;</li>
* <li>本接口对可查询的商户范围有所规定,仅支持对本商户进行查询;</li>
* <li>本接口仅提供近90天内的银行来账记录查询且一次只能查询一天商户需确保查询记录日期在此范围内</li>
* <li>本接口返回的记录字段后续可能会有所扩充,商户需做好接口兼容准备;</li>
* <li>单商户单接口最大请求频率不超过50TPS。</li>
* </ol>
*
* @param queryIncomeRecordParams the transfer day balance params
* @return the wechat response entity
* @since 1.0.11.RELEASE
*/
public WechatResponseEntity<ObjectNode> queryIncomeRecords(QueryIncomeRecordParams queryIncomeRecordParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.BATCH_TRANSFER_FUND_INCOME_RECORDS, queryIncomeRecordParams)
.function((type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("account_type", params.getAccountType().name());
queryParams.add("date", params.getDate().toString());
queryParams.add("offset", Optional.ofNullable(params.getOffset()).orElse(0).toString());
queryParams.add("limit", params.getLimit().toString());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -87,7 +87,7 @@ public class WechatDirectPayApi extends AbstractApi {
.toString()
.replaceAll("-", "");
String prepayId = body.get("prepay_id").asText();
String paySign = signatureProvider.doRequestSign(true,privateKey, appId, timestamp, nonceStr, prepayId);
String paySign = signatureProvider.doRequestSign(privateKey, appId, timestamp, nonceStr, prepayId);
String mchId = wechatMetaBean.getV3().getMchId();
body.put("appid", appId);
@@ -136,7 +136,7 @@ public class WechatDirectPayApi extends AbstractApi {
.toString()
.replaceAll("-", "");
String packageStr = "prepay_id=" + body.get("prepay_id").asText();
String paySign = signatureProvider.doRequestSign(true,privateKey, appId, timestamp, nonceStr, packageStr);
String paySign = signatureProvider.doRequestSign(privateKey, appId, timestamp, nonceStr, packageStr);
body.put("appId", appId);
body.put("timeStamp", timestamp);

View File

@@ -353,9 +353,9 @@ public class WechatMarketingFavorApi extends AbstractApi {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("appid", v3.getAppId());
MultiValueMap<String, String> pathParams = new LinkedMultiValueMap<>();
pathParams.add("openid", params.getOpenId());
pathParams.add("coupon_id", params.getCouponId());
Map<String, String> pathParams = new HashMap<>(2);
pathParams.put("openid", params.getOpenId());
pathParams.put("coupon_id", params.getCouponId());
URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
@@ -493,7 +493,7 @@ public class WechatMarketingFavorApi extends AbstractApi {
*
* @param stockId the stock id
* @return the wechat response entity
* @see AbstractApi#billDownload(String) AbstractApi#billDownload(String)对账单下载api
* @see AbstractApi#billCsvDownload(String) AbstractApi#billDownload(String)对账单下载api
*/
public WechatResponseEntity<ObjectNode> downloadStockUseFlow(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
@@ -501,7 +501,7 @@ public class WechatMarketingFavorApi extends AbstractApi {
.function(this::downloadFlowFunction)
.consumer(wechatResponseEntity::convert)
.request();
String csv = this.billDownload(wechatResponseEntity.getBody().get("url").asText());
String csv = this.billCsvDownload(wechatResponseEntity.getBody().get("url").asText());
wechatResponseEntity.getBody().put("csv", csv);
return wechatResponseEntity;
}
@@ -515,7 +515,7 @@ public class WechatMarketingFavorApi extends AbstractApi {
*
* @param stockId the stock id
* @return the wechat response entity
* @see AbstractApi#billDownload(String) AbstractApi#billDownload(String)对账单下载api
* @see AbstractApi#billCsvDownload(String) AbstractApi#billDownload(String)对账单下载api
*/
public WechatResponseEntity<ObjectNode> downloadStockRefundFlow(String stockId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
@@ -523,7 +523,7 @@ public class WechatMarketingFavorApi extends AbstractApi {
.function(this::downloadFlowFunction)
.consumer(wechatResponseEntity::convert)
.request();
String csv = this.billDownload(wechatResponseEntity.getBody().get("url").asText());
String csv = this.billCsvDownload(wechatResponseEntity.getBody().get("url").asText());
wechatResponseEntity.getBody().put("csv", csv);
return wechatResponseEntity;
}

View File

@@ -87,7 +87,7 @@ public class WechatPartnerPayApi extends AbstractApi {
.toString()
.replaceAll("-", "");
String prepayId = body.get("prepay_id").asText();
String paySign = signatureProvider.doRequestSign(true, privateKey, subAppid, timestamp, nonceStr, prepayId);
String paySign = signatureProvider.doRequestSign(privateKey, subAppid, timestamp, nonceStr, prepayId);
body.put("appid", subAppid);
body.put("partnerid", partnerPayParams.getSubMchid());
@@ -136,7 +136,7 @@ public class WechatPartnerPayApi extends AbstractApi {
.toString()
.replaceAll("-", "");
String packageStr = "prepay_id=" + body.get("prepay_id").asText();
String paySign = signatureProvider.doRequestSign(true, privateKey, subAppid, timestamp, nonceStr, packageStr);
String paySign = signatureProvider.doRequestSign(privateKey, subAppid, timestamp, nonceStr, packageStr);
body.put("appId", subAppid);
body.put("timeStamp", timestamp);

View File

@@ -0,0 +1,318 @@
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.profitsharing.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 微信V3服务商分账
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
public class WechatPartnerProfitsharingApi extends AbstractApi {
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatPartnerProfitsharingApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 请求分账API
* <p>
* 微信订单支付成功后,商户发起分账请求,将结算后的资金分到分账接收方
* <p>
* 注意:
* <ul>
* <li>对同一笔订单最多能发起20次分账请求每次请求最多分给50个接收方</li>
* <li>此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取</li>
* </ul>
*
* @param profitSharingOrder the profit sharing order
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> profitsharingOrders(PartnerProfitSharingOrder profitSharingOrder) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS, profitSharingOrder)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
SignatureProvider signatureProvider = this.client().signatureProvider();
X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
final X509Certificate x509Certificate = certificate.getX509Certificate();
params.setAppid(v3.getAppId());
List<Receiver> receivers = params.getReceivers();
if (!CollectionUtils.isEmpty(receivers)) {
List<Receiver> encrypted = receivers.stream()
.peek(receiversItem -> {
String name = receiversItem.getName();
if (StringUtils.hasText(name)) {
String encryptedName = signatureProvider.encryptRequestMessage(name, x509Certificate);
receiversItem.setName(encryptedName);
}
}).collect(Collectors.toList());
params.setReceivers(encrypted);
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Wechatpay-Serial", certificate.getWechatPaySerial());
return Post(uri, params, httpHeaders);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询分账结果API
* <p>
* 发起分账请求后,可调用此接口查询分账结果
* <p>
* 注意:
* <ul>
* <li>发起解冻剩余资金请求后,可调用此接口查询解冻剩余资金的结果</li>
* </ul>
*
* @param queryOrderParams the query order params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryProfitsharingOrder(PartnerQueryOrderParams queryOrderParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS_RESULT, queryOrderParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("transaction_id", params.getTransactionId());
Optional.ofNullable(params.getSubMchid())
.ifPresent(mchId -> queryParams.add("sub_mchid", params.getSubMchid()));
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(params.getOutOrderNo())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 请求分账回退API
* <p>
* 如果订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款
* <p>
* 注意:
* <ul>
* <li>分账回退以原分账单为依据,支持多次回退,申请回退总金额不能超过原分账单分给该接收方的金额</li>
* <li>此接口采用同步处理模式,即在接收到商户请求后,会实时返回处理结果</li>
* <li>对同一笔分账单最多能发起20次分账回退请求</li>
* <li>退款和分账回退没有耦合,分账回退可以先于退款请求,也可以后于退款请求</li>
* <li>此功能需要接收方在商户平台-交易中心-分账-分账接收设置下,开启同意分账回退后,才能使用</li>
* </ul>
*
* @param returnOrdersParams the return orders params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> returnOrders(PartnerReturnOrdersParams returnOrdersParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RETURN_ORDERS, returnOrdersParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询分账回退结果API
* <p>
* 商户需要核实回退结果,可调用此接口查询回退结果
* <p>
* 注意:
* <ul>
* <li>如果分账回退接口返回状态为处理中,可调用此接口查询回退结果</li>
* </ul>
*
* @param queryReturnOrderParams the query return order params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryReturnOrders(PartnerQueryReturnOrderParams queryReturnOrderParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RETURN_ORDERS_RESULT, queryReturnOrderParams)
.function((wechatPayV3Type, params) -> {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("out_order_no", params.getOutOrderNo());
Optional.ofNullable(params.getSubMchid())
.ifPresent(mchId -> queryParams.add("sub_mchid", params.getSubMchid()));
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParams(queryParams)
.build()
.expand(params.getOutReturnNo())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 解冻剩余资金API
* <p>
* 不需要进行分账的订单,可直接调用本接口将订单的金额全部解冻给特约商户
* <p>
* 注意:
* <ul>
* <li>调用分账接口后,需要解冻剩余资金时,调用本接口将剩余的分账金额全部解冻给特约商户</li>
* <li>此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取</li>
* </ul>
*
* @param unfreezeParams the unfreeze params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> unfreeze(PartnerUnfreezeParams unfreezeParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS_UNFREEZE, unfreezeParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询剩余待分金额API
* <p>
* 可调用此接口查询订单剩余待分金额
*
* @param transactionId the transaction id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryAmounts(String transactionId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_AMOUNTS, transactionId)
.function((wechatPayV3Type, id) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(id)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 服务商查询剩余待分金额API
* <p>
* 可调用此接口查询订单剩余待分金额
*
* @param subMchid the sub mchid
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryMchConfigs(String subMchid) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_MCH_CONFIG, subMchid)
.function((wechatPayV3Type, id) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(id)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 添加分账接收方API
* <p>
* 商户发起添加分账接收方请求,建立分账接收方列表。后续可通过发起分账请求,将分账方商户结算后的资金,分到该分账接收方
*
* @param addReceiversParams the add receivers params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> addReceivers(PartnerAddReceiversParams addReceiversParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RECEIVERS_ADD, addReceiversParams)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
SignatureProvider signatureProvider = this.client().signatureProvider();
X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
final X509Certificate x509Certificate = certificate.getX509Certificate();
params.setAppid(v3.getAppId());
String name = params.getName();
if (StringUtils.hasText(name)) {
String encryptedName = signatureProvider.encryptRequestMessage(name, x509Certificate);
params.setName(encryptedName);
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Wechatpay-Serial", certificate.getWechatPaySerial());
return Post(uri, params, httpHeaders);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 删除分账接收方API
* <p>
* 商户发起删除分账接收方请求。删除后,不支持将分账方商户结算后的资金,分到该分账接收方
*
* @param delReceiversParams the del receivers params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> deleteReceivers(PartnerDelReceiversParams delReceiversParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RECEIVERS_DELETE, delReceiversParams)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
params.setAppid(v3.getAppId());
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -29,6 +29,7 @@ import cn.felord.payment.wechat.v3.model.payscore.PayScoreConsumer;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserConfirmConsumeData;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserPaidConsumeData;
import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserPermissionConsumeData;
import cn.felord.payment.wechat.v3.model.profitsharing.ProfitsharingConsumeData;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -39,7 +40,6 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -73,8 +73,8 @@ public class WechatPayCallback {
static {
MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false)
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
.registerModule(new JavaTimeModule());
}
@@ -92,7 +92,7 @@ public class WechatPayCallback {
/**
* 微信支付分账回调.
* 微信支付分账V2回调.
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
@@ -100,14 +100,11 @@ public class WechatPayCallback {
* @since 1.0.10.RELEASE
*/
@SneakyThrows
public Map<String, ?> profitSharingCallback(ResponseSignVerifyParams params, Consumer<ProfitSharingConsumeData> consumeDataConsumer) {
public Map<String, String> profitSharingCallback(ResponseSignVerifyParams params, Consumer<ProfitSharingConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.TRANSACTION);
ProfitSharingConsumeData consumeData = MAPPER.readValue(data, ProfitSharingConsumeData.class);
consumeDataConsumer.accept(consumeData);
Map<String, Object> responseBody = new HashMap<>(2);
responseBody.put("code", 200);
responseBody.put("message", "SUCCESS");
return responseBody;
return response();
}
@@ -121,14 +118,11 @@ public class WechatPayCallback {
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> couponCallback(ResponseSignVerifyParams params, Consumer<CouponConsumeData> consumeDataConsumer) {
public Map<String, String> couponCallback(ResponseSignVerifyParams params, Consumer<CouponConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.COUPON_USE);
CouponConsumeData consumeData = MAPPER.readValue(data, CouponConsumeData.class);
consumeDataConsumer.accept(consumeData);
Map<String, Object> responseBody = new HashMap<>(2);
responseBody.put("code", 200);
responseBody.put("message", "SUCCESS");
return responseBody;
return response();
}
@@ -143,12 +137,11 @@ public class WechatPayCallback {
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> transactionCallback(ResponseSignVerifyParams params, Consumer<TransactionConsumeData> consumeDataConsumer) {
public Map<String, String> transactionCallback(ResponseSignVerifyParams params, Consumer<TransactionConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.TRANSACTION);
TransactionConsumeData consumeData = MAPPER.readValue(data, TransactionConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -162,12 +155,11 @@ public class WechatPayCallback {
* @since 1.0.0.RELEASE
*/
@SneakyThrows
public Map<String, ?> combineTransactionCallback(ResponseSignVerifyParams params, Consumer<CombineTransactionConsumeData> consumeDataConsumer) {
public Map<String, String> combineTransactionCallback(ResponseSignVerifyParams params, Consumer<CombineTransactionConsumeData> consumeDataConsumer) {
String data = this.callback(params, EventType.TRANSACTION);
CombineTransactionConsumeData consumeData = MAPPER.readValue(data, CombineTransactionConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -181,7 +173,7 @@ public class WechatPayCallback {
* @since 1.0.2.RELEASE
*/
@SneakyThrows
public Map<String, ?> payscoreUserOrderCallback(ResponseSignVerifyParams params, PayScoreConsumer payScoreConsumer) {
public Map<String, String> payscoreUserOrderCallback(ResponseSignVerifyParams params, PayScoreConsumer payScoreConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
@@ -197,8 +189,7 @@ public class WechatPayCallback {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -213,7 +204,7 @@ public class WechatPayCallback {
* @return the map
*/
@SneakyThrows
public Map<String, ?> permissionCallback(ResponseSignVerifyParams params, Consumer<PayScoreUserPermissionConsumeData> consumeDataConsumer) {
public Map<String, String> permissionCallback(ResponseSignVerifyParams params, Consumer<PayScoreUserPermissionConsumeData> consumeDataConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
boolean closed;
@@ -229,7 +220,7 @@ public class WechatPayCallback {
PayScoreUserPermissionConsumeData consumeData = MAPPER.readValue(data, PayScoreUserPermissionConsumeData.class);
consumeData.setClosed(closed);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -265,7 +256,7 @@ public class WechatPayCallback {
log.error("wechat pay event type is not matched, callbackParams {}", callbackParams);
throw new PayException(" wechat pay event type is not matched");
}
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -280,7 +271,7 @@ public class WechatPayCallback {
* @return the map
*/
@SneakyThrows
public Map<String, ?> busiFavorReceiveCallback(ResponseSignVerifyParams params, Consumer<BusiFavorReceiveConsumeData> consumeDataConsumer) {
public Map<String, String> busiFavorReceiveCallback(ResponseSignVerifyParams params, Consumer<BusiFavorReceiveConsumeData> consumeDataConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
@@ -292,7 +283,7 @@ public class WechatPayCallback {
BusiFavorReceiveConsumeData consumeData = MAPPER.readValue(data, BusiFavorReceiveConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
@@ -302,10 +293,10 @@ public class WechatPayCallback {
*
* @param params the params
* @param consumeDataConsumer the consume data consumer
* @return map
* @return map map
*/
@SneakyThrows
public Map<String, ?> refundCallback(ResponseSignVerifyParams params, Consumer<RefundConsumeData> consumeDataConsumer) {
public Map<String, String> refundCallback(ResponseSignVerifyParams params, Consumer<RefundConsumeData> consumeDataConsumer) {
CallbackParams callbackParams = resolve(params);
String eventType = callbackParams.getEventType();
@@ -319,9 +310,23 @@ public class WechatPayCallback {
RefundConsumeData consumeData = MAPPER.readValue(data, RefundConsumeData.class);
consumeDataConsumer.accept(consumeData);
return Collections.singletonMap("code", "SUCCESS");
return response();
}
/**
* 微信支付分账V3动账通知
*
* @param params the params
* @param profitsharingConsumeDataConsumer the profitsharing consume data consumer
* @return map map
*/
@SneakyThrows
public Map<String, String> profitsharingCallback(ResponseSignVerifyParams params, Consumer<ProfitsharingConsumeData> profitsharingConsumeDataConsumer) {
String callback = this.callback(params, EventType.TRANSACTION);
ProfitsharingConsumeData consumeData = MAPPER.readValue(callback, ProfitsharingConsumeData.class);
profitsharingConsumeDataConsumer.accept(consumeData);
return response();
}
/**
* Callback.
@@ -373,6 +378,18 @@ public class WechatPayCallback {
return data;
}
/**
* 回调应答
*
* @return response
*/
private Map<String, String> response() {
Map<String, String> responseBody = new HashMap<>(2);
responseBody.put("code", "SUCCESS");
responseBody.put("message", "SUCCESS");
return responseBody;
}
/**
* 事件类型用于处理回调.
@@ -450,7 +467,7 @@ public class WechatPayCallback {
COUPON_SEND("COUPON.SEND"),
/**
* 支付成功事件.
* 支付成功、分账、分账回退事件.
*
* @since 1.0.0.RELEASE
*/

View File

@@ -164,7 +164,7 @@ public class WechatPayClient {
public void request() {
RequestEntity<?> requestEntity = this.requestEntityBiFunction.apply(this.wechatPayV3Type, this.model);
WechatRequestEntity<?> wechatRequestEntity = WechatRequestEntity.of(requestEntity, this.responseBodyConsumer);
this.doExecute(this.header(true,wechatRequestEntity));
this.doExecute(this.header(wechatRequestEntity));
}
@@ -176,7 +176,7 @@ public class WechatPayClient {
public String download() {
RequestEntity<?> requestEntity = this.requestEntityBiFunction.apply(this.wechatPayV3Type, this.model);
WechatRequestEntity<?> wechatRequestEntity = WechatRequestEntity.of(requestEntity, this.responseBodyConsumer);
return this.doDownload(this.header(true,wechatRequestEntity));
return this.doDownload(this.header(wechatRequestEntity));
}
/**
@@ -185,10 +185,10 @@ public class WechatPayClient {
* @return the string
* @since 1.0.6.RELEASE
*/
public ResponseEntity<Resource> resource() {
protected ResponseEntity<Resource> resource() {
RequestEntity<?> requestEntity = this.requestEntityBiFunction.apply(this.wechatPayV3Type, this.model);
WechatRequestEntity<?> wechatRequestEntity = WechatRequestEntity.of(requestEntity, this.responseBodyConsumer);
return this.doResource(this.header(false,wechatRequestEntity));
return this.doResource(this.header(wechatRequestEntity));
}
@@ -199,7 +199,7 @@ public class WechatPayClient {
* @param requestEntity the request entity
* @return the wechat request entity
*/
private <T> WechatRequestEntity<T> header(boolean newLine,WechatRequestEntity<T> requestEntity) {
private <T> WechatRequestEntity<T> header(WechatRequestEntity<T> requestEntity) {
UriComponents uri = UriComponentsBuilder.fromUri(requestEntity.getUrl()).build();
String canonicalUrl = uri.getPath();
@@ -220,7 +220,7 @@ public class WechatPayClient {
}
String tenantId = Objects.requireNonNull(headers.get("Pay-TenantId")).get(0);
String authorization = signatureProvider.requestSign(newLine,tenantId, httpMethod.name(), canonicalUrl, body);
String authorization = signatureProvider.requestSign(tenantId, httpMethod.name(), canonicalUrl, body);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.addAll(headers);

View File

@@ -29,6 +29,8 @@ import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
* 微信支付分API.
@@ -63,14 +65,14 @@ public class WechatPayScoreApi extends AbstractApi {
.function((wechatPayV3Type, userServiceStateParams) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
MultiValueMap<String, String> expandParams = new LinkedMultiValueMap<>();
expandParams.add("appid", v3.getAppId());
expandParams.add("service_id", params.getServiceId());
expandParams.add("openid", params.getOpenId());
Map<String, String> pathParams = new HashMap<>(3);
pathParams.put("appid", v3.getAppId());
pathParams.put("service_id", params.getServiceId());
pathParams.put("openid", params.getOpenId());
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(expandParams)
.expand(pathParams)
.toUri();
return Get(uri);
})

View File

@@ -0,0 +1,285 @@
package cn.felord.payment.wechat.v3;
import cn.felord.payment.wechat.WechatPayProperties;
import cn.felord.payment.wechat.enumeration.WeChatServer;
import cn.felord.payment.wechat.enumeration.WechatPayV3Type;
import cn.felord.payment.wechat.v3.model.profitsharing.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.stream.Collectors;
/**
* 微信V3直联商户分账
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
public class WechatProfitsharingApi extends AbstractApi {
/**
* Instantiates a new Abstract api.
*
* @param wechatPayClient the wechat pay client
* @param tenantId the tenant id
*/
public WechatProfitsharingApi(WechatPayClient wechatPayClient, String tenantId) {
super(wechatPayClient, tenantId);
}
/**
* 请求分账API
* <p>
* 微信订单支付成功后,商户发起分账请求,将结算后的资金分到分账接收方
* <p>
* 注意:
* <ul>
* <li>对同一笔订单最多能发起20次分账请求每次请求最多分给50个接收方</li>
* <li>此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取</li>
* </ul>
*
* @param profitSharingOrder the profit sharing order
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> profitsharingOrders(ProfitSharingOrder profitSharingOrder) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS, profitSharingOrder)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
SignatureProvider signatureProvider = this.client().signatureProvider();
X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
final X509Certificate x509Certificate = certificate.getX509Certificate();
params.setAppid(v3.getAppId());
List<Receiver> receivers = params.getReceivers();
if (!CollectionUtils.isEmpty(receivers)) {
List<Receiver> encrypted = receivers.stream()
.peek(receiversItem -> {
String name = receiversItem.getName();
if (StringUtils.hasText(name)) {
String encryptedName = signatureProvider.encryptRequestMessage(name, x509Certificate);
receiversItem.setName(encryptedName);
}
}).collect(Collectors.toList());
params.setReceivers(encrypted);
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Wechatpay-Serial", certificate.getWechatPaySerial());
return Post(uri, params, httpHeaders);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询分账结果API
* <p>
* 发起分账请求后,可调用此接口查询分账结果
* <p>
* 注意:
* <ul>
* <li>发起解冻剩余资金请求后,可调用此接口查询解冻剩余资金的结果</li>
* </ul>
*
* @param queryOrderParams the query order params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryProfitsharingOrder(QueryOrderParams queryOrderParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS_RESULT, queryOrderParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParam("transaction_id", params.getTransactionId())
.build()
.expand(params.getOutOrderNo())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 请求分账回退API
* <p>
* 如果订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款
* <p>
* 注意:
* <ul>
* <li>分账回退以原分账单为依据,支持多次回退,申请回退总金额不能超过原分账单分给该接收方的金额</li>
* <li>此接口采用同步处理模式,即在接收到商户请求后,会实时返回处理结果</li>
* <li>对同一笔分账单最多能发起20次分账回退请求</li>
* <li>退款和分账回退没有耦合,分账回退可以先于退款请求,也可以后于退款请求</li>
* <li>此功能需要接收方在商户平台-交易中心-分账-分账接收设置下,开启同意分账回退后,才能使用</li>
* </ul>
*
* @param returnOrdersParams the return orders params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> returnOrders(ReturnOrdersParams returnOrdersParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RETURN_ORDERS, returnOrdersParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询分账回退结果API
* <p>
* 商户需要核实回退结果,可调用此接口查询回退结果
* <p>
* 注意:
* <ul>
* <li>如果分账回退接口返回状态为处理中,可调用此接口查询回退结果</li>
* </ul>
*
* @param queryReturnOrderParams the query return order params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryReturnOrders(QueryReturnOrderParams queryReturnOrderParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RETURN_ORDERS_RESULT, queryReturnOrderParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.queryParam("out_order_no", params.getOutOrderNo())
.build()
.expand(params.getOutReturnNo())
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 解冻剩余资金API
* <p>
* 不需要进行分账的订单,可直接调用本接口将订单的金额全部解冻给特约商户
* <p>
* 注意:
* <ul>
* <li>调用分账接口后,需要解冻剩余资金时,调用本接口将剩余的分账金额全部解冻给特约商户</li>
* <li>此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取</li>
* </ul>
*
* @param unfreezeParams the unfreeze params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> unfreeze(UnfreezeParams unfreezeParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_ORDERS_UNFREEZE, unfreezeParams)
.function((wechatPayV3Type, params) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 查询剩余待分金额API
* <p>
* 可调用此接口查询订单剩余待分金额
*
* @param transactionId the transaction id
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> queryAmounts(String transactionId) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_AMOUNTS, transactionId)
.function((wechatPayV3Type, id) -> {
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.expand(id)
.toUri();
return Get(uri);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 添加分账接收方API
* <p>
* 商户发起添加分账接收方请求,建立分账接收方列表。后续可通过发起分账请求,将分账方商户结算后的资金,分到该分账接收方
*
* @param addReceiversParams the add receivers params
* @return wechat response entity
*/
public WechatResponseEntity<ObjectNode> addReceivers(AddReceiversParams addReceiversParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RECEIVERS_ADD, addReceiversParams)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
SignatureProvider signatureProvider = this.client().signatureProvider();
X509WechatCertificateInfo certificate = signatureProvider.getCertificate();
final X509Certificate x509Certificate = certificate.getX509Certificate();
params.setAppid(v3.getAppId());
String name = params.getName();
if (StringUtils.hasText(name)) {
String encryptedName = signatureProvider.encryptRequestMessage(name, x509Certificate);
params.setName(encryptedName);
}
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Wechatpay-Serial", certificate.getWechatPaySerial());
return Post(uri, params, httpHeaders);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
/**
* 删除分账接收方API
* <p>
* 商户发起删除分账接收方请求。删除后,不支持将分账方商户结算后的资金,分到该分账接收方
*
* @param delReceiversParams the del receivers params
* @return the wechat response entity
*/
public WechatResponseEntity<ObjectNode> deleteReceivers(DelReceiversParams delReceiversParams) {
WechatResponseEntity<ObjectNode> wechatResponseEntity = new WechatResponseEntity<>();
this.client().withType(WechatPayV3Type.PROFITSHARING_RECEIVERS_DELETE, delReceiversParams)
.function((wechatPayV3Type, params) -> {
WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3();
params.setAppid(v3.getAppId());
URI uri = UriComponentsBuilder.fromHttpUrl(wechatPayV3Type.uri(WeChatServer.CHINA))
.build()
.toUri();
return Post(uri, params);
})
.consumer(wechatResponseEntity::convert)
.request();
return wechatResponseEntity;
}
}

View File

@@ -75,6 +75,12 @@ public class RefundParams {
*/
@Data
public static class RefundAmount {
/**
* 退款出资账户及金额
*
* @since 1.0.11.RELEASE
*/
private List<RefundForm> form;
/**
* 原订单金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
*/
@@ -89,4 +95,41 @@ public class RefundParams {
private Integer refund;
}
/**
* 退款出资账户及金额
* <p>
* 退款需要从指定账户出资时,传递此参数指定出资金额(币种的最小单位,只能为整数)。
* <p>
* 同时指定多个账户出资退款的使用场景需要满足以下条件:
* <ol>
* <li>未开通退款支出分离产品功能;</li>
* <li>订单属于分账订单,且分账处于待分账或分账中状态。</li>
* </ol>
* <p>
* 参数传递需要满足条件:
* <ol>
* <li>基本账户可用余额出资金额与基本账户不可用余额出资金额之和等于退款金额;</li>
* <li>账户类型不能重复。</li>
* </ol>
* <p>
* 上述任一条件不满足将返回错误
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public static class RefundForm {
/**
* 出资账户类型
* <p>
* {@code AVAILABLE} 可用余额
* {@code UNAVAILABLE} 不可用余额
*/
private String account;
/**
* 对应账户出资金额
*/
private String amount;
}
}

View File

@@ -0,0 +1,25 @@
package cn.felord.payment.wechat.v3.model.batchtransfer;
import cn.felord.payment.wechat.enumeration.FundFlowAccountType;
import lombok.Data;
import java.time.LocalDate;
/**
* 查询账户日终余额API 请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class QueryDayBalanceParams {
/**
* 账户类型,必填
* @see FundFlowAccountType
*/
private FundFlowAccountType accountType;
/**
* 日期,必填
*/
private LocalDate date;
}

View File

@@ -0,0 +1,37 @@
package cn.felord.payment.wechat.v3.model.batchtransfer;
import cn.felord.payment.wechat.enumeration.FundFlowAccountType;
import lombok.Data;
import java.time.LocalDate;
/**
* 商户银行来账查询API 请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class QueryIncomeRecordParams {
/**
* 账户类型,必填
* @see FundFlowAccountType
*/
private FundFlowAccountType accountType;
/**
* 日期,必填
*/
private LocalDate date;
/**
* 本次查询偏移量,选填
* <p>
* 表示该次请求资源的起始位置从0开始计数。调用方选填默认为0。offset为20limit为100时查询第20-119条数据
*/
private Integer offset;
/**
* 本次请求最大查询条数,必填
* <p>
* 非0非负的整数该次请求可返回的最大资源条数最大支持100条。
*/
private Integer limit;
}

View File

@@ -0,0 +1,35 @@
package cn.felord.payment.wechat.v3.model.batchtransfer;
import cn.felord.payment.wechat.enumeration.TransferAcceptType;
import lombok.Data;
/**
* 转账明细电子回单受理API请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class TransferDetailElectronicParams {
/**
* 电子回单受理类型,必填。
*
* @see TransferAcceptType
*/
private TransferAcceptType acceptType;
/**
* 商家转账批次单号,选填。
* <p>
* 需要电子回单的批量转账明细单所在的转账批次单号,该单号为商户申请转账时生成的商户单号。
* 受理类型为{@code BATCH_TRANSFER}时该单号必填,否则该单号留空。
*/
private String outBatchNo;
/**
* 商家转账明细单号,必填。
* <ul>
* <li>受理类型为{@code BATCH_TRANSFER}时填写商家批量转账明细单号。</li>
* <li>受理类型为{@code TRANSFER_TO_POCKET}或{@code TRANSFER_TO_BANK}时填写商家转账单号。</li>
* </ul>
*/
private String outDetailNo;
}

View File

@@ -19,6 +19,7 @@ package cn.felord.payment.wechat.v3.model.combine;
import cn.felord.payment.wechat.enumeration.TradeState;
import cn.felord.payment.wechat.enumeration.TradeType;
import cn.felord.payment.wechat.v3.model.PromotionDetail;
import cn.felord.payment.wechat.v3.model.SceneInfo;
import lombok.Data;
@@ -125,6 +126,13 @@ public class CombineTransactionConsumeData {
*/
private String transactionId;
/**
* 优惠功能,子单有核销优惠券时有返回
*
* @since 1.0.11.RELEASE
*/
private List<PromotionDetail> promotionDetail;
}
/**

View File

@@ -78,7 +78,7 @@ public class CompleteServiceOrderParams {
* false不分账默认false
* true分账。
*/
private Boolean profitSharing = Boolean.TRUE;
private Boolean profitSharing = Boolean.FALSE;
/**
* 订单优惠标记,选填
* <p>

View File

@@ -0,0 +1,92 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import cn.felord.payment.wechat.enumeration.ReceiverType;
import lombok.Data;
/**
* 直连商户-添加分账接收方API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class AddReceiversParams {
/**
* 应用ID自动注入
*/
private String appid;
/**
* 分账接收方类型,必填
*/
private ReceiverType type;
/**
* 分账接收方帐号,必填
*/
private String account;
/**
* 分账个人接收方姓名,选填
* <p>
* 分账接收方类型是{@code MERCHANT_ID}时,是商户全称(必传),当商户是小微商户或个体户时,是开户人姓名 分账接收方类型是{@code PERSONAL_OPENID}时,是个人姓名(选传,传则校验)
* <ol>
* <li>分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明</li>
* <li>使用微信支付平台证书中的公钥</li>
* <li>使用RSAES-OAEP算法进行加密</li>
* <li>将请求中HTTP头部的Wechatpay-Serial设置为证书序列号</li>
* </ol>
*/
private String name;
/**
* 与分账方的关系类型,必填
*/
private RelationType relationType;
/**
* 自定义的分账关系,选填
*/
private String customRelation;
/**
* 子商户与接收方的关系
*/
public enum RelationType {
/**
* 门店.
*/
STORE,
/**
* 员工.
*/
STAFF,
/**
* 店主.
*/
STORE_OWNER,
/**
* 合作伙伴.
*/
PARTNER,
/**
* 总部.
*/
HEADQUARTER,
/**
* 品牌方.
*/
BRAND,
/**
* 分销商.
*/
DISTRIBUTOR,
/**
* 用户.
*/
USER,
/**
* 供应商.
*/
SUPPLIER,
/**
* 自定义.
*/
CUSTOM
}
}

View File

@@ -0,0 +1,26 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import cn.felord.payment.wechat.enumeration.ReceiverType;
import lombok.Data;
/**
* 直连商户-删除分账接收方API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class DelReceiversParams {
/**
* 应用ID自动注入
*/
private String appid;
/**
* 分账接收方类型,必填
*/
private ReceiverType type;
/**
* 分账接收方帐号,必填
*/
private String account;
}

View File

@@ -0,0 +1,102 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import cn.felord.payment.wechat.enumeration.ReceiverType;
import lombok.Data;
/**
* 服务商-添加分账接收方API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerAddReceiversParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 应用ID自动注入
*/
private String appid;
/**
* 子商户应用ID选填
* <p>
* 分账接收方类型包含{@code PERSONAL_SUB_OPENID}时必填
*/
private String subAppid;
/**
* 分账接收方类型,必填
*/
private ReceiverType type;
/**
* 分账接收方帐号,必填
*/
private String account;
/**
* 分账个人接收方姓名,选填
* <p>
* 分账接收方类型是{@code MERCHANT_ID}时,是商户全称(必传),当商户是小微商户或个体户时,是开户人姓名 分账接收方类型是{@code PERSONAL_OPENID}时,是个人姓名(选传,传则校验)
* <ol>
* <li>分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明</li>
* <li>使用微信支付平台证书中的公钥</li>
* <li>使用RSAES-OAEP算法进行加密</li>
* <li>将请求中HTTP头部的Wechatpay-Serial设置为证书序列号</li>
* </ol>
*/
private String name;
/**
* 与分账方的关系类型,必填
*/
private RelationType relationType;
/**
* 自定义的分账关系,选填
*/
private String customRelation;
/**
* 子商户与接收方的关系
*/
public enum RelationType {
/**
* 门店.
*/
STORE,
/**
* 员工.
*/
STAFF,
/**
* 店主.
*/
STORE_OWNER,
/**
* 合作伙伴.
*/
PARTNER,
/**
* 总部.
*/
HEADQUARTER,
/**
* 品牌方.
*/
BRAND,
/**
* 分销商.
*/
DISTRIBUTOR,
/**
* 用户.
*/
USER,
/**
* 供应商.
*/
SUPPLIER,
/**
* 自定义.
*/
CUSTOM
}
}

View File

@@ -0,0 +1,36 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import cn.felord.payment.wechat.enumeration.ReceiverType;
import lombok.Data;
/**
* 服务商-删除分账接收方API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerDelReceiversParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 应用ID自动注入
*/
private String appid;
/**
* 子商户应用ID选填
* <p>
* 分账接收方类型包含{@code PERSONAL_SUB_OPENID}时必填
*/
private String subAppid;
/**
* 分账接收方类型,必填
*/
private ReceiverType type;
/**
* 分账接收方帐号,必填
*/
private String account;
}

View File

@@ -0,0 +1,54 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
import java.util.List;
/**
* 服务商请求分账API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerProfitSharingOrder {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 服务商应用ID自动注入
*/
private String appid;
/**
* 子商户应用ID选填
* <p>
* 分账接收方类型包含{@code PERSONAL_SUB_OPENID}时必填
*/
private String subAppid;
/**
* 微信订单号,必填
*/
private String transactionId;
/**
* 商户分账单号,必填
* <p>
* 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。
* 只能是数字、大小写字母_-|*@
*/
private String outOrderNo;
/**
* 分账接收方列表,选填
* <p>
* 可以设置出资商户作为分账接受方最多可有50个分账接收方
*/
private List<Receiver> receivers;
/**
* 是否解冻剩余未分资金,必填
* <ol>
* <li>如果为{@code true},该笔订单剩余未分账的金额会解冻回分账方商户;</li>
* <li>如果为{@code false},该笔订单剩余未分账的金额不会解冻回分账方商户,可以对该笔订单再次进行分账。</li>
* </ol>
*/
private Boolean unfreezeUnsplit;
}

View File

@@ -0,0 +1,84 @@
/*
*
* Copyright 2019-2020 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.v3.model.profitsharing;
import cn.felord.payment.wechat.v2.model.allocation.Receiver;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 服务商-微信支付分账动账通知参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerProfitsharingConsumeData {
/**
* 服务商商户号.
* <p>
* 服务商模式分账发起商户
*/
private String mchid;
/**
* 子商户号
* <p>
* 服务商模式分账出资商户
*/
private String subMchid;
/**
* 微信订单号.
* <p>
* 微信支付订单号
*/
private String transactionId;
/**
* 微信分账/回退单号.
*/
private String orderId;
/**
* 商户分账/回退单号.
* <p>
* 分账方系统内部的分账/回退单号
*/
private String outOrderNo;
/**
* 分账接收方.
* <p>
* 分账接收方对象
*/
private List<Receiver> receivers;
/**
* 成功时间.
* <p>
* Rfc3339标准
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "GMT+8")
private LocalDateTime successTime;
}

View File

@@ -0,0 +1,25 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 服务商-查询分账结果API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerQueryOrderParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 商户分账单号,必填
*/
private String outOrderNo;
/**
* 微信订单号,必填
*/
private String transactionId;
}

View File

@@ -0,0 +1,25 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 服务商-查询分账回退结果API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerQueryReturnOrderParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 商户回退单号,必填
*/
private String outReturnNo;
/**
* 商户分账单号,必填
*/
private String outOrderNo;
}

View File

@@ -0,0 +1,45 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 服务商-请求分账回退API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerReturnOrdersParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 微信分账单号,同{@link #outOrderNo} 二选一
*/
private String orderId;
/**
* 商户分账单号,同{@link #orderId} 二选一
*/
private String outOrderNo;
/**
* 商户回退单号,必填
*/
private String outReturnNo;
/**
* 回退商户号,必填
* <p>
* 分账回退的出资商户,只能对原分账请求中成功分给商户接收方进行回退
*/
private String returnMchid;
/**
* 回退金额,必填
* <p>
* 需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额
*/
private Integer amount;
/**
* 回退描述,必填
*/
private String description;
}

View File

@@ -0,0 +1,29 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 服务商-解冻剩余资金API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class PartnerUnfreezeParams {
/**
* 子商户号,选填
*/
private String subMchid;
/**
* 微信订单号,必填
*/
private String transactionId;
/**
* 商户分账单号,必填
*/
private String outOrderNo;
/**
* 分账描述,必填
*/
private String description;
}

View File

@@ -0,0 +1,44 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
import java.util.List;
/**
* 直连商户-请求分账API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class ProfitSharingOrder {
/**
* 应用ID自动注入
*/
private String appid;
/**
* 微信订单号,必填
*/
private String transactionId;
/**
* 商户分账单号,必填
* <p>
* 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。
* 只能是数字、大小写字母_-|*@
*/
private String outOrderNo;
/**
* 分账接收方列表,选填
* <p>
* 可以设置出资商户作为分账接受方最多可有50个分账接收方
*/
private List<Receiver> receivers;
/**
* 是否解冻剩余未分资金,必填
* <ol>
* <li>如果为{@code true},该笔订单剩余未分账的金额会解冻回分账方商户;</li>
* <li>如果为{@code false},该笔订单剩余未分账的金额不会解冻回分账方商户,可以对该笔订单再次进行分账。</li>
* </ol>
*/
private Boolean unfreezeUnsplit;
}

View File

@@ -0,0 +1,78 @@
/*
*
* Copyright 2019-2020 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.v3.model.profitsharing;
import cn.felord.payment.wechat.v2.model.allocation.Receiver;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 直连商户-微信支付分账动账通知参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class ProfitsharingConsumeData {
/**
* 直连商户号.
* <p>
* 直连模式分账发起和出资商户
*/
private String mchid;
/**
* 微信订单号.
* <p>
* 微信支付订单号
*/
private String transactionId;
/**
* 微信分账/回退单号.
*/
private String orderId;
/**
* 商户分账/回退单号.
* <p>
* 分账方系统内部的分账/回退单号
*/
private String outOrderNo;
/**
* 分账接收方.
* <p>
* 分账接收方对象
*/
private List<Receiver> receivers;
/**
* 成功时间.
* <p>
* Rfc3339标准
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "GMT+8")
private LocalDateTime successTime;
}

View File

@@ -0,0 +1,21 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 直连商户-查询分账结果API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class QueryOrderParams {
/**
* 商户分账单号,必填
*/
private String outOrderNo;
/**
* 微信订单号,必填
*/
private String transactionId;
}

View File

@@ -0,0 +1,21 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 直连商户-查询分账回退结果API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class QueryReturnOrderParams {
/**
* 商户回退单号,必填
*/
private String outReturnNo;
/**
* 商户分账单号,必填
*/
private String outOrderNo;
}

View File

@@ -0,0 +1,44 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import cn.felord.payment.wechat.enumeration.ReceiverType;
import lombok.Data;
/**
* 分账接收方信息
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class Receiver {
/**
* 分账接收方类型,必填
*/
private ReceiverType type;
/**
* 分账接收方帐号,必填
*/
private String account;
/**
* 分账个人接收方姓名,选填
* <p>
* 在接收方类型为个人的时可选填,若有值,会检查与 name 是否实名匹配,不匹配会拒绝分账请求
* <ol>
* <li>分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明</li>
* <li>使用微信支付平台证书中的公钥</li>
* <li>使用RSAES-OAEP算法进行加密</li>
* <li>将请求中HTTP头部的Wechatpay-Serial设置为证书序列号</li>
* </ol>
*/
private String name;
/**
* 分账金额,必填
* <p>
* 单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额
*/
private Integer amount;
/**
* 分账的原因描述,必填。分账账单中需要体现
*/
private String description;
}

View File

@@ -0,0 +1,41 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 直连商户-请求分账回退API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class ReturnOrdersParams {
/**
* 微信分账单号,同{@link #outOrderNo} 二选一
*/
private String orderId;
/**
* 商户分账单号,同{@link #orderId} 二选一
*/
private String outOrderNo;
/**
* 商户回退单号,必填
*/
private String outReturnNo;
/**
* 回退商户号,必填
* <p>
* 分账回退的出资商户,只能对原分账请求中成功分给商户接收方进行回退
*/
private String returnMchid;
/**
* 回退金额,必填
* <p>
* 需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额
*/
private Integer amount;
/**
* 回退描述,必填
*/
private String description;
}

View File

@@ -0,0 +1,25 @@
package cn.felord.payment.wechat.v3.model.profitsharing;
import lombok.Data;
/**
* 直连商户-解冻剩余资金API-请求参数
*
* @author felord.cn
* @since 1.0.11.RELEASE
*/
@Data
public class UnfreezeParams {
/**
* 微信订单号,必填
*/
private String transactionId;
/**
* 商户分账单号,必填
*/
private String outOrderNo;
/**
* 分账描述,必填
*/
private String description;
}

View File

@@ -5,11 +5,11 @@
<parent>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
</parent>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>

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.10.RELEASE</version>
<version>1.0.11.RELEASE</version>
<packaging>pom</packaging>
<modelVersion>4.0.0</modelVersion>
@@ -76,7 +76,7 @@
<oss-starter.version>1.0.0.RELEASE</oss-starter.version>
<lombok.verison>1.18.12</lombok.verison>
<jackson.version>2.9.10</jackson.version>
<bcprov.version>1.66</bcprov.version>
<bcprov.version>1.67</bcprov.version>
<jackson.version>2.11.4</jackson.version>
<httpclient.version>4.5.13</httpclient.version>
</properties>