feat: 直连商户V3分账

This commit is contained in:
felord
2021-05-31 18:45:26 +08:00
parent 10a91c51be
commit e387e91e38
15 changed files with 816 additions and 46 deletions

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

@@ -496,6 +496,7 @@ public enum WechatPayV3Type {
/**
* 查询转账明细电子回单受理结果API.
* 请求方式同{@link WechatPayV3Type#BATCH_TRANSFER_ELECTRONIC}不同
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_ELECTRONIC_DETAIL(HttpMethod.GET, "%s/v3/transfer-detail/electronic-receipts"),
@@ -510,7 +511,8 @@ public enum WechatPayV3Type {
*
* @since 1.0.11.RELEASES
*/
BATCH_TRANSFER_FUND_DAY_BALANCE(HttpMethod.GET, "%s/v3/merchant/fund/dayendbalance/{account_type}"), /**
BATCH_TRANSFER_FUND_DAY_BALANCE(HttpMethod.GET, "%s/v3/merchant/fund/dayendbalance/{account_type}"),
/**
* 商户银行来账查询API
*
* @since 1.0.11.RELEASES
@@ -565,7 +567,56 @@ 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_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

@@ -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();
@@ -262,7 +262,7 @@ public class WechatBatchTransferApi extends AbstractApi {
* <p>
* 受理转账明细电子回单接口,商户通过该接口可以申请受理转账明细单电子回单服务。
* <p>
* 返回的下载链接可调用{@link this#downloadBillResponse(String, String)}下载文件
* 返回的下载链接可调用{@link #downloadBillResponse(String, String)}下载文件
*
* @param params the params
* @return the wechat response entity

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

@@ -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

@@ -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,45 @@
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;
/**
* 是否解冻剩余未分资金,必填
* <p>
* <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;
}