diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/StrategyType.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/StrategyType.java new file mode 100644 index 0000000..2aa3bbc --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/enumeration/StrategyType.java @@ -0,0 +1,18 @@ +package cn.felord.payment.wechat.enumeration; + +/** + * 目标完成类型、优惠使用类型 + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +public enum StrategyType { + /** + * 增加数量,表示用户发生了履约行为 + */ + INCREASE, + /** + * 减少数量,表示取消用户的履约行为(例如用户取消购买、退货退款等) + */ + DECREASE +} 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 00d940b..248e6e1 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 @@ -6,7 +6,7 @@ import cn.felord.payment.wechat.v3.model.CouponConsumeData; import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams; import cn.felord.payment.wechat.v3.model.TransactionConsumeData; import cn.felord.payment.wechat.v3.model.combine.CombineTransactionConsumeData; -import cn.felord.payment.wechat.v3.model.discountcard.DiscountCardAcceptedConsumeData; +import cn.felord.payment.wechat.v3.model.discountcard.*; 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; @@ -27,6 +27,8 @@ import java.util.function.Consumer; /** * 微信支付回调工具. *

+ * 注意:开发者应该保证回调调用的幂等性 + *

* 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。 * * @author felord.cn @@ -123,40 +125,34 @@ public class WechatPayCallback { } - /** - * 微信支付分确认订单回调通知. + * 微信支付分确认订单、支付成功回调通知. *

- * 该链接是通过商户[创建支付分订单]提交notify_url参数,必须为https协议。如果链接无法访问,商户将无法接收到微信通知。 通知url必须为直接可访问的url,不能携带参数。示例: “https://pay.weixin.qq.com/wxpay/pay.action” + * 该链接是通过商户 创建支付分订单 提交notify_url参数,必须为https协议。如果链接无法访问,商户将无法接收到微信通知。 通知url必须为直接可访问的url,不能携带参数。示例: “https://pay.weixin.qq.com/wxpay/pay.action” * - * @param params the params - * @param consumeDataConsumer the consume data consumer + * @param params the params + * @param payScoreConsumer the pay score consumer * @return the map * @since 1.0.2.RELEASE */ @SneakyThrows - public Map payscoreUserConfirmCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { - String data = this.callback(params, EventType.PAYSCORE_USER_CONFIRM); - PayScoreUserConfirmConsumeData consumeData = MAPPER.readValue(data, PayScoreUserConfirmConsumeData.class); - consumeDataConsumer.accept(consumeData); - return Collections.singletonMap("code", "SUCCESS"); - } + public Map payscoreUserOrderCallback(ResponseSignVerifyParams params, PayScoreConsumer payScoreConsumer) { + CallbackParams callbackParams = resolve(params); + String eventType = callbackParams.getEventType(); + + if (Objects.equals(eventType, EventType.PAYSCORE_USER_CONFIRM.event)) { + String data = this.decrypt(callbackParams); + PayScoreUserConfirmConsumeData confirmConsumeData = MAPPER.readValue(data, PayScoreUserConfirmConsumeData.class); + payScoreConsumer.getConfirmConsumeDataConsumer().accept(confirmConsumeData); + } else if (Objects.equals(eventType, EventType.PAYSCORE_USER_PAID.event)) { + String data = this.decrypt(callbackParams); + PayScoreUserPaidConsumeData paidConsumeData = MAPPER.readValue(data, PayScoreUserPaidConsumeData.class); + payScoreConsumer.getPaidConsumeDataConsumer().accept(paidConsumeData); + } else { + log.error("wechat pay event type is not matched, callbackParams {}", callbackParams); + throw new PayException(" wechat pay event type is not matched"); + } - /** - * 微信支付分支付成功回调通知API. - *

- * 请求URL:该链接是通过商户[创建支付分订单]提交notify_url参数,必须为https协议。如果链接无法访问,商户将无法接收到微信通知。 通知url必须为直接可访问的url,不能携带参数。示例: “https://pay.weixin.qq.com/wxpay/pay.action” - * - * @param params the params - * @param consumeDataConsumer the consume data consumer - * @return the map - * @since 1.0.2.RELEASE - */ - @SneakyThrows - public Map payscoreUserPaidCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { - String data = this.callback(params, EventType.PAYSCORE_USER_PAID); - PayScoreUserPaidConsumeData consumeData = MAPPER.readValue(data, PayScoreUserPaidConsumeData.class); - consumeDataConsumer.accept(consumeData); return Collections.singletonMap("code", "SUCCESS"); } @@ -192,20 +188,42 @@ public class WechatPayCallback { } /** - * Discount card user accepted callback map. + * 用户领卡、守约状态变化、扣费状态变化通知API + *

+ * 用户领取优惠卡后或者用户守约状态发生变更后或扣费状态变化后,微信会把对应信息发送给商户。 + *

+ * 该链接是通过商户预受理领卡请求中提交notify_url参数,必须为https协议。如果链接无法访问,商户将无法接收到微信通知。 通知url必须为直接可访问的url,不能携带参数。示例: “https://pay.weixin.qq.com/wxpay/pay.action” * - * @param params the params - * @param consumeDataConsumer the consume data consumer + * @param params the params + * @param discountCardConsumer the discount card consumer * @return the map */ @SneakyThrows - public Map discountCardUserAcceptedCallback(ResponseSignVerifyParams params, Consumer consumeDataConsumer) { - String data = this.callback(params, EventType.DISCOUNT_CARD_USER_ACCEPTED); - DiscountCardAcceptedConsumeData consumeData = MAPPER.readValue(data, DiscountCardAcceptedConsumeData.class); - consumeDataConsumer.accept(consumeData); + public Map discountCardCallback(ResponseSignVerifyParams params, DiscountCardConsumer discountCardConsumer) { + + CallbackParams callbackParams = resolve(params); + String eventType = callbackParams.getEventType(); + + if (Objects.equals(eventType, EventType.DISCOUNT_CARD_AGREEMENT_ENDED.event)) { + String data = this.decrypt(callbackParams); + DiscountCardAgreementEndConsumeData agreementEndConsumeData = MAPPER.readValue(data, DiscountCardAgreementEndConsumeData.class); + discountCardConsumer.getAgreementEndConsumeDataConsumer().accept(agreementEndConsumeData); + } else if (Objects.equals(eventType, EventType.DISCOUNT_CARD_USER_ACCEPTED.event)) { + String data = this.decrypt(callbackParams); + DiscountCardAcceptedConsumeData acceptedConsumeData = MAPPER.readValue(data, DiscountCardAcceptedConsumeData.class); + discountCardConsumer.getAcceptedConsumeDataConsumer().accept(acceptedConsumeData); + } else if (Objects.equals(eventType, EventType.DISCOUNT_CARD_USER_PAID.event)) { + String data = this.decrypt(callbackParams); + DiscountCardUserPaidConsumeData paidConsumeData = MAPPER.readValue(data, DiscountCardUserPaidConsumeData.class); + discountCardConsumer.getCardUserPaidConsumeDataConsumer().accept(paidConsumeData); + } else { + 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"); } + /** * Callback. * @@ -301,14 +319,14 @@ public class WechatPayCallback { DISCOUNT_CARD_USER_ACCEPTED("DISCOUNT_CARD.USER_ACCEPTED"), /** - * TODO 微信先享卡守约状态变化事件. + * 微信先享卡守约状态变化事件. * * @since 1.0.2.RELEASE */ DISCOUNT_CARD_AGREEMENT_ENDED("DISCOUNT_CARD.AGREEMENT_ENDED"), /** - * TODO 微信先享卡扣费状态变化事件. + * 微信先享卡扣费状态变化事件. * * @since 1.0.2.RELEASE */ diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java index 6b1b930..200dc75 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/WechatPayClient.java @@ -181,8 +181,7 @@ public class WechatPayClient { Assert.notNull(httpMethod, "httpMethod is required"); HttpHeaders headers = requestEntity.getHeaders(); - T entityBody = requestEntity.getBody(); - String body = requestEntity.hasBody() ? Objects.requireNonNull(entityBody).toString() : ""; + String body = requestEntity.hasBody() ? Objects.requireNonNull(requestEntity.getBody()).toString() : ""; if (WechatPayV3Type.MARKETING_IMAGE_UPLOAD.pattern().contains(canonicalUrl)) { body = Objects.requireNonNull(headers.get("Meta-Info")).get(0); } @@ -193,7 +192,7 @@ public class WechatPayClient { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.addAll(headers); httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - // 兼容图片上传,自定义优先级最高 + // for upload if (Objects.isNull(httpHeaders.getContentType())) { httpHeaders.setContentType(MediaType.APPLICATION_JSON); } @@ -218,13 +217,13 @@ public class WechatPayClient { HttpHeaders headers = responseEntity.getHeaders(); ObjectNode body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); + // 微信请求id + String requestId = headers.getFirst("Request-ID"); if (!statusCode.is2xxSuccessful()) { - throw new PayException("wechat pay server error,statusCode " + statusCode + ",result : " + body); + throw new PayException("wechat pay server error, Request-ID "+requestId+" , statusCode " + statusCode + ",result : " + body); } ResponseSignVerifyParams params = new ResponseSignVerifyParams(); - // 微信请求回调id - // String RequestId = response.header("Request-ID"); // 微信平台证书序列号 用来取微信平台证书 params.setWechatpaySerial(headers.getFirst("Wechatpay-Serial")); //获取应答签名 @@ -244,7 +243,7 @@ public class WechatPayClient { responseConsumer.accept(responseEntity); } } else { - throw new PayException("wechat pay signature failed"); + throw new PayException("wechat pay signature failed, Request-ID "+requestId ); } } @@ -261,11 +260,13 @@ public class WechatPayClient { String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); + // 微信请求id + String requestId = requestEntity.getHeaders().getFirst("Request-ID"); if (!statusCode.is2xxSuccessful()) { - throw new PayException("wechat pay server error,statusCode " + statusCode + ",result : " + body); + throw new PayException("wechat pay server error, Request-ID "+requestId+" , statusCode " + statusCode + ",result : " + body); } if (Objects.isNull(body)) { - throw new PayException("cant obtain wechat response body"); + throw new PayException("cant obtain wechat response body, Request-ID "+requestId); } return body; } diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardAgreementEndConsumeData.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardAgreementEndConsumeData.java new file mode 100644 index 0000000..7e47eea --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardAgreementEndConsumeData.java @@ -0,0 +1,165 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import lombok.Data; + +import java.util.List; + +/** + * 微信支付先享卡用户守约状态变化通知解密 + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class DiscountCardAgreementEndConsumeData { + /** + * The Appid. + */ + private String appid; + /** + * The Card id. + */ + private String cardId; + /** + * The Card template id. + */ + private String cardTemplateId; + /** + * The Create time. + */ + private String createTime; + /** + * The Mchid. + */ + private String mchid; + /** + * The Objectives. + */ + private List objectives; + /** + * The Openid. + */ + private String openid; + /** + * The Out card code. + */ + private String outCardCode; + /** + * The Rewards. + */ + private List rewards; + /** + * The State. + */ + private String state; + /** + * The Time range. + */ + private TimeRange timeRange; + /** + * The Total amount. + */ + private Long totalAmount; + /** + * The Unfinished reason. + */ + private String unfinishedReason; + + /** + * The type Objective. + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ + @Data + public static class Objective { + + /** + * The Count. + */ + private Long count; + /** + * The Description. + */ + private String description; + /** + * The Name. + */ + private String name; + /** + * The Objective completion records. + */ + private List objectiveCompletionRecords; + /** + * The Objective id. + */ + private String objectiveId; + /** + * The Unit. + */ + private String unit; + + } + + /** + * The type Time range. + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ + @Data + public static class TimeRange { + /** + * The Betin time. + */ + private String betinTime; + /** + * The End time. + */ + private String endTime; + } + + /** + * The type Reward. + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ + @Data + public static class Reward { + + /** + * The Amount. + */ + private Long amount; + /** + * The Count. + */ + private Long count; + /** + * The Count type. + */ + private String countType; + /** + * The Description. + */ + private String description; + /** + * The Name. + */ + private String name; + /** + * The Reward id. + */ + private String rewardId; + /** + * The Reward usage records. + */ + private List rewardUsageRecords; + /** + * The Unit. + */ + private String unit; + + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardConsumer.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardConsumer.java new file mode 100644 index 0000000..0544e16 --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardConsumer.java @@ -0,0 +1,27 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import lombok.Data; + +import java.util.function.Consumer; + +/** + * 先享卡回调消费复合消费器 + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class DiscountCardConsumer { + /** + * The Accepted consume data consumer. + */ + private Consumer acceptedConsumeDataConsumer; + /** + * The Agreement end consume data consumer. + */ + private Consumer agreementEndConsumeDataConsumer; + /** + * The Card user paid consume data consumer. + */ + private Consumer cardUserPaidConsumeDataConsumer; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardUserPaidConsumeData.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardUserPaidConsumeData.java new file mode 100644 index 0000000..f5b916a --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/DiscountCardUserPaidConsumeData.java @@ -0,0 +1,79 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import lombok.Data; + +/** + * 先享卡扣费状态变化通知解密. + * + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class DiscountCardUserPaidConsumeData { + + /** + * The Appid. + */ + private String appid; + /** + * The Card id. + */ + private String cardId; + /** + * The Card template id. + */ + private String cardTemplateId; + /** + * The Mchid. + */ + private String mchid; + /** + * The Openid. + */ + private String openid; + /** + * The Out card code. + */ + private String outCardCode; + /** + * The Pay information. + */ + private PayInformation payInformation; + /** + * The State. + */ + private String state; + /** + * The Total amount. + */ + private Long totalAmount; + /** + * The Unfinished reason. + */ + private String unfinishedReason; + + /** + * The type Pay information. + */ + @Data + public static class PayInformation { + + /** + * The Pay amount. + */ + private Long payAmount; + /** + * The Pay state. + */ + private String payState; + /** + * The Pay time. + */ + private String payTime; + /** + * The Transaction id. + */ + private String transactionId; + } +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/ObjectiveCompletionRecord.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/ObjectiveCompletionRecord.java new file mode 100644 index 0000000..7d93a5c --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/ObjectiveCompletionRecord.java @@ -0,0 +1,44 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import cn.felord.payment.wechat.enumeration.StrategyType; +import lombok.Data; + +/** + * The type Objective completion record. + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class ObjectiveCompletionRecord { + + /** + * The Completion count. + */ + private Long completionCount; + /** + * The Completion time. + */ + private String completionTime; + /** + * The Completion type. + */ + private StrategyType completionType; + /** + * The Description. + */ + private String description; + /** + * The Objective completion serial no. + */ + private String objectiveCompletionSerialNo; + /** + * The Objective id. + */ + private String objectiveId; + /** + * The Remark. + */ + private String remark; + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/PayScoreConsumer.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/PayScoreConsumer.java new file mode 100644 index 0000000..185551e --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/PayScoreConsumer.java @@ -0,0 +1,25 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserConfirmConsumeData; +import cn.felord.payment.wechat.v3.model.payscore.PayScoreUserPaidConsumeData; +import lombok.Data; + +import java.util.function.Consumer; + +/** + * 支付分回调复合消费器 + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class PayScoreConsumer { + /** + * The Confirm consume data consumer. + */ + private Consumer confirmConsumeDataConsumer; + /** + * The Paid consume data consumer. + */ + private Consumer paidConsumeDataConsumer; +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/RewardUsageRecord.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/RewardUsageRecord.java new file mode 100644 index 0000000..7344a2a --- /dev/null +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/RewardUsageRecord.java @@ -0,0 +1,48 @@ +package cn.felord.payment.wechat.v3.model.discountcard; + +import cn.felord.payment.wechat.enumeration.StrategyType; +import lombok.Data; + +/** + * The type Reward usage record. + * + * @author felord.cn + * @since 1.0.2.RELEASE + */ +@Data +public class RewardUsageRecord { + + /** + * The Amount. + */ + private Long amount; + /** + * The Description. + */ + private String description; + /** + * The Remark. + */ + private String remark; + /** + * The Reward id. + */ + private String rewardId; + /** + * The Reward usage serial no. + */ + private String rewardUsageSerialNo; + /** + * The Usage count. + */ + private Long usageCount; + /** + * The Usage time. + */ + private String usageTime; + /** + * The Usage type. + */ + private StrategyType usageType; + +} diff --git a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/UserRecordsParams.java b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/UserRecordsParams.java index c4750d7..a57df12 100644 --- a/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/UserRecordsParams.java +++ b/payment-spring-boot-autoconfigure/src/main/java/cn/felord/payment/wechat/v3/model/discountcard/UserRecordsParams.java @@ -31,104 +31,4 @@ public class UserRecordsParams { */ private List rewardUsageRecords; - /** - * The type Objective completion record. - * - * @author felord.cn - * @since 1.0.2.RELEASE - */ - @Data - public static class ObjectiveCompletionRecord { - - /** - * The Completion count. - */ - private Long completionCount; - /** - * The Completion time. - */ - private String completionTime; - /** - * The Completion type. - */ - private StrategyType completionType; - /** - * The Description. - */ - private String description; - /** - * The Objective completion serial no. - */ - private String objectiveCompletionSerialNo; - /** - * The Objective id. - */ - private String objectiveId; - /** - * The Remark. - */ - private String remark; - - } - - /** - * The type Reward usage record. - * - * @author felord.cn - * @since 1.0.2.RELEASE - */ - @Data - public static class RewardUsageRecord { - - /** - * The Amount. - */ - private Long amount; - /** - * The Description. - */ - private String description; - /** - * The Remark. - */ - private String remark; - /** - * The Reward id. - */ - private String rewardId; - /** - * The Reward usage serial no. - */ - private String rewardUsageSerialNo; - /** - * The Usage count. - */ - private Long usageCount; - /** - * The Usage time. - */ - private String usageTime; - /** - * The Usage type. - */ - private StrategyType usageType; - - } - - /** - * 目标完成类型、优惠使用类型 - * - * @author felord.cn - * @since 1.0.2.RELEASE - */ - public enum StrategyType { - /** - * 增加数量,表示用户发生了履约行为 - */ - INCREASE, - /** - * 减少数量,表示取消用户的履约行为(例如用户取消购买、退货退款等) - */ - DECREASE - } }