diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/ReceiverType.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/ReceiverType.java new file mode 100644 index 0000000..6c1c8be --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/ReceiverType.java @@ -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 +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java index c4ea443..b21734d 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/WechatPayV3Type.java @@ -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. * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/WechatAllocationApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/WechatAllocationApi.java index 6c815ca..e7c050b 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/WechatAllocationApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v2/WechatAllocationApi.java @@ -36,6 +36,7 @@ import java.util.List; * 微信支付分账 *

* + * @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 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"); } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java index a6602a5..01f6189 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatBatchTransferApi.java @@ -77,11 +77,11 @@ public class WechatBatchTransferApi extends AbstractApi { List transferDetailList = createBatchTransferParams.getTransferDetailList(); SignatureProvider signatureProvider = this.client().signatureProvider(); - final X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + X509WechatCertificateInfo certificate = signatureProvider.getCertificate(); + final X509Certificate x509Certificate = certificate.getX509Certificate(); List 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 { *

* 受理转账明细电子回单接口,商户通过该接口可以申请受理转账明细单电子回单服务。 *

- * 返回的下载链接可调用{@link this#downloadBillResponse(String, String)}下载文件 + * 返回的下载链接可调用{@link #downloadBillResponse(String, String)}下载文件 * * @param params the params * @return the wechat response entity diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayCallback.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayCallback.java index f6789a8..604abed 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayCallback.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayCallback.java @@ -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 profitSharingCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map profitSharingCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { String data = this.callback(params, EventType.TRANSACTION); ProfitSharingConsumeData consumeData = MAPPER.readValue(data, ProfitSharingConsumeData.class); consumeDataConsumer.accept(consumeData); - Map 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 couponCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map couponCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { String data = this.callback(params, EventType.COUPON_USE); CouponConsumeData consumeData = MAPPER.readValue(data, CouponConsumeData.class); consumeDataConsumer.accept(consumeData); - Map 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 transactionCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map transactionCallback(ResponseSignVerifyParams params, Consumer 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 combineTransactionCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map combineTransactionCallback(ResponseSignVerifyParams params, Consumer 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 payscoreUserOrderCallback(ResponseSignVerifyParams params, PayScoreConsumer payScoreConsumer) { + public Map 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 permissionCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map permissionCallback(ResponseSignVerifyParams params, Consumer 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 busiFavorReceiveCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map busiFavorReceiveCallback(ResponseSignVerifyParams params, Consumer 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 refundCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { + public Map refundCallback(ResponseSignVerifyParams params, Consumer 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 profitsharingCallback(ResponseSignVerifyParams params, Consumer 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 response() { + Map 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 */ diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java new file mode 100644 index 0000000..3158ae6 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatProfitsharingApi.java @@ -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 + *

+ * 微信订单支付成功后,商户发起分账请求,将结算后的资金分到分账接收方 + *

+ * 注意: + *

+ * + * @param profitSharingOrder the profit sharing order + * @return the wechat response entity + */ + public WechatResponseEntity profitsharingOrders(ProfitSharingOrder profitSharingOrder) { + WechatResponseEntity 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 receivers = params.getReceivers(); + if (!CollectionUtils.isEmpty(receivers)) { + List 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 + *

+ * 发起分账请求后,可调用此接口查询分账结果 + *

+ * 注意: + *

    + *
  • 发起解冻剩余资金请求后,可调用此接口查询解冻剩余资金的结果
  • + *
+ * + * @param queryOrderParams the query order params + * @return the wechat response entity + */ + public WechatResponseEntity queryProfitsharingOrder(QueryOrderParams queryOrderParams) { + WechatResponseEntity 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 + *

+ * 如果订单已经分账,在退款时,可以先调此接口,将已分账的资金从分账接收方的账户回退给分账方,再发起退款 + *

+ * 注意: + *

    + *
  • 分账回退以原分账单为依据,支持多次回退,申请回退总金额不能超过原分账单分给该接收方的金额
  • + *
  • 此接口采用同步处理模式,即在接收到商户请求后,会实时返回处理结果
  • + *
  • 对同一笔分账单最多能发起20次分账回退请求
  • + *
  • 退款和分账回退没有耦合,分账回退可以先于退款请求,也可以后于退款请求
  • + *
  • 此功能需要接收方在商户平台-交易中心-分账-分账接收设置下,开启同意分账回退后,才能使用
  • + *
+ * + * @param returnOrdersParams the return orders params + * @return the wechat response entity + */ + public WechatResponseEntity returnOrders(ReturnOrdersParams returnOrdersParams) { + WechatResponseEntity 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 + *

+ * 商户需要核实回退结果,可调用此接口查询回退结果 + *

+ * 注意: + *

    + *
  • 如果分账回退接口返回状态为处理中,可调用此接口查询回退结果
  • + *
+ * + * @param queryReturnOrderParams the query return order params + * @return the wechat response entity + */ + public WechatResponseEntity queryReturnOrders(QueryReturnOrderParams queryReturnOrderParams) { + WechatResponseEntity 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 + *

+ * 不需要进行分账的订单,可直接调用本接口将订单的金额全部解冻给特约商户 + *

+ * 注意: + *

    + *
  • 调用分账接口后,需要解冻剩余资金时,调用本接口将剩余的分账金额全部解冻给特约商户
  • + *
  • 此接口采用异步处理模式,即在接收到商户请求后,优先受理请求再异步处理,最终的分账结果可以通过查询分账接口获取
  • + *
+ * + * @param unfreezeParams the unfreeze params + * @return the wechat response entity + */ + public WechatResponseEntity unfreeze(UnfreezeParams unfreezeParams) { + WechatResponseEntity 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 + *

+ * 可调用此接口查询订单剩余待分金额 + * + * @param transactionId the transaction id + * @return the wechat response entity + */ + public WechatResponseEntity queryAmounts(String transactionId) { + WechatResponseEntity 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 + *

+ * 商户发起添加分账接收方请求,建立分账接收方列表。后续可通过发起分账请求,将分账方商户结算后的资金,分到该分账接收方 + * + * @param addReceiversParams the add receivers params + * @return wechat response entity + */ + public WechatResponseEntity addReceivers(AddReceiversParams addReceiversParams) { + WechatResponseEntity 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 + *

+ * 商户发起删除分账接收方请求。删除后,不支持将分账方商户结算后的资金,分到该分账接收方 + * + * @param delReceiversParams the del receivers params + * @return the wechat response entity + */ + public WechatResponseEntity deleteReceivers(DelReceiversParams delReceiversParams) { + WechatResponseEntity 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; + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/AddReceiversParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/AddReceiversParams.java new file mode 100644 index 0000000..5a9bf30 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/AddReceiversParams.java @@ -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; + /** + * 分账个人接收方姓名,选填 + *

+ * 分账接收方类型是{@code MERCHANT_ID}时,是商户全称(必传),当商户是小微商户或个体户时,是开户人姓名 分账接收方类型是{@code PERSONAL_OPENID}时,是个人姓名(选传,传则校验) + *

    + *
  1. 分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明
  2. + *
  3. 使用微信支付平台证书中的公钥
  4. + *
  5. 使用RSAES-OAEP算法进行加密
  6. + *
  7. 将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
  8. + *
+ */ + private String name; + /** + * 与分账方的关系类型,必填 + */ + private RelationType relationType; + /** + * 自定义的分账关系,选填 + */ + private String customRelation; + + /** + * 子商户与接收方的关系 + */ + public enum RelationType { + /** + * 门店. + */ + STORE, + /** + * 员工. + */ + STAFF, + /** + * 店主. + */ + STORE_OWNER, + /** + * 合作伙伴. + */ + PARTNER, + /** + * 总部. + */ + HEADQUARTER, + /** + * 品牌方. + */ + BRAND, + /** + * 分销商. + */ + DISTRIBUTOR, + /** + * 用户. + */ + USER, + /** + * 供应商. + */ + SUPPLIER, + /** + * 自定义. + */ + CUSTOM + } +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/DelReceiversParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/DelReceiversParams.java new file mode 100644 index 0000000..e306eb7 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/DelReceiversParams.java @@ -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; +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitSharingOrder.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitSharingOrder.java new file mode 100644 index 0000000..4dd55fe --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitSharingOrder.java @@ -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; + /** + * 商户分账单号,必填 + *

+ * 商户系统内部的分账单号,在商户系统内部唯一,同一分账单号多次请求等同一次。 + * 只能是数字、大小写字母_-|*@ + */ + private String outOrderNo; + /** + * 分账接收方列表,选填 + *

+ * 可以设置出资商户作为分账接受方,最多可有50个分账接收方 + */ + private List receivers; + /** + * 是否解冻剩余未分资金,必填 + *

+ *

    + *
  1. 如果为{@code true},该笔订单剩余未分账的金额会解冻回分账方商户;
  2. + *
  3. 如果为{@code false},该笔订单剩余未分账的金额不会解冻回分账方商户,可以对该笔订单再次进行分账。
  4. + *
+ */ + private Boolean unfreezeUnsplit; +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitsharingConsumeData.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitsharingConsumeData.java new file mode 100644 index 0000000..3b65f51 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ProfitsharingConsumeData.java @@ -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 { + + /** + * 直连商户号. + *

+ * 直连模式分账发起和出资商户 + */ + private String mchid; + + /** + * 微信订单号. + *

+ * 微信支付订单号 + */ + private String transactionId; + + /** + * 微信分账/回退单号. + */ + private String orderId; + + /** + * 商户分账/回退单号. + *

+ * 分账方系统内部的分账/回退单号 + */ + private String outOrderNo; + + /** + * 分账接收方. + *

+ * 分账接收方对象 + */ + private List receivers; + + /** + * 成功时间. + *

+ * Rfc3339标准 + */ + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "GMT+8") + private LocalDateTime successTime; + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryOrderParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryOrderParams.java new file mode 100644 index 0000000..8a25740 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryOrderParams.java @@ -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; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryReturnOrderParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryReturnOrderParams.java new file mode 100644 index 0000000..182e72e --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/QueryReturnOrderParams.java @@ -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; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/Receiver.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/Receiver.java new file mode 100644 index 0000000..3e9432b --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/Receiver.java @@ -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; + /** + * 分账个人接收方姓名,选填 + *

+ * 在接收方类型为个人的时可选填,若有值,会检查与 name 是否实名匹配,不匹配会拒绝分账请求 + *

    + *
  1. 分账接收方类型是{@code PERSONAL_OPENID},是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明
  2. + *
  3. 使用微信支付平台证书中的公钥
  4. + *
  5. 使用RSAES-OAEP算法进行加密
  6. + *
  7. 将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
  8. + *
+ */ + private String name; + /** + * 分账金额,必填 + *

+ * 单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额 + */ + private Integer amount; + /** + * 分账的原因描述,必填。分账账单中需要体现 + */ + private String description; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ReturnOrdersParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ReturnOrdersParams.java new file mode 100644 index 0000000..981c3f2 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/ReturnOrdersParams.java @@ -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; + /** + * 回退商户号,必填 + *

+ * 分账回退的出资商户,只能对原分账请求中成功分给商户接收方进行回退 + */ + private String returnMchid; + /** + * 回退金额,必填 + *

+ * 需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额 + */ + private Integer amount; + /** + * 回退描述,必填 + */ + private String description; +} \ No newline at end of file diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/UnfreezeParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/UnfreezeParams.java new file mode 100644 index 0000000..a53f789 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/profitsharing/UnfreezeParams.java @@ -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; +} \ No newline at end of file