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 133aa73..2b1500f 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 @@ -486,7 +486,57 @@ public enum WechatPayV3Type { * * @since 1.0.6.RELEASES */ - BATCH_TRANSFER_DOWNLOAD_BILL(HttpMethod.GET, "%s/v3/transfer/bill-receipt/{out_batch_no}"); + BATCH_TRANSFER_DOWNLOAD_BILL(HttpMethod.GET, "%s/v3/transfer/bill-receipt/{out_batch_no}"), + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * 服务商APP下单API. + * + * @since 1.0.8.RELEASES + */ + APP_PARTNER(HttpMethod.POST, "%s/v3/pay/partner/transactions/app"), + + /** + * 微信公众号支付或者小程序支付. + * + * @since 1.0.8.RELEASE + */ + JSAPI_PARTNER(HttpMethod.POST, "%s/v3/pay/partner/transactions/jsapi"), + + /** + * 微信扫码支付. + * + * @since 1.0.8.RELEASE + */ + NATIVE_PARTNER(HttpMethod.POST, "%s/v3/pay/partner/transactions/native"), + + /** + * H5支付. + * + * @since 1.0.8.RELEASE + */ + MWEB_PARTNER(HttpMethod.POST, "%s/v3/pay/partner/transactions/h5"), + + /** + * 关闭订单. + * + * @since 1.0.0.RELEASE + */ + CLOSE_PARTNER(HttpMethod.POST, "%s/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close"), + /** + * 微信支付订单号查询API. + * + * @since 1.0.0.RELEASE + */ + TRANSACTION_TRANSACTION_ID_PARTNER(HttpMethod.GET, "%s/v3/pay/partner/transactions/id/{transaction_id}"), + /** + * 商户订单号查询API. + * + * @since 1.0.0.RELEASE + */ + TRANSACTION_OUT_TRADE_NO_PARTNER(HttpMethod.GET, "%s/v3/pay/partner/transactions/out-trade-no/{out_trade_no}"), + ; /** * The Pattern. * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerPayApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerPayApi.java new file mode 100644 index 0000000..908c15b --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPartnerPayApi.java @@ -0,0 +1,276 @@ +/* + * Copyright 2019-2021 felord.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * Website: + * https://felord.cn + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.felord.payment.wechat.v3; + +import cn.felord.payment.PayException; +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.TransactionQueryParams; +import cn.felord.payment.wechat.v3.model.partner.CloseTransParams; +import cn.felord.payment.wechat.v3.model.partner.PartnerPayParams; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.RequestEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.security.PrivateKey; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 普通支付-服务商模式 + * + * @author felord.cn + * @since 1.0.8.RELEASE + */ +public class WechatPartnerPayApi extends AbstractApi { + /** + * Instantiates a new Abstract api. + * + * @param wechatPayClient the wechat pay client + * @param tenantId the tenant id + */ + public WechatPartnerPayApi(WechatPayClient wechatPayClient, String tenantId) { + super(wechatPayClient, tenantId); + } + + + /** + * APP下单API + * + * @param partnerPayParams the partner pay params + * @return the wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity appPay(PartnerPayParams partnerPayParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.APP_PARTNER, partnerPayParams) + .function(this::payFunction) + .consumer(responseEntity -> { + ObjectNode body = responseEntity.getBody(); + if (Objects.isNull(body)) { + throw new PayException("response body cannot be resolved"); + } + + SignatureProvider signatureProvider = this.client().signatureProvider(); + WechatMetaContainer wechatMetaContainer = signatureProvider.wechatMetaContainer(); + WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId()); + PrivateKey privateKey = wechatMetaBean.getKeyPair().getPrivate(); + + String subAppid = partnerPayParams.getSubAppid(); + long epochSecond = LocalDateTime.now() + .toEpochSecond(ZoneOffset.of("+8")); + String timestamp = String.valueOf(epochSecond); + String nonceStr = signatureProvider.nonceStrGenerator() + .generateId() + .toString() + .replaceAll("-", ""); + String prepayId = body.get("prepay_id").asText(); + String paySign = signatureProvider.doRequestSign(true, privateKey, subAppid, timestamp, nonceStr, prepayId); + + body.put("appid", subAppid); + body.put("partnerid", partnerPayParams.getSubMchid()); + body.put("prepayid", prepayId); + body.put("package", "Sign=WXPay"); + body.put("nonceStr", nonceStr); + body.put("timeStamp", timestamp); + body.put("signType", "RSA"); + body.put("paySign", paySign); + + wechatResponseEntity.setHttpStatus(responseEntity.getStatusCodeValue()); + wechatResponseEntity.setBody(body); + }) + .request(); + return wechatResponseEntity; + } + + /** + * JSAPI/小程序下单API + * + * @param partnerPayParams the pay params + * @return wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity jsPay(PartnerPayParams partnerPayParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.JSAPI_PARTNER, partnerPayParams) + .function(this::payFunction) + .consumer(responseEntity -> { + ObjectNode body = responseEntity.getBody(); + if (Objects.isNull(body)) { + throw new PayException("response body cannot be resolved"); + } + + SignatureProvider signatureProvider = this.client().signatureProvider(); + WechatMetaContainer wechatMetaContainer = signatureProvider.wechatMetaContainer(); + WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId()); + PrivateKey privateKey = wechatMetaBean.getKeyPair().getPrivate(); + + String subAppid = partnerPayParams.getSubAppid(); + long epochSecond = LocalDateTime.now() + .toEpochSecond(ZoneOffset.of("+8")); + String timestamp = String.valueOf(epochSecond); + String nonceStr = signatureProvider.nonceStrGenerator() + .generateId() + .toString() + .replaceAll("-", ""); + String packageStr = "prepay_id=" + body.get("prepay_id").asText(); + String paySign = signatureProvider.doRequestSign(true, privateKey, subAppid, timestamp, nonceStr, packageStr); + + body.put("appId", subAppid); + body.put("timeStamp", timestamp); + body.put("nonceStr", nonceStr); + body.put("package", packageStr); + body.put("signType", "RSA"); + body.put("paySign", paySign); + + wechatResponseEntity.setHttpStatus(responseEntity.getStatusCodeValue()); + wechatResponseEntity.setBody(body); + }) + .request(); + return wechatResponseEntity; + } + + /** + * Native下单API + * + * @param partnerPayParams the pay params + * @return wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity nativePay(PartnerPayParams partnerPayParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.NATIVE_PARTNER, partnerPayParams) + .function(this::payFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + + /** + * H5下单API + * + * @param partnerPayParams the partner pay params + * @return wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity h5Pay(PartnerPayParams partnerPayParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.MWEB_PARTNER, partnerPayParams) + .function(this::payFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + + private RequestEntity payFunction(WechatPayV3Type type, PartnerPayParams partnerPayParams) { + WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); + partnerPayParams.setSpAppid(v3.getAppId()); + partnerPayParams.setSpMchid(v3.getMchId()); + String notifyUrl = v3.getDomain().concat(partnerPayParams.getNotifyUrl()); + partnerPayParams.setNotifyUrl(notifyUrl); + URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA)) + .build() + .toUri(); + return Post(uri, partnerPayParams); + } + + /** + * 微信支付订单号查询API + * + * @param params the params + * @return the wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity queryTransactionById(TransactionQueryParams params) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.TRANSACTION_TRANSACTION_ID_PARTNER, params) + .function(this::queryTransactionFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + + /** + * 商户订单号查询API + * + * @param params the params + * @return the wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity queryTransactionByOutTradeNo(TransactionQueryParams params) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.TRANSACTION_OUT_TRADE_NO_PARTNER, params) + .function(this::queryTransactionFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + + private RequestEntity queryTransactionFunction(WechatPayV3Type type, TransactionQueryParams params) { + WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); + + MultiValueMap queryParams = new LinkedMultiValueMap<>(); + queryParams.add("sp_mchid", v3.getMchId()); + queryParams.add("sub_mchid", params.getMchId()); + + URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA)) + .queryParams(queryParams) + .build() + .expand(params.getTransactionIdOrOutTradeNo()) + .toUri(); + return Get(uri); + } + + /** + * 关单API + * + * @param closeTransParams the closeTransParams + * @return the wechat response entity + * @since 1.0.8.RELEASE + */ + public WechatResponseEntity close(CloseTransParams closeTransParams) { + WechatResponseEntity wechatResponseEntity = new WechatResponseEntity<>(); + this.client().withType(WechatPayV3Type.CLOSE_PARTNER, closeTransParams) + .function(this::closeByOutTradeNoFunction) + .consumer(wechatResponseEntity::convert) + .request(); + return wechatResponseEntity; + } + + private RequestEntity closeByOutTradeNoFunction(WechatPayV3Type type, CloseTransParams closeTransParams) { + WechatPayProperties.V3 v3 = this.wechatMetaBean().getV3(); + + Map queryParams = new HashMap<>(1); + queryParams.put("sp_mchid", v3.getMchId()); + queryParams.put("sub_mchid", closeTransParams.getSubMchid()); + + URI uri = UriComponentsBuilder.fromHttpUrl(type.uri(WeChatServer.CHINA)) + .build() + .expand(closeTransParams.getOutTradeNo()) + .toUri(); + return Post(uri, queryParams); + } + +} 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 750ec94..f232ddc 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 @@ -111,7 +111,7 @@ public class WechatPayCallback { } /** - * 微信支付成功回调. + * 微信支付成功回调,在1.0.8.RELEASE时支持服务商模式支付回调通知 *

* 无需开发者判断,只有扣款成功微信才会回调此接口 * diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/AbstractPayParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/AbstractPayParams.java new file mode 100644 index 0000000..f915df2 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/AbstractPayParams.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019-2021 felord.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * Website: + * https://felord.cn + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.felord.payment.wechat.v3.model; + +import lombok.Data; + +/** + * @author felord.cn + * @since 1.0.8.RELEASE + */ +@Data +public abstract class AbstractPayParams { + + /** + * 商品描述 + * Image形象店-深圳腾大-QQ公仔 + */ + private String description; + /** + * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。 + * 示例值:1217752501201407033233368018 + */ + private String outTradeNo; + /** + * 订单失效时间 YYYY-MM-DDTHH:mm:ss+TIMEZONE + */ + private String timeExpire; + /** + * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 + */ + private String attach; + /** + * 通知URL必须为直接可访问的URL,不允许携带查询串。 + */ + private String notifyUrl; + /** + * 订单优惠标记 + */ + private String goodsTag; + /** + * 支付金额 + */ + private Amount amount; + /** + * 优惠功能 + */ + private Detail detail; + /** + * 支付者 JSAPI/小程序下单 必传 + */ + private Payer payer; + /** + * 场景信息 + */ + private SceneInfo sceneInfo; + /** + * 结算信息 + */ + private SettleInfo settleInfo; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/PayParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/PayParams.java index 25a7e21..be8e6aa 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/PayParams.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/PayParams.java @@ -20,6 +20,7 @@ package cn.felord.payment.wechat.v3.model; import lombok.Data; +import lombok.EqualsAndHashCode; /** * 支付请求参数. @@ -27,8 +28,9 @@ import lombok.Data; * @author felord.cn * @since 1.0.0.RELEASE */ +@EqualsAndHashCode(callSuper = true) @Data -public class PayParams { +public class PayParams extends AbstractPayParams { /** * The Appid. */ @@ -37,48 +39,5 @@ public class PayParams { * The Mchid. */ private String mchid; - /** - * 商品描述 - * Image形象店-深圳腾大-QQ公仔 - */ - private String description; - /** - * 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。 - * 示例值:1217752501201407033233368018 - */ - private String outTradeNo; - /** - * 订单失效时间 YYYY-MM-DDTHH:mm:ss+TIMEZONE - */ - private String timeExpire; - /** - * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 - */ - private String attach; - /** - * 通知URL必须为直接可访问的URL,不允许携带查询串。 - */ - private String notifyUrl; - /** - * 订单优惠标记 - */ - private String goodsTag; - /** - * 支付金额 - */ - private Amount amount; - /** - * 支付者 JSAPI/小程序下单 必传 - */ - private Payer payer; - /** - * 优惠功能 - */ - private Detail detail; - /** - * 场景信息 - */ - private SceneInfo sceneInfo; - } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/TransactionConsumeData.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/TransactionConsumeData.java index a6dc8fe..a9fe1f7 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/TransactionConsumeData.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/TransactionConsumeData.java @@ -40,9 +40,29 @@ public class TransactionConsumeData { */ private Amount amount; /** - * 应用ID + * 直连模式应用ID,服务商模式请解析spAppid */ private String appid; + /** + * 直连模式商户号,服务商模式请解析spMchid + */ + private String mchid; + /** + * 服务商模式-服务商APPID + */ + private String spAppid; + /** + * 服务商模式-服务商户号 + */ + private String spMchid; + /** + * 服务商模式-子商户appid + */ + private String subAppid; + /** + * 服务商模式-子商户商户id + */ + private String subMchid; /** * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 */ @@ -51,10 +71,6 @@ public class TransactionConsumeData { * 银行类型,采用字符串类型的银行标识。银行标识请参考 《银行类型对照表》 */ private String bankType; - /** - * 商户号 - */ - private String mchid; /** * 商户订单号 */ diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/CloseTransParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/CloseTransParams.java new file mode 100644 index 0000000..6df229c --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/CloseTransParams.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2021 felord.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * Website: + * https://felord.cn + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.felord.payment.wechat.v3.model.partner; + +import lombok.Data; + +/** + * 服务商模式-关闭订单请求参数 + * + * @author felord.cn + * @since 1.0.8.RELEASE + */ +@Data +public class CloseTransParams { + /** + * 子商户号 + */ + private String subMchid; + /** + * 商户订单号 + */ + private String outTradeNo; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/PartnerPayParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/PartnerPayParams.java new file mode 100644 index 0000000..362e31f --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/partner/PartnerPayParams.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019-2021 felord.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * Website: + * https://felord.cn + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.felord.payment.wechat.v3.model.partner; + +import cn.felord.payment.wechat.v3.model.AbstractPayParams; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author felord.cn + * @since 1.0.8.RELEASE + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PartnerPayParams extends AbstractPayParams { + /** + * 服务商公众号ID + */ + private String spAppid; + /** + * 服务商户号 + */ + private String spMchid; + /** + * 子商户appid. + */ + private String subAppid; + /** + * 子商户号 + */ + private String subMchid; + +}