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 6131821..7017f02 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 @@ -109,12 +109,44 @@ public enum WechatPayV3Type { */ COMBINE_CLOSE(HttpMethod.POST, "%s/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close"), + /** + * 商户预授权API. + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_PERMISSIONS(HttpMethod.POST, "%s/v3/payscore/permissions"), + /** + * 查询与用户授权记录(授权协议号)API. + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_QUEERY_PERMISSIONS_AUTHORIZATION_CODE(HttpMethod.GET, "%s/v3/payscore/permissions/authorization-code/{authorization_code}"), + /** + * 解除用户授权关系(授权协议号)API. + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_TERMINATE_PERMISSIONS_AUTHORIZATION_CODE(HttpMethod.POST, "%s/v3/payscore/permissions/authorization-code/{authorization_code}/terminate"), + /** + * 查询与用户授权记录(openid)API. + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_PERMISSIONS_OPENID(HttpMethod.GET, "%s/v3/payscore/permissions/openid/{openid}"), + /** + * 解除用户授权关系(openid)API. + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_TERMINATE_PERMISSIONS_OPENID(HttpMethod.POST, "%s/v3/payscore/permissions/openid/{openid}/terminate"), /** * 查询用户授权状态API. * * @since 1.0.2.RELEASE */ PAY_SCORE_USER_SERVICE_STATE(HttpMethod.GET, "%s/v3/payscore/user-service-state?service_id={service_id}&appid={appid}&openid={openid}"), + + /** * 创建支付分订单API * @@ -133,6 +165,30 @@ public enum WechatPayV3Type { * @since 1.0.2.RELEASE */ PAY_SCORE_CANCEL_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/cancel"), + /** + * 修改订单金额API + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_MODIFY_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/modify"), + /** + * 完结支付分订单API + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_COMPLETE_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/complete"), + /** + * 商户发起催收扣款API + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_PAY_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/pay"), + /** + * 同步服务订单信息API + * + * @since 1.0.2.RELEASE + */ + PAY_SCORE_SYNC_USER_SERVICE_ORDER(HttpMethod.POST, "%s/v3/payscore/serviceorder/{out_order_no}/sync"), /** diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java index a3e9980..b69182e 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/SignatureProvider.java @@ -86,7 +86,7 @@ public class SignatureProvider { /** - * 我方请求时加签,使用API证书. + * 我方请求前用 SHA256withRSA 加签,使用API证书. * * @param tenantId the properties key * @param method the method @@ -95,7 +95,7 @@ public class SignatureProvider { * @return the string */ @SneakyThrows - public String requestSign(String tenantId,String method, String canonicalUrl, String body) { + public String requestSign(String tenantId, String method, String canonicalUrl, String body) { Signature signer = Signature.getInstance("SHA256withRSA"); WechatMetaBean wechatMetaBean = wechatMetaContainer.getWechatMeta(tenantId); signer.initSign(wechatMetaBean.getKeyPair().getPrivate()); @@ -160,7 +160,7 @@ public class SignatureProvider { } // 签名 HttpMethod httpMethod = WechatPayV3Type.CERT.method(); - String authorization = requestSign(tenantId,httpMethod.name(), canonicalUrl, ""); + String authorization = requestSign(tenantId, httpMethod.name(), canonicalUrl, ""); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); @@ -177,30 +177,27 @@ public class SignatureProvider { ArrayNode certificates = bodyObjectNode.withArray("data"); if (certificates.isArray() && certificates.size() > 0) { CERTIFICATE_MAP.clear(); - final CertificateFactory cf = CertificateFactory.getInstance("X509"); + final CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); certificates.forEach(objectNode -> { JsonNode encryptCertificate = objectNode.get("encrypt_certificate"); String associatedData = encryptCertificate.get("associated_data").asText(); String nonce = encryptCertificate.get("nonce").asText(); String ciphertext = encryptCertificate.get("ciphertext").asText(); - String publicKey = decryptResponseBody(tenantId,associatedData, nonce, ciphertext); + String publicKey = decryptResponseBody(tenantId, associatedData, nonce, ciphertext); ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8)); - Certificate certificate = null; + try { - certificate = cf.generateCertificate(inputStream); + Certificate certificate = certificateFactory.generateCertificate(inputStream); + String responseSerialNo = objectNode.get("serial_no").asText(); + CERTIFICATE_MAP.put(responseSerialNo, certificate); } catch (CertificateException e) { - e.printStackTrace(); + throw new PayException("An error occurred while generating the wechat v3 certificate, reason : " + e.getMessage()); } - String responseSerialNo = objectNode.get("serial_no").asText(); - CERTIFICATE_MAP.put(responseSerialNo, certificate); }); - } - } - /** * 解密响应体. * @@ -210,7 +207,7 @@ public class SignatureProvider { * @param ciphertext the ciphertext * @return the string */ - public String decryptResponseBody(String tenantId,String associatedData, String nonce, String ciphertext) { + public String decryptResponseBody(String tenantId, String associatedData, String nonce, String ciphertext) { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); String apiV3Key = wechatMetaContainer.getWechatMeta(tenantId).getV3().getAppV3Secret(); @@ -233,7 +230,6 @@ public class SignatureProvider { } } - /** * Wechat meta container. * @@ -249,10 +245,9 @@ public class SignatureProvider { * @param components the components * @return string string */ - private String createSign(String... components) { + private static String createSign(String... components) { return Arrays.stream(components) .collect(Collectors.joining("\n", "", "\n")); } - } 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 f38b7e1..72bc8dc 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 @@ -19,7 +19,7 @@ import java.util.function.Consumer; * 微信支付回调工具. *
* 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
- *
+ * TODO 微信支付分有三个回调通知
* @author felord.cn
* @since 1.0.0.RELEASE
*/
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayScoreApi.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayScoreApi.java
index ce3017b..c541967 100644
--- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayScoreApi.java
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayScoreApi.java
@@ -3,10 +3,7 @@ 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.payscore.CancelServiceOrderParams;
-import cn.felord.payment.wechat.v3.model.payscore.QueryServiceOrderParams;
-import cn.felord.payment.wechat.v3.model.payscore.UserPayScoreOrderParams;
-import cn.felord.payment.wechat.v3.model.payscore.UserServiceStateParams;
+import cn.felord.payment.wechat.v3.model.payscore.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -72,7 +69,7 @@ public class WechatPayScoreApi extends AbstractApi {
* @param params the params
* @return the wechat response entity
*/
- public WechatResponseEntity
+ * 完结订单总金额与实际金额不符时,可通过该接口修改订单金额。
+ * 例如:充电宝场景,由于机器计费问题导致商户完结订单时扣除用户99元,用户客诉成功后,商户需要按照实际的消费金额(如10元)扣费,当服务订单支付状态处于“待支付”时,商户可使用此能力修改订单金额。
+ *
+ * 注意:
+ * • 若此笔订单已收款成功,商户直接使用退款能力,将差价退回用户即可。
+ *
+ * • 修改次数>=1,第n次修改后金额 <第n-1次修改后金额
+ *
+ * @param params the params
+ * @return the wechat response entity
+ */
+ public WechatResponseEntity
+ * 前置条件:服务订单状态为“进行中”且订单状态说明需为[USER_CONFIRM:用户确认]
+ *
+ * 完结微信支付分订单。用户使用服务完成后,商户可通过此接口完结订单。
+ *
+ * 特别说明:
+ * • 完结接口调用成功后,微信支付将自动发起免密代扣。 若扣款失败,微信支付将自动再次发起免密代扣(按照一定频次),直到扣成功为止。
+ *
+ * @param params the params
+ * @return wechat response entity
+ */
+ public WechatResponseEntity
+ * 前置条件:服务订单支付状态处于“待支付”状态
+ *
+ * 当微信支付分订单支付状态处于“待支付”时,商户可使用该接口向用户发起收款。
+ *
+ * 注意:
+ * • 此能力不影响微信支付分代商户向用户发起收款的策略。
+ *
+ * @param params the params
+ * @return the wechat response entity
+ */
+ public WechatResponseEntity
+ * 前提条件:同步商户渠道收款成功信息时,即场景类型=“Order_Paid”,订单的状态需为[MCH_COMPLETE:商户完结订单]
+ *
+ * 由于收款商户进行的某些“线下操作”会导致微信支付侧的订单状态与实际情况不符。例如,用户通过线下付款的方式已经完成支付,而微信支付侧并未支付成功,此时可能导致用户重复支付。因此商户需要通过订单同步接口将订单状态同步给微信支付,修改订单在微信支付系统中的状态。
+ *
+ * @param params the params
+ * @return the wechat response entity
+ */
+ public WechatResponseEntity
+ * 商户系统内部服务订单号(不是交易单号),要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
+ */
+ private String outOrderNo;
+ /**
+ * 与传入的商户号建立了支付绑定关系的appid,必填
+ */
+ private String appid;
+ /**
+ * 服务ID,必填
+ *
+ * 该服务ID有本接口对应产品的权限。
+ */
+ private String serviceId;
+ /**
+ * 后付费项目,必填
+ */
+ private List
+ * 不能超过完结订单时候的总金额,只能为整数,详见 支付金额。此参数需满足:总金额 =(修改后付费项目1…+修改后完结付费项目n)-(修改 后付费商户优惠项目1…+修改后付费商户优惠项目n)
+ */
+ private Long totalAmount;
+ /**
+ * 服务时间段,条件选填
+ *
+ * 服务时间范围,创建订单未填写服务结束时间,则完结的时候,服务结束时间必填
+ * 如果传入,用户侧则显示此参数。
+ */
+ private TimeRange timeRange;
+ /**
+ * 服务位置,选填
+ */
+ private CompleteLocation location;
+ /**
+ * 微信支付服务分账标记,选填
+ *
+ * 完结订单分账接口标记。分账开通流程,详见 分账
+ * false:不分账,默认:false
+ * true:分账。
+ */
+ private Boolean profitSharing = Boolean.TRUE;
+ /**
+ * 订单优惠标记,选填
+ *
+ * 代金券或立减金优惠的参数,说明详见代金券或立减金优惠
+ */
+ private String goods_tag;
+
+
+ /**
+ * 服务位置信息
+ *
+ * 如果传入,用户侧则显示此参数。
+ *
+ * @author felord.cn
+ * @since 1.0.2.RELEASE
+ */
+ @Data
+ public static class CompleteLocation {
+
+ /**
+ * 预计服务结束地点,条件选填。
+ *
+ * 结束使用服务的地点,不超过50个字符,超出报错处理 。 创建订单传入了【服务开始地点】,此项才能填写
+ * 【建议】
+ * 1、预计结束地点为空时,实际结束地点与开始地点相同,不填写
+ * 2、预计结束地点不为空时,实际结束地点与预计结束地点相同,不填写
+ */
+ private String endLocation;
+ }
+}
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/ModifyServiceOrderParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/ModifyServiceOrderParams.java
new file mode 100644
index 0000000..f7b3816
--- /dev/null
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/ModifyServiceOrderParams.java
@@ -0,0 +1,53 @@
+
+package cn.felord.payment.wechat.v3.model.payscore;
+
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 修改微信支付分订单金额请求参数.
+ *
+ * @author felord.cn
+ * @since 1.0.2.RELEASE
+ */
+@Data
+public class ModifyServiceOrderParams {
+
+ /**
+ * 商户服务订单号,必填
+ *
+ * 商户系统内部服务订单号(不是交易单号),要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
+ */
+ private String outOrderNo;
+ /**
+ * 与传入的商户号建立了支付绑定关系的appid,必填
+ */
+ private String appid;
+ /**
+ * 服务ID,必填
+ *
+ * 该服务ID有本接口对应产品的权限。需要与创建订单时保持一致。
+ */
+ private String serviceId;
+ /**
+ * 后付费项目,必填
+ */
+ private List
+ * 商户系统内部服务订单号(不是交易单号),要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
+ */
+ private String outOrderNo;
+ /**
+ * 与传入的商户号建立了支付绑定关系的appid,必填
+ */
+ private String appid;
+ /**
+ * 服务ID,必填
+ *
+ * 该服务ID有本接口对应产品的权限。
+ */
+ private String serviceId;
+}
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostDiscount.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostDiscount.java
index 803deb0..7b253b4 100644
--- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostDiscount.java
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostDiscount.java
@@ -26,6 +26,11 @@ public class PostDiscount {
* 优惠使用条件说明。{@link PostDiscount#name}若填写,则必须同时填写。
*/
private String description;
+ /**
+ * 总金额,单位分,必填
+ * todo 新增没有此字段,修改必填,感觉不太符合常理
+ */
+ private Long amount;
/**
* 优惠数量,选填。
*
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostPayment.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostPayment.java
index 51502e0..ededb85 100644
--- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostPayment.java
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/PostPayment.java
@@ -14,13 +14,13 @@ import lombok.Data;
@Data
public class PostPayment {
/**
- * 付费项目名称,选填。
+ * 付费项目名称,选填。 修改订单必填
*
* 相同订单号下不能出现相同的付费项目名称,当参数长度超过20个字符时,报错处理。
*/
private String name;
/**
- * 金额,条件选填。
+ * 金额,条件选填。修改订单必填
*
* 此付费项目总金额,大于等于0,单位为分,等于0时代表不需要扣费,只能为整数,详见支付金额。如果填写了“付费项目名称”,则amount或description必须填写其一,或都填。
*/
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/RiskFund.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/RiskFund.java
index 746a624..31da2de 100644
--- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/RiskFund.java
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/RiskFund.java
@@ -22,7 +22,7 @@ public class RiskFund {
* 1、数字,必须>0(单位分)。
* 2、风险金额≤每个服务ID的风险金额上限。
* 3、当商户优惠字段为空时,付费项目总金额≤服务ID的风险金额上限 (未填写金额的付费项目,视为该付费项目金额为0)。
- * 4、完结金额可大于、小于或等于风险金额。详细可见QA 关于订单风险金额问题
+ * 4、完结金额可大于、小于或等于风险金额。详细可见QA 关于订单风险金额问题
*/
private Long amount;
/**
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/SyncServiceOrderParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/SyncServiceOrderParams.java
new file mode 100644
index 0000000..7764199
--- /dev/null
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/SyncServiceOrderParams.java
@@ -0,0 +1,64 @@
+package cn.felord.payment.wechat.v3.model.payscore;
+
+import lombok.Data;
+
+/**
+ * 同步服务订单信息请求参数.
+ *
+ * @author felord.cn
+ * @since 1.0.2.RELEASE
+ */
+@Data
+public class SyncServiceOrderParams {
+
+ /**
+ * 商户服务订单号,必填
+ *
+ * 商户系统内部服务订单号(不是交易单号),要求此参数只能由数字、大小写字母_-|*组成,且在同一个商户号下唯一。详见[商户订单号]。
+ */
+ private String outOrderNo;
+ /**
+ * 与传入的商户号建立了支付绑定关系的appid,必填
+ */
+ private String appid;
+ /**
+ * 服务ID,必填
+ *
+ * 该服务ID有本接口对应产品的权限。与订单要保持一致。
+ */
+ private String serviceId;
+ /**
+ * 场景类型,必填,场景类型为“Order_Paid”,字符串表示“订单收款成功” 。
+ */
+ private String type = "Order_Paid";
+
+ /**
+ * 内容信息详情,场景类型为Order_Paid时,为必填项。
+ */
+ private SyncDetail detail;
+
+
+ /**
+ * 内容信息详情
+ */
+ @Data
+ public static class SyncDetail{
+ /**
+ * 收款成功时间
+ *
+ * 支付成功时间,支持两种格式:yyyyMMddHHmmss和yyyyMMdd
+ * ● 传入20091225091010表示2009年12月25日9点10分10秒。
+ * ● 传入20091225默认认为时间为2009年12月25日0点0分0秒。
+ * 用户通过其他方式付款成功的实际时间需满足条件:服务开始时间<调用商户完结订单接口的时间<用户通过其他方式付款成功的实际时间≤商户调用支付分订单同步接口的时间。
+ * 【服务开始时间】
+ * 1、当完结订单有填写【实际服务开始时间】时,【服务开始时间】=完结订单【实际服务开始时间】。
+ * 2、当完结订单未填写【实际服务开始时间】时,【服务开始时间】=创建订单【服务开始时间】
+ * 场景类型为Order_Paid时,必填。
+ * 支持两种格式:yyyyMMddHHmmss和yyyyMMdd
+ * ● 传入20091225091010表示2009年12月25日9点10分10秒。
+ * ● 传入20091225表示时间为2009年12月25日23点59分59秒。
+ * 注意:微信支付分会根据此时间更新用户侧的守约记录、负面记录信息;因此请务必如实填写用户实际付款成功时间,以免造成不必要的客诉。
+ */
+ private String paidTime;
+ }
+}
diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserPayScoreOrderParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserServiceOrderParams.java
similarity index 98%
rename from payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserPayScoreOrderParams.java
rename to payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserServiceOrderParams.java
index dba2540..5b5e612 100644
--- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserPayScoreOrderParams.java
+++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/payscore/UserServiceOrderParams.java
@@ -13,7 +13,7 @@ import java.util.List;
* @since 1.0.2.RELEASE
*/
@Data
-public class UserPayScoreOrderParams {
+public class UserServiceOrderParams {
/**
* 商户服务订单号,必填