bihs 1 month ago
parent
commit
392172d192
25 changed files with 1165 additions and 130 deletions
  1. 4 0
      .vscode/settings.json
  2. 36 14
      qmjszx-admin/src/main/resources/application.yml
  3. BIN
      qmjszx-admin/src/main/resources/cert/apiclient_cert.p12
  4. 25 0
      qmjszx-admin/src/main/resources/cert/apiclient_cert.pem
  5. 28 0
      qmjszx-admin/src/main/resources/cert/apiclient_key.pem
  6. 9 0
      qmjszx-admin/src/main/resources/cert/pub_key.pem
  7. 7 1
      qmjszx-business/src/main/java/beilv/order/domain/StoreOrder.java
  8. 31 0
      qmjszx-business/src/main/java/beilv/order/domain/StoreRefund.java
  9. 9 1
      qmjszx-business/src/main/java/beilv/order/mapper/StoreOrderMapper.java
  10. 69 0
      qmjszx-business/src/main/java/beilv/order/mapper/StoreRefundMapper.java
  11. 24 2
      qmjszx-business/src/main/resources/mapper/order/StoreOrderMapper.xml
  12. 141 0
      qmjszx-business/src/main/resources/mapper/order/StoreRefundMapper.xml
  13. 4 0
      qmjszx-framework/src/main/java/beilv/framework/config/ShiroConfig.java
  14. 8 4
      qmjszx-pay/pom.xml
  15. 50 0
      qmjszx-pay/src/main/java/beilv/wx/pay/config/WxPayConfiguration.java
  16. 92 0
      qmjszx-pay/src/main/java/beilv/wx/pay/config/WxPayProperties.java
  17. 95 0
      qmjszx-pay/src/main/java/beilv/wx/pay/controller/WxPayController.java
  18. 43 0
      qmjszx-pay/src/main/java/beilv/wx/pay/domain/vo/AppPayParam.java
  19. 31 0
      qmjszx-pay/src/main/java/beilv/wx/pay/domain/vo/AppRefundParam.java
  20. 10 0
      qmjszx-pay/src/main/java/beilv/wx/pay/enums/ErrorCodeConstants.java
  21. 27 0
      qmjszx-pay/src/main/java/beilv/wx/pay/enums/OrderTypeEnum.java
  22. 22 0
      qmjszx-pay/src/main/java/beilv/wx/pay/enums/PayOrderConstants.java
  23. 36 0
      qmjszx-pay/src/main/java/beilv/wx/pay/enums/PayTypeEnum.java
  24. 355 0
      qmjszx-pay/src/main/java/beilv/wx/pay/service/IWxPayService.java
  25. 9 108
      qmjszx-system/src/main/java/beilv/system/domain/SysMember.java

+ 4 - 0
.vscode/settings.json

@@ -0,0 +1,4 @@
+{
+    "java.compile.nullAnalysis.mode": "automatic",
+    "java.configuration.updateBuildConfiguration": "interactive"
+}

+ 36 - 14
qmjszx-admin/src/main/resources/application.yml

@@ -30,7 +30,7 @@ server:
       max: 800
       # Tomcat启动初始化的线程数,默认值10
       min-spare: 100
- 
+
 # 日志配置
 logging:
   level:
@@ -148,26 +148,48 @@ rsa:
   privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAh2ZN5B1TJgrZrmK89d1gCs6AMwMq7qhvtg+VoLpHX9fGMeEZpIi5xgavM74mEIU2SGAfGZzg86nIWr48V0u40wIDAQABAkBbgNXu7ap9sSN/aJcPCXaYlwmob+GZvBcS0OFr57fImsxTEzWg/OJWxcbHk7Li31AbHjwqQmVtUxk2wQ6GawthAiEAve5+CCaUHV3BIH7LhzU7MsPAr6IIx25t1NkNNg+dn3kCIQC2f7XtdTOVRtPUwO7QhwC0fX/wJu53pR01p55tqmgLqwIgYzUHr8o243/tOMQCG4W6fjGxnAvO+hy8UcluFSbi9kECIQCAK4NOyPA4V6zwD6vpgdcJ69YNiJoUJz8zboxCwtodzwIgNUN6uuiVJuZmBJDm+9AIY7Ury+ajGRZJ7l1FIBbxMPY=
 
 # 微信支付配置
+#wx:
+#  pay:
+#    # 小程序或公众号的appid(必需)
+#    appId: wxf02cdfd83ef8ce4d
+#    # 微信支付商户号(必需)
+#    mchId: 1730382253
+#    # APIv3密钥(必需)
+#    apiv3Key: TulAW0rXQj82sx8CEaO0pbUnTXZqWRKP
+#    # 小程序密钥
+#    appSecret: 228a6905484fbf9c3e571962c92c7e7e
+#    # 证书序列号(必需)
+#    certSerialNo: 55AFB8526ECF0CECE18137B49703C20B237744A5
+#    # 私钥文件路径(必需)
+#    privateKeyPath: classpath:cert/apiclient_key.pem
+#    # 商户证书文件路径(必需)
+#    privateCertPath: classpath:cert/apiclient_cert.pem
+#    # 异步通知地址(必需)
+#    notifyUrl: http://k99464d6.natappfree.cc:80/app/pay/notify
+#    # 退款异步通知地址
+#    refundNotifyUrl: http://k99464d6.natappfree.cc/app/pay/refund/notify
+#    #证书路径
+#    certurl: classpath:cert/apiclient_cert.p12
+
 wx:
   pay:
-    # 小程序或公众号的appid(必需)
     appId: wxf02cdfd83ef8ce4d
-    # 微信支付商户号(必需)
     mchId: 1730382253
-    # APIv3密钥(必需)
-    apiv3Key: TulAW0rXQj82sx8CEaO0pbUnTXZqWRKP
-    # 小程序密钥
-    appSecret: 228a6905484fbf9c3e571962c92c7e7e
-    # 证书序列号(必需)
+    mchKey: TulAW0rXQj82sx8CEaO0pbUnTXZqWRKP #微信支付商户密钥
+    subAppId: #服务商模式下的子商户公众账号ID
+    subMchId: #服务商模式下的子商户号
+    keyPath: classpath:cert/apiclient_cert.p12
+    apiV3Key: TulAW0rXQj82sx8CEaO0pbUnTXZqWRKP
     certSerialNo: 55AFB8526ECF0CECE18137B49703C20B237744A5
-    # 私钥文件路径(必需)
-    privateKeyPath: classpath:cert/apiclient_key.pem
-    # 商户证书文件路径(必需)
     privateCertPath: classpath:cert/apiclient_cert.pem
+    privateKeyPath: classpath:cert/apiclient_key.pem
+    publicKeyPath: classpath:cert/pub_key.pem
+    publicKeyId: PUB_KEY_ID_0117303822532025111100191793000600
+    appSecret: 228a6905484fbf9c3e571962c92c7e7e
     # 异步通知地址(必需)
-    notifyUrl: http://116.142.80.10:80/api/pay/notify
-    #证书路径
-    certurl: classpath:cert/apiclient_cert.p12
+    notifyUrl: http://k99464d6.natappfree.cc:80/app/pay/notify
+    # 退款异步通知地址
+    refundNotifyUrl: http://k99464d6.natappfree.cc/app/notify/refund
 # 海康获取直播视频流配置
 hik:
   appKey: "28356728"

BIN
qmjszx-admin/src/main/resources/cert/apiclient_cert.p12


+ 25 - 0
qmjszx-admin/src/main/resources/cert/apiclient_cert.pem

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIELjCCAxagAwIBAgIUVa+4Um7PDOzhgTe0lwPCCyN3RKUwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUxMTExMDU1NTM3WhcNMzAxMTEwMDU1NTM3WjCBhzETMBEGA1UEAwwK
+MTczMDM4MjI1MzEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQL
+DCrlkInmnpfljJflm73po47lhYnml4XmuLjlvIDlj5HmnInpmZDlhazlj7gxCzAJ
+BgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBANLQYNwk20iJ5K6BLhTVnJP/P0JHkFTC/9F2DHLMkBDs3Ol6
+RcU7rlr+QbFEosjO2nrDpLZ4FwfSy+YwCQfMgR8y6lfw4qLrkml4x6HhvciUBdRK
+R8JgwCSFP02N4ILkxZGVLoRGCn4GY6vWsqazmnSXraPq5+c3EvoyWvTXx8cHOhsp
+ta1xzeCcOSnDWFbUT6GEN4FusJC15BVj+LQYcZdNFUV7X+VD1tCar4lC0RuLQ4gP
+VioalVHPitr3Fvj+kLP8cNqKhfhQDGu6Xkxo6ucK2vbDtCR5i6pLcut2fvh68+a2
+KntP4fd/GpDUAiJygQVWL2Q0/PfupkNpT9RNlpECAwEAAaOBuTCBtjAJBgNVHRME
+AjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDov
+L2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUw
+REJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJF
+MTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IB
+AQCJMNrs9dJXN3ZsjH2WOmA4vBu5CuUa0nwQbWqvXSMasFhASkup8ULLNXRHIbu+
+husHOgP/uWcO3wmNhGNobQ43SSIYb+SVEukAqWqhHzo6C6zbTkJUjFzXy5gYo66v
+vQSGlD01Zdnu80u1I4Xy9wFYKsIbfIm3U8YKgSBCBGqk5+Cgk7H8AIVnTUTBpglS
+11+VpVweWKV92fLaOrF2yBMqbPdniA2k7n6AVdEax0sJdvalhm8tv8Q+nMm6mFg9
+ymClZT4za+gPsJdUgWHDMRMJ7SKTP5a6+GQR52Q/aEBPExnbUEoul4QebP03kVCt
+ZJHA75UmxMd3FN6LoSyEwuuJ
+-----END CERTIFICATE-----

+ 28 - 0
qmjszx-admin/src/main/resources/cert/apiclient_key.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDS0GDcJNtIieSu
+gS4U1ZyT/z9CR5BUwv/RdgxyzJAQ7NzpekXFO65a/kGxRKLIztp6w6S2eBcH0svm
+MAkHzIEfMupX8OKi65JpeMeh4b3IlAXUSkfCYMAkhT9NjeCC5MWRlS6ERgp+BmOr
+1rKms5p0l62j6ufnNxL6Mlr018fHBzobKbWtcc3gnDkpw1hW1E+hhDeBbrCQteQV
+Y/i0GHGXTRVFe1/lQ9bQmq+JQtEbi0OID1YqGpVRz4ra9xb4/pCz/HDaioX4UAxr
+ul5MaOrnCtr2w7QkeYuqS3Lrdn74evPmtip7T+H3fxqQ1AIicoEFVi9kNPz37qZD
+aU/UTZaRAgMBAAECggEBAJBod4E1qhSViNXU3Zkd/oUOuumPFhpJ/W0hXUOiRymg
+527z+Ck0SapBMwTYTF4b/GNN812fA7+zMUM8MmNqPiT9/rNA6i7KCeBAARguB5AH
+fbq/K0rGdbhOol9gyaNATOn99ABd7kAH5mzGxFgW/b3STPrVJggnnNNQfSftTAdT
++FawZLgSlIrrtN7datX6Hf4qc/3XnQEOAmQSiobqCEc2G/0Dmfhv2hXnuGVbKEMj
+Z5eg3K59XlUTz0V2olA+mP9l2anN1o5gq3qTAxlGejJ022BvDSS9WXzfgz7AAucB
+JncItCOBAxpo4tV3mrSE6YQjaBWqsgHAj7LHfLrgadUCgYEA9F9WY+dLId/4JEVO
+sssnkRagSP6aclKCECoabcBIJxqwcZQIX09gEn/8kTtvGmUHt0t/CGrZNiv0p2UW
++iLB6ujSfCOfaYhdnQ8YMSjyIvxdvRCWrfECMV69XY2V7vi/UI0bHILXP1+86e6h
+vDNgFmuIMoo3YZ3aeO8B7jnHUKcCgYEA3NhFU35WoyqjV2trZ5p2JL7zf5uwNFzO
+srOtaXttEq5kXF83cLfdrHUOscNfxhLbAf3v0toEQ+ecrD35kdQ3/65rMQruAYDe
+3ufyfFr0+TEiqBcxEdXRW94kY05nraPjl6uck34CovjzffCNHLi5XeU8sCRLeE1U
+oo2QacXszgcCgYAH9BZmt/9tAdIctBjEnvIRuc/LsGWsdN0A0636hniStT6q46uG
+FppE39+DhpCuJj2jDJT83CVnqvSSgirGR11SXPOE0M/+ak7JrtUVvIRs+RO/9ItC
+7AYrPy8gnVwU5AmuAHmyatvAl0gZwQeIjY+CC0vqgS8eQTn8F6NTcpP3HwKBgQCh
+9vrhAQcJI3wn4OAkW2PKD0EFlWjk/iuZY60KbzrPOJbiJ/LA+BfbIv5j4KWc3rc/
+/rykJsB6DKMar1kZWkq3eXOjxCBJefn6AKIWEZ8YIrEIFfom2mlwtrp5GkdvUYLY
++UMb6I6Gd44cDw9uwQsxka2wgO3YjN5FWQ1QhZfmgQKBgHYSa+zQzLKnBaByIsjX
+F0N8Fjrb2hKWNraYzT9XSqpFFWW0vPXGJFfJP+ts3GxmdbrJ2fXVIyqsIxMD3c9i
+psOlvg4mDmxaBmQD5ILLbDdNSoLU0TfTKZCJHjBH1bswIwmyO6fE7h/Y8h17p54N
+OUkeAl0DlTzykhMLFMW3m9+T
+-----END PRIVATE KEY-----

+ 9 - 0
qmjszx-admin/src/main/resources/cert/pub_key.pem

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzJ0yNQrOHoWmSTAUFB8z
+DgX8NoNcErq/ZVu/VEblR2ZmRyD7DbTyi1sKUVzDjBsvjQco2QVag1cd2aV552bA
+8jiV5va4QSTANYgUd5UbbKZEBTsK2d+TfEic6zIbtNqfaIzJMg42JrJ4OFi0S+Sm
+it0PXVwqRv5ddUNa4OyQ80B8FWoBMr0I7gS6id36DCbQ8whnZ7ILU48cENFQwWwO
+F1+A9Y/CU86cbTbUe57Q44bL1Qt+gGZOEUjEk/ItTWJA67YfPs6zr6GU5ftHcL7h
+E6VSPnc3lL9fyrukJIe08CRrslNsLHlBlSh+c6mQhHckqPhfyxEi3h3SmzYrqLt7
+SQIDAQAB
+-----END PUBLIC KEY-----

+ 7 - 1
qmjszx-business/src/main/java/beilv/order/domain/StoreOrder.java

@@ -32,6 +32,12 @@ public class StoreOrder extends BaseEntity {
     private String orderId;
 
     /**
+     * 微信支付订单号
+     */
+    @Excel(name = "微信支付订单号")
+    private String transactionId;
+
+    /**
      * 订单类型
      */
     @Excel(name = "订单类型")
@@ -170,4 +176,4 @@ public class StoreOrder extends BaseEntity {
     @Excel(name = "支付id")
     private String payId;
 
-}
+}

+ 31 - 0
qmjszx-business/src/main/java/beilv/order/domain/StoreRefund.java

@@ -0,0 +1,31 @@
+package beilv.order.domain;
+
+import beilv.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class StoreRefund extends BaseEntity {
+    private Long id;
+
+    private Long uid;      // 用户id
+
+    private String refundNo;      // 商户退款单号 (out_refund_no)
+
+    private String orderId;       // 对应订单号
+
+    private String transactionId; // 微信支付单号
+
+    private BigDecimal refundAmount; // 本次退款金额(元)
+
+    private String refundReason;  // 退款原因
+
+    private String refundStatus;  // PENDING / SUCCESS / FAILED
+
+    private String wxRefundId;    // 微信退款单号 (refund_id)
+
+    private Date successTime;  // 退款成功时间
+}
+

+ 9 - 1
qmjszx-business/src/main/java/beilv/order/mapper/StoreOrderMapper.java

@@ -20,6 +20,14 @@ public interface StoreOrderMapper {
     public StoreOrder selectStoreOrderById(Long id);
 
     /**
+     * 根据订单号查询订单
+     *
+     * @param orderId 订单号
+     * @return 订单
+     */
+    public StoreOrder selectStoreOrderByOrderId(String orderId);
+
+    /**
      * 查询订单列表
      *
      * @param storeOrder 订单
@@ -58,4 +66,4 @@ public interface StoreOrderMapper {
      * @return 结果
      */
     public int deleteStoreOrderByIds(String[] ids);
-}
+}

+ 69 - 0
qmjszx-business/src/main/java/beilv/order/mapper/StoreRefundMapper.java

@@ -0,0 +1,69 @@
+package beilv.order.mapper;
+
+import beilv.order.domain.StoreRefund;
+
+import java.util.List;
+
+/**
+ * 退款Mapper接口
+ *
+ * @author ruoyi
+ * @date 2025-11-13
+ */
+public interface StoreRefundMapper {
+    /**
+     * 查询退款
+     *
+     * @param id 退款主键
+     * @return 退款
+     */
+    public StoreRefund selectStoreRefundById(Long id);
+
+    /**
+     * 根据退款单号查询退款
+     *
+     * @param orderId 退款单号
+     * @return 退款
+     */
+    public StoreRefund selectStoreRefundByOrderId(String orderId);
+
+    /**
+     * 查询退款列表
+     *
+     * @param storeRefund 退款
+     * @return 退款集合
+     */
+    public List<StoreRefund> selectStoreRefundList(StoreRefund storeRefund);
+
+    /**
+     * 新增退款
+     *
+     * @param storeRefund 退款
+     * @return 结果
+     */
+    public int insertStoreRefund(StoreRefund storeRefund);
+
+    /**
+     * 修改退款
+     *
+     * @param storeRefund 退款
+     * @return 结果
+     */
+    public int updateStoreRefund(StoreRefund storeRefund);
+
+    /**
+     * 删除退款
+     *
+     * @param id 退款主键
+     * @return 结果
+     */
+    public int deleteStoreRefundById(Long id);
+
+    /**
+     * 批量删除退款
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteStoreRefundByIds(String[] ids);
+}

+ 24 - 2
qmjszx-business/src/main/resources/mapper/order/StoreOrderMapper.xml

@@ -7,7 +7,7 @@
     <resultMap type="StoreOrder" id="StoreOrderResult">
         <result property="id" column="id"/>
         <result property="orderId" column="order_id"/>
-        <result property="orderType" column="order_type"/>
+        <result property="transactionId" column="transaction_id"/>
         <result property="uid" column="uid"/>
         <result property="realName" column="real_name"/>
         <result property="userPhone" column="user_phone"/>
@@ -23,6 +23,7 @@
         <result property="paid" column="paid"/>
         <result property="payTime" column="pay_time"/>
         <result property="payType" column="pay_type"/>
+        <result property="orderType" column="order_type"/>
         <result property="status" column="status"/>
         <result property="refundStatus" column="refund_status"/>
         <result property="useIntegral" column="use_integral"/>
@@ -31,6 +32,7 @@
         <result property="createTime" column="create_time"/>
         <result property="updateBy" column="update_by"/>
         <result property="updateTime" column="update_time"/>
+        <result property="payId" column="pay_id"/>
         <result property="remark" column="remark"/>
     </resultMap>
 
@@ -69,7 +71,16 @@
         <include refid="selectStoreOrderVo"/>
         <where>
             <if test="orderId != null  and orderId != ''">and order_id = #{orderId}</if>
+            <if test="uid != null ">and uid = #{uid}</if>
             <if test="realName != null  and realName != ''">and real_name like concat('%', #{realName}, '%')</if>
+            <if test="userPhone != null  and userPhone != ''">and user_phone = #{userPhone}</if>
+            <if test="status != null ">and status = #{status}</if>
+            <if test="paid != null ">and paid = #{paid}</if>
+            <if test="payType != null  and payType != ''">and pay_type = #{payType}</if>
+            <if test="orderType != null  and orderType != ''">and order_type = #{orderType}</if>
+            <if test="params.beginPayTime != null and params.endPayTime != null">and pay_time between
+                #{params.beginPayTime} and #{params.endPayTime}
+            </if>
         </where>
         order by create_time desc
     </select>
@@ -79,11 +90,17 @@
         where id = #{id}
     </select>
 
+    <select id="selectStoreOrderByOrderId" parameterType="String" resultMap="StoreOrderResult">
+        <include refid="selectStoreOrderVo"/>
+        where order_id = #{orderId}
+    </select>
+
     <insert id="insertStoreOrder" parameterType="StoreOrder">
         insert into store_order
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="id != null">id,</if>
             <if test="orderId != null and orderId != ''">order_id,</if>
+            <if test="transactionId != null and transactionId != ''">transaction_id,</if>
             <if test="uid != null">uid,</if>
             <if test="realName != null and realName != ''">real_name,</if>
             <if test="userPhone != null and userPhone != ''">user_phone,</if>
@@ -108,11 +125,13 @@
             <if test="createTime != null">create_time,</if>
             <if test="updateBy != null and updateBy != ''">update_by,</if>
             <if test="updateTime != null">update_time,</if>
+            <if test="payId != null and payId != ''">pay_id,</if>
             <if test="remark != null and remark != ''">remark,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
             <if test="orderId != null and orderId != ''">#{orderId},</if>
+            <if test="transactionId != null and transactionId != ''">#{transactionId},</if>
             <if test="uid != null">#{uid},</if>
             <if test="realName != null and realName != ''">#{realName},</if>
             <if test="userPhone != null and userPhone != ''">#{userPhone},</if>
@@ -137,6 +156,7 @@
             <if test="createTime != null">#{createTime},</if>
             <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
             <if test="updateTime != null">#{updateTime},</if>
+            <if test="payId != null and payId != ''">#{payId},</if>
             <if test="remark != null and remark != ''">#{remark},</if>
         </trim>
     </insert>
@@ -145,6 +165,7 @@
         update store_order
         <trim prefix="SET" suffixOverrides=",">
             <if test="orderId != null and orderId != ''">order_id = #{orderId},</if>
+            <if test="transactionId != null and transactionId != ''">transaction_id = #{transactionId},</if>
             <if test="uid != null">uid = #{uid},</if>
             <if test="realName != null and realName != ''">real_name = #{realName},</if>
             <if test="userPhone != null and userPhone != ''">user_phone = #{userPhone},</if>
@@ -169,6 +190,7 @@
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="payId != null and payId != ''">pay_id = #{payId},</if>
             <if test="remark != null and remark != ''">remark = #{remark},</if>
         </trim>
         where id = #{id}
@@ -180,7 +202,7 @@
         where id = #{id}
     </delete>
 
-    <delete id="deleteStoreOrderByIds" parameterType="Long">
+    <delete id="deleteStoreOrderByIds" parameterType="String">
         delete from store_order where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}

+ 141 - 0
qmjszx-business/src/main/resources/mapper/order/StoreRefundMapper.xml

@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="beilv.order.mapper.StoreRefundMapper">
+
+    <resultMap type="StoreRefund" id="StoreRefundResult">
+        <result property="id" column="id"/>
+        <result property="uid" column="uid"/>
+        <result property="refundNo" column="refund_no"/>
+        <result property="orderId" column="order_id"/>
+        <result property="transactionId" column="transaction_id"/>
+        <result property="refundAmount" column="refund_amount"/>
+        <result property="refundReason" column="refund_reason"/>
+        <result property="refundStatus" column="refund_status"/>
+        <result property="wxRefundId" column="wx_refund_id"/>
+        <result property="successTime" column="success_time"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+    </resultMap>
+
+    <sql id="selectStoreRefundVo">
+        select id,
+               uid,
+               refund_no,
+               order_id,
+               transaction_id,
+               refund_amount,
+               refund_reason,
+               refund_status,
+               wx_refund_id,
+               success_time,
+               create_by,
+               create_time,
+               update_by,
+               update_time,
+               remark
+        from store_refund
+    </sql>
+
+    <select id="selectStoreRefundList" parameterType="StoreRefund" resultMap="StoreRefundResult">
+        <include refid="selectStoreRefundVo"/>
+        <where>
+            <if test="refundNo != null  and refundNo != ''">and refund_no = #{refundNo}</if>
+            <if test="orderId != null  and orderId != ''">and order_id = #{orderId}</if>
+            <if test="transactionId != null  and transactionId != ''">and transaction_id = #{transactionId}</if>
+            <if test="refundAmount != null ">and refund_amount = #{refundAmount}</if>
+            <if test="refundReason != null  and refundReason != ''">and refund_reason = #{refundReason}</if>
+            <if test="refundStatus != null  and refundStatus != ''">and refund_status = #{refundStatus}</if>
+            <if test="wxRefundId != null  and wxRefundId != ''">and wx_refund_id = #{wxRefundId}</if>
+            <if test="successTime != null ">and success_time = #{successTime}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectStoreRefundById" parameterType="Long" resultMap="StoreRefundResult">
+        <include refid="selectStoreRefundVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectStoreRefundByOrderId" parameterType="String" resultMap="StoreRefundResult">
+        <include refid="selectStoreRefundVo"/>
+        where order_id = #{orderId}
+    </select>
+
+    <insert id="insertStoreRefund" parameterType="StoreRefund">
+        insert into store_refund
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="uid != null and uid != ''">uid,</if>
+            <if test="refundNo != null and refundNo != ''">refund_no,</if>
+            <if test="orderId != null and orderId != ''">order_id,</if>
+            <if test="transactionId != null and transactionId != ''">transaction_id,</if>
+            <if test="refundAmount != null">refund_amount,</if>
+            <if test="refundReason != null and refundReason != ''">refund_reason,</if>
+            <if test="refundStatus != null and refundStatus != ''">refund_status,</if>
+            <if test="wxRefundId != null and wxRefundId != ''">wx_refund_id,</if>
+            <if test="successTime != null">success_time,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null and updateBy != ''">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null and remark != ''">remark,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="uid != null and uid != ''">#{uid},</if>
+            <if test="refundNo != null and refundNo != ''">#{refundNo},</if>
+            <if test="orderId != null and orderId != ''">#{orderId},</if>
+            <if test="transactionId != null and transactionId != ''">#{transactionId},</if>
+            <if test="refundAmount != null">#{refundAmount},</if>
+            <if test="refundReason != null and refundReason != ''">#{refundReason},</if>
+            <if test="refundStatus != null and refundStatus != ''">#{refundStatus},</if>
+            <if test="wxRefundId != null and wxRefundId != ''">#{wxRefundId},</if>
+            <if test="successTime != null">#{successTime},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null and remark != ''">#{remark},</if>
+        </trim>
+    </insert>
+
+    <update id="updateStoreRefund" parameterType="StoreRefund">
+        update store_refund
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="uid != null and uid != ''">uid = #{uid},</if>
+            <if test="refundNo != null and refundNo != ''">refund_no = #{refundNo},</if>
+            <if test="orderId != null and orderId != ''">order_id = #{orderId},</if>
+            <if test="transactionId != null and transactionId != ''">transaction_id = #{transactionId},</if>
+            <if test="refundAmount != null">refund_amount = #{refundAmount},</if>
+            <if test="refundReason != null and refundReason != ''">refund_reason = #{refundReason},</if>
+            <if test="refundStatus != null and refundStatus != ''">refund_status = #{refundStatus},</if>
+            <if test="wxRefundId != null and wxRefundId != ''">wx_refund_id = #{wxRefundId},</if>
+            <if test="successTime != null">success_time = #{successTime},</if>
+            <if test="createBy != null and createBy != ''">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null and remark != ''">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteStoreRefundById" parameterType="Long">
+        delete
+        from store_refund
+        where id = #{id}
+    </delete>
+
+    <delete id="deleteStoreRefundByIds" parameterType="String">
+        delete from store_refund where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 4 - 0
qmjszx-framework/src/main/java/beilv/framework/config/ShiroConfig.java

@@ -294,6 +294,10 @@ public class ShiroConfig {
         filterChainDefinitionMap.put("/weixin-mini-app/login", "anon,captchaValidate");
         // 微信小程序所有接口请求全部放权
         filterChainDefinitionMap.put("/app-api/**", "anon,captchaValidate");
+        // 微信支付成功回调接口
+        filterChainDefinitionMap.put("/app/pay/notify", "anon,captchaValidate");
+        //微信支付退款回调接口
+        filterChainDefinitionMap.put("/app/pay/refund", "anon,captchaValidate");
         filterChainDefinitionMap.put("/profile/upload/**", "anon,captchaValidate");
         // 微信小程序直播请求接口
         filterChainDefinitionMap.put("/system/hik/getCameraPreviewURL", "anon,captchaValidate");

+ 8 - 4
qmjszx-pay/pom.xml

@@ -72,12 +72,16 @@
         </dependency>
 
         <!-- 微信支付 -->
+<!--        <dependency>-->
+<!--            <groupId>com.github.wechatpay-apiv3</groupId>-->
+<!--            <artifactId>wechatpay-apache-httpclient</artifactId>-->
+<!--            <version>0.4.9</version>-->
+<!--        </dependency>-->
         <dependency>
-            <groupId>com.github.wechatpay-apiv3</groupId>
-            <artifactId>wechatpay-apache-httpclient</artifactId>
-            <version>0.4.4</version>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-pay</artifactId>
+            <version>4.7.0</version>
         </dependency>
-        
         <!-- Jackson Databind -->
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>

+ 50 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/config/WxPayConfiguration.java

@@ -0,0 +1,50 @@
+package beilv.wx.pay.config;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Binary Wang
+ */
+@Configuration
+@ConditionalOnClass(WxPayService.class)
+@EnableConfigurationProperties(WxPayProperties.class)
+@AllArgsConstructor
+public class WxPayConfiguration {
+  private WxPayProperties properties;
+
+  @Bean
+  @ConditionalOnMissingBean
+  public WxPayService wxService() {
+    WxPayConfig payConfig = new WxPayConfig();
+    payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));//V3商户模式需要
+    payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));//V3商户模式需要
+    payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
+    payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
+    payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
+    payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
+    payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiV3Key()));//V3商户模式需要
+    payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));//V3商户模式需要
+    payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));//V3商户模式需要
+    payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));//V3商户模式需要
+    payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));//V3商户模式需要
+    payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));//V3商户模式需要
+    payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));//支付成功回调地址
+
+    // 可以指定是否使用沙箱环境
+    payConfig.setUseSandboxEnv(false);
+
+    WxPayService wxPayService = new WxPayServiceImpl();
+    wxPayService.setConfig(payConfig);
+    return wxPayService;
+  }
+
+}

+ 92 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/config/WxPayProperties.java

@@ -0,0 +1,92 @@
+package beilv.wx.pay.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * wxpay pay properties.
+ *
+ * @author Binary Wang
+ */
+@Data
+@ConfigurationProperties(prefix = "wx.pay")
+public class WxPayProperties {
+    /**
+     * 设置微信公众号或者小程序等的appid
+     * (V3商户模式需要)
+     */
+    private String appId;
+
+    /**
+     * 微信支付商户号
+     * (V3商户模式需要)
+     */
+    private String mchId;
+
+    /**
+     * 微信支付商户密钥
+     */
+    private String mchKey;
+
+    /**
+     * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
+     */
+    private String subAppId;
+
+    /**
+     * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
+     */
+    private String subMchId;
+
+    /**
+     * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
+     */
+    private String keyPath;
+
+    /**
+     * apiV3 秘钥值
+     * (V3商户模式需要)
+     */
+    private String apiV3Key;
+
+    /**
+     * apiV3 证书序列号值
+     * (V3商户模式需要)
+     */
+    private String certSerialNo;
+
+    /**
+     * apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     * (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代)
+     */
+    private String privateCertPath;
+
+    /**
+     * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     * (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代)
+     */
+    private String privateKeyPath;
+
+    /**
+     * 微信支付公钥,pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     * (V3商户模式需要,这里用的是文件路径,可以用其它base64编码或字节数组参数替代,2024.08后的新商户验签需要用此公钥)
+     */
+    private String publicKeyPath;
+
+    /**
+     * 微信支付公钥ID
+     * (V3商户模式需要,2024.08后的新商户验签需要用此公钥ID)
+     */
+    private String publicKeyId;
+
+    /**
+     * 异步通知地址
+     */
+    private String notifyUrl;
+
+    /**
+     * 退款异步通知地址
+     */
+    private String refundNotifyUrl;
+
+}

+ 95 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/controller/WxPayController.java

@@ -0,0 +1,95 @@
+package beilv.wx.pay.controller;
+
+import beilv.common.core.controller.BaseController;
+import beilv.common.core.domain.AjaxResult;
+import beilv.wx.pay.domain.vo.AppPayParam;
+import beilv.wx.pay.domain.vo.AppRefundParam;
+import beilv.wx.pay.service.IWxPayService;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result.JsapiResult;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Api("微信支付V3部分接口示例")
+@RestController
+@RequestMapping("/")
+public class WxPayController extends BaseController {
+
+    @Autowired
+    private IWxPayService wxPayService;
+
+    /**
+     * 调用统一下单接口(JSAPI)
+     * 详见:https://pay.weixin.qq.com/doc/v3/merchant/4012791856
+     */
+    @ApiOperation(value = "统一下单,并组装所需支付参数")
+    @PostMapping("app-api/pay/unifiedOrder")
+    public AjaxResult unifiedOrder(@RequestBody AppPayParam param) throws Exception {
+        return AjaxResult.success(wxPayService.unifiedOrder(param));
+    }
+
+
+    /**
+     * <pre>
+     * 支付成功回调
+     * 详见 https://pay.weixin.qq.com/doc/v3/merchant/4012791861
+     * </pre>
+     *
+     * @param notifyData
+     * @param request
+     * @return
+     * @throws WxPayException
+     */
+    @ApiOperation(value = "支付回调通知处理")
+    @PostMapping("app/pay/notify")
+    public ResponseEntity<String> parseOrderNotifyResult(@RequestBody String notifyData, HttpServletRequest request) {
+        return wxPayService.handleNotify(notifyData, request);
+    }
+
+    /**
+     * <pre>
+     * 微信支付-申请退款
+     * 详见 https://pay.weixin.qq.com/doc/v3/merchant/4012791862
+     * </pre>
+     *
+     * @return 退款操作结果
+     */
+    @ApiOperation(value = "退款")
+    @PostMapping("app-api/pay/refund")
+    public WxPayRefundV3Result refund(@RequestBody AppRefundParam param) throws WxPayException {
+        return wxPayService.refund(param);
+    }
+
+    /**
+     * <pre>
+     * 退款成功回调
+     * 详见 https://pay.weixin.qq.com/doc/v3/merchant/4012791865
+     * </pre>
+     *
+     * @param notifyData
+     * @param request
+     * @return
+     * @throws WxPayException
+     */
+    @ApiOperation(value = "退款回调通知处理")
+    @PostMapping("app/notify/refund")
+    public ResponseEntity<String> parseRefundNotifyResult(@RequestBody String notifyData, HttpServletRequest request) {
+        return wxPayService.parseRefundNotify(notifyData, request);
+    }
+
+}

+ 43 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/domain/vo/AppPayParam.java

@@ -0,0 +1,43 @@
+package beilv.wx.pay.domain.vo;
+
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+
+@Data
+public class AppPayParam implements Serializable {
+    /**
+     * 订单类型(1:会员充值;2:卡券;3:直播赛事;4:约球;)
+     */
+    private String orderType;
+    /**
+     * 支付方式(1:微信支付,2:余额支付)
+     */
+    private String payType;
+    /**
+     * 商品id
+     */
+    private String orderId;
+    /***
+     * 订单金额
+     */
+    private BigDecimal money;
+    /***
+     * 登录人id
+     */
+    private Long userId;
+
+    /***
+     * 退款单号
+     */
+    private String outRefundNo;
+    
+    /**
+     * 退款原因
+     */
+    private String refundReason;
+
+}

+ 31 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/domain/vo/AppRefundParam.java

@@ -0,0 +1,31 @@
+package beilv.wx.pay.domain.vo;
+
+import beilv.system.domain.SysMember;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+
+@Data
+public class AppRefundParam extends WxPayRefundV3Request {
+    /***
+     * 订单金额
+     */
+    private BigDecimal money;
+
+    /***
+     * 支付类型  1:微信 2:余额 3:退款
+     */
+    private String payType;
+
+    private SysMember sysMember;
+
+    private Long uid;
+
+    /**
+     * 订单类型(1:会员充值;2:卡券;3:报名;4:约球;5:观赛;6:退款;)
+     */
+    private String orderType;
+
+}

+ 10 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/enums/ErrorCodeConstants.java

@@ -0,0 +1,10 @@
+package beilv.wx.pay.enums;
+
+
+import beilv.common.utils.ErrorCode;
+
+public interface ErrorCodeConstants {
+
+    ErrorCode USER_NOT_BINDING_WX_APPLET = new ErrorCode(1008007011, "该用户未绑定微信小程序!openid为空!");
+
+}

+ 27 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/enums/OrderTypeEnum.java

@@ -0,0 +1,27 @@
+package beilv.wx.pay.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+
+/**
+ * @author hupeng
+ * 订单类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum OrderTypeEnum {
+
+    MEMBER_CHARGING("1", "会员充值"),
+    SINGLE_TIME("2", "卡券"),
+    MANY_TIMES("3", "报名"),
+    APPOINTMENT("4", "约球"),
+    WATCH_GAME("5", "观赛"),
+    REFOUND_MONEY("6", "退款");
+
+    private String value;
+
+    private String desc;
+
+}

+ 22 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/enums/PayOrderConstants.java

@@ -0,0 +1,22 @@
+package beilv.wx.pay.enums;
+
+// 支付订单常亮
+public interface PayOrderConstants {
+
+    // 会员充值订单前缀
+    String RECHARGE_ORDER_PREFIX = "REC";
+    //观赛订单前缀
+    String EVENT_ORDER_PREFIX = "EVE";
+    //购卡订单前缀
+    String CAR_ORDER_PREFIX = "CAR";
+    //约球类型订单前缀
+    String APPOINTMENT_ORDER_PREFIX = "APP";
+    //报名类型订单前缀
+    String REGISTRATION_ORDER_PREFIX = "REG";
+    //退款订单前缀
+    String REFOUND_MONEY_PREFIX = "REF";
+
+    // 微信回调返回
+    String WEINXIN_RESULT = "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
+
+}

+ 36 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/enums/PayTypeEnum.java

@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2018-2022
+ * All rights reserved, Designed By www.yixiang.co
+ */
+package beilv.wx.pay.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * @author hupeng
+ * 支付相关枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum PayTypeEnum {
+
+    WEIXIN_APPLET("weixin_applet", "微信小程序支付"),
+
+    YUE("yue", "余额支付"),
+    INTEGRAL("integral", "积分兑换");
+
+    private String type;
+
+    private String desc;
+
+    public static PayTypeEnum toType(String type) {
+        return Stream.of(PayTypeEnum.values())
+                .filter(p -> p.type.equals(type))
+                .findAny()
+                .orElse(null);
+    }
+
+}

+ 355 - 0
qmjszx-pay/src/main/java/beilv/wx/pay/service/IWxPayService.java

@@ -0,0 +1,355 @@
+package beilv.wx.pay.service;
+
+import beilv.carinformation.domain.CarInformation;
+import beilv.carinformation.mapper.CarInformationMapper;
+import beilv.competition.domain.Competition;
+import beilv.competition.mapper.CompetitionMapper;
+import beilv.order.domain.StoreOrder;
+import beilv.order.domain.StoreRefund;
+import beilv.order.mapper.StoreOrderMapper;
+import beilv.order.mapper.StoreRefundMapper;
+import beilv.system.domain.SysMember;
+import beilv.system.service.ISysMemberService;
+import beilv.wx.pay.config.WxPayProperties;
+import beilv.wx.pay.domain.vo.AppPayParam;
+import beilv.wx.pay.domain.vo.AppRefundParam;
+import beilv.wx.pay.enums.OrderTypeEnum;
+import beilv.wx.pay.enums.PayOrderConstants;
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result.JsapiResult;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+public class IWxPayService {
+
+    @Autowired
+    private CarInformationMapper carInformationMapper;
+    @Autowired
+    private StoreOrderMapper storeOrderMapper;
+    @Autowired
+    private CompetitionMapper competitionMapper;
+    @Autowired
+    private WxPayProperties wxPayProperties;
+    @Autowired
+    private ISysMemberService sysMemberService;
+    @Autowired
+    private StoreRefundMapper storeRefundMapper;
+
+    private WxPayService wxService;
+
+
+    /**
+     * 统一下单接口
+     *
+     * @return 预支付交易会话标识
+     */
+    public JsapiResult unifiedOrder(AppPayParam param) throws Exception {
+        SysMember user = getSysMember(param.getUserId());
+        //生成唯一标识商家订单号
+        String outTradeNo = generateOutTradeNo(param.getOrderType());
+        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+        request.setAppid(wxPayProperties.getAppId());
+        request.setMchid(wxPayProperties.getMchId());
+        request.setDescription(orderInfo(param, user));
+        request.setOutTradeNo(outTradeNo);
+        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
+        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
+        amount.setTotal(yuanToFen(param.getMoney()));
+        amount.setCurrency("CNY");
+        request.setAmount(amount);
+        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(user.getOpenId()));
+        JsapiResult jsapiResult = this.wxService.createOrderV3(TradeTypeEnum.JSAPI, request);
+        if (StringUtils.isNotEmpty(jsapiResult.getPackageValue())) {
+            //成功之后创建订单
+            generateOrder(user, param, outTradeNo, jsapiResult.getPackageValue(), orderInfo(param, user));
+
+        }
+        return jsapiResult;
+    }
+
+    //获取当前用户信息
+    private SysMember getSysMember(Long userId) {
+        return sysMemberService.selectSysMemberById(userId);
+    }
+
+
+    /**
+     * 元转分(不四舍五入,直接截取)
+     *
+     * @param amount 金额(元)
+     * @return 分(整数)
+     */
+    public static int yuanToFen(BigDecimal amount) {
+        if (amount == null) return 0;
+        return amount.multiply(BigDecimal.valueOf(100))
+                .setScale(0, RoundingMode.DOWN)  // 不四舍五入
+                .intValueExact();
+    }
+
+    /**
+     * 统一下单接口
+     *
+     * @return 预支付交易会话标识
+     */
+    private void generateOrder(SysMember user, AppPayParam param, String outTradeNo, String payId, String remark) {
+        StoreOrder storeOrder = new StoreOrder();
+        storeOrder.setOrderId(outTradeNo);
+        storeOrder.setOrderType(param.getOrderType());
+        storeOrder.setUid(user.getId());
+        storeOrder.setRealName(user.getUsername());
+        storeOrder.setUserPhone(user.getMobile());
+        storeOrder.setUserAddress(user.getAddress());
+        storeOrder.setTotalNum(1);
+        storeOrder.setPayType(param.getPayType());
+        storeOrder.setTotalPrice(param.getMoney());
+        storeOrder.setPayPrice(param.getMoney());
+        storeOrder.setPayTime(new Date());
+        storeOrder.setPayId(payId);
+        storeOrder.setCreateTime(new Date());
+        storeOrder.setRemark(remark);
+        storeOrderMapper.insertStoreOrder(storeOrder);
+    }
+
+    //生成商户订单号
+    public String generateOutTradeNo(String orderType) {
+        String prefix = "";
+        String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+        String random = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 6位随机
+        if (orderType.equals(OrderTypeEnum.MEMBER_CHARGING.getValue())) {
+            prefix = PayOrderConstants.RECHARGE_ORDER_PREFIX;
+        } else if (orderType.equals(OrderTypeEnum.SINGLE_TIME.getValue())) {
+            prefix = PayOrderConstants.CAR_ORDER_PREFIX;
+        } else if (orderType.equals(OrderTypeEnum.MANY_TIMES.getValue())) {
+            prefix = PayOrderConstants.EVENT_ORDER_PREFIX;
+        } else if (orderType.equals(OrderTypeEnum.APPOINTMENT.getValue())) {
+            prefix = PayOrderConstants.APPOINTMENT_ORDER_PREFIX;
+        } else if (orderType.equals(OrderTypeEnum.WATCH_GAME.getValue())) {
+            prefix = PayOrderConstants.REGISTRATION_ORDER_PREFIX;
+        } else if (orderType.equals(OrderTypeEnum.REFOUND_MONEY.getValue())) {
+            prefix = PayOrderConstants.REFOUND_MONEY_PREFIX;
+        }
+        return prefix + time + random;
+    }
+
+    //获取购买商品信息
+    private String orderInfo(AppPayParam param, SysMember user) {
+        String orderType = param.getOrderType();
+        String isVip = user.getIsVip();
+        if (orderType.equals(OrderTypeEnum.MEMBER_CHARGING.getValue())) {
+            return "会员充值";
+        } else if (orderType.equals(OrderTypeEnum.SINGLE_TIME.getValue())) {
+            CarInformation carInformation = carInformationMapper.selectCarInformationById(Long.valueOf(param.getOrderId()));
+            if ("1".equals(isVip)) {
+                return carInformation.getMemberPrice().toString();
+            }
+            return carInformation.getName();
+        } else if (orderType.equals(OrderTypeEnum.MANY_TIMES.getValue())) {
+            Competition competition = competitionMapper.selectCompetitionById(Integer.valueOf(param.getOrderId()));
+            return competition.getCompetitionTitle() + competition.getCompetitionType() + "报名";
+        } else if (orderType.equals(OrderTypeEnum.APPOINTMENT.getValue())) {
+            return "球场预约";
+        } else if (orderType.equals(OrderTypeEnum.WATCH_GAME.getValue())) {
+            Competition competition = competitionMapper.selectCompetitionById(Integer.valueOf(param.getOrderId()));
+            return competition.getCompetitionTitle() + competition.getCompetitionType() + "观赛";
+        }
+        return null;
+    }
+
+    /**
+     * 处理支付结果通知
+     *
+     * @return 处理结果
+     */
+    public ResponseEntity<String> handleNotify(String notifyData, HttpServletRequest request) {
+        SignatureHeader header = getRequestHeader(request);
+        try {
+            WxPayNotifyV3Result res = this.wxService.parseOrderNotifyV3Result(notifyData, header);
+            WxPayNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
+            // TODO 根据自己业务场景需要构造返回对象
+            if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(decryptRes.getTradeState())) {
+                // 成功处理订单
+                processOrderPayment(decryptRes.getOutTradeNo());
+                // 成功返回200/204,body无需有内容
+                return ResponseEntity.status(200).body("");
+            } else {
+                // 失败返回4xx或5xx,且需要构造body信息
+                return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
+            }
+        } catch (WxPayException e) {
+            // 失败返回4xx或5xx,且需要构造body信息
+            return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
+        }
+    }
+
+    /**
+     * 处理订单支付完成后的业务逻辑
+     *
+     * @param outTradeNo 商户订单号
+     */
+    private void processOrderPayment(String outTradeNo) {
+        StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(outTradeNo);
+        if (order != null) {
+            // 检查订单是否已经处理过
+            if (order.getPaid() == null || order.getPaid() != 1) {
+                // 更新订单状态
+                order.setPaid(1); // 设置为已支付
+                order.setStatus(1); // 设置订单状态为已支付
+                order.setPayTime(new java.util.Date()); // 设置支付时间
+
+                storeOrderMapper.updateStoreOrder(order);
+
+                // TODO: 根据订单类型处理不同的业务逻辑
+                // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
+            }
+        }
+    }
+
+    /**
+     * 微信支付-申请退款
+     *
+     */
+    public WxPayRefundV3Result refund(AppRefundParam param) throws WxPayException {
+        WxPayRefundV3Request request = new WxPayRefundV3Request();
+        //商户退款单号
+        request.setOutRefundNo(param.getOutTradeNo());
+        //原支付商户订单号
+        request.setOutTradeNo(param.getOutTradeNo());
+        //退款原因说明
+        request.setReason("不想要了");
+        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
+        //原订单总金额(单位:分)
+        amount.setTotal(yuanToFen(param.getMoney()));
+        //退款金额(单位:分)
+        amount.setRefund(yuanToFen(param.getMoney()));
+        //货币类型,默认 "CNY"
+        amount.setCurrency("CNY");
+        request.setAmount(amount);
+        request.setNotifyUrl(wxPayProperties.getRefundNotifyUrl());
+        String outTradeNo = param.getOutTradeNo();
+
+        refundOrder(getSysMember(param.getUid()), param, outTradeNo, request.getReason());
+        return this.wxService.refundV3(request);
+    }
+
+    /**
+     * 统一下单接口
+     *
+     * @return 预支付交易会话标识
+     */
+    private void refundOrder(SysMember user, AppRefundParam param, String outTradeNo, String reason) {
+        StoreRefund storeRefund = new StoreRefund();
+        storeRefund.setUid(user.getId());
+        storeRefund.setRefundNo(generateOutTradeNo(param.getOrderType()));
+        storeRefund.setOrderId(outTradeNo);
+        storeRefund.setRefundAmount(param.getMoney());
+        storeRefund.setRefundReason(reason);
+        storeRefund.setCreateTime(new Date());
+        storeRefundMapper.insertStoreRefund(storeRefund);
+    }
+
+    /**
+     * 退款结果通知
+     *
+     * @return 处理结果
+     */
+    public ResponseEntity<String> parseRefundNotify(String notifyData, HttpServletRequest request) {
+        SignatureHeader header = getRequestHeader(request);
+        try {
+            WxPayRefundNotifyV3Result res = this.wxService.parseRefundNotifyV3Result(notifyData, header);
+            WxPayRefundNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
+            // TODO 根据自己业务场景需要构造返回对象
+            if (WxPayConstants.RefundStatus.SUCCESS.equals(decryptRes.getRefundStatus())) {
+                // 成功处理订单
+                processOrderRefund(decryptRes.getOutTradeNo());
+                //成功返回200/204,body无需有内容
+                return ResponseEntity.status(200).body("");
+            } else {
+                //失败返回4xx或5xx,且需要构造body信息
+                return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
+            }
+        } catch (WxPayException e) {
+            //失败返回4xx或5xx,且需要构造body信息
+            return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
+        }
+    }
+
+    /**
+     * 退款完成后的业务逻辑
+     *
+     * @param outTradeNo 商户订单号
+     */
+    private void processOrderRefund(String outTradeNo) {
+        StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(outTradeNo);
+        if (order != null) {
+            // 检查订单是否已经处理过
+            if (order.getRefundStatus() == null && order.getPaid() == 1) {
+                // 更新订单状态
+                order.setStatus(1); // 设置订单状态为已退款
+                order.setUpdateTime(new Date());
+                storeOrderMapper.updateStoreOrder(order);
+
+                // TODO: 根据订单类型处理不同的业务逻辑
+                // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
+            }
+        }
+        StoreRefund refund = storeRefundMapper.selectStoreRefundByOrderId(outTradeNo);
+        if (refund != null) {
+            // 检查订单是否已经处理过
+            if (refund.getRefundStatus() == null && refund.getRefundStatus().equals("1")) {
+                // 更新订单状态
+                refund.setRefundStatus("2"); // 设置订单状态为已退款
+                refund.setSuccessTime(new Date());
+                storeRefundMapper.updateStoreRefund(refund);
+
+                // TODO: 根据订单类型处理不同的业务逻辑
+                // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
+            }
+        }
+    }
+
+
+    /**
+     * 组装请求头重的前面信息
+     *
+     * @param request
+     * @return
+     */
+    private SignatureHeader getRequestHeader(HttpServletRequest request) {
+        // 获取通知签名
+        String signature = request.getHeader("Wechatpay-Signature");
+        String nonce = request.getHeader("Wechatpay-Nonce");
+        String serial = request.getHeader("Wechatpay-Serial");
+        String timestamp = request.getHeader("Wechatpay-Timestamp");
+        SignatureHeader signatureHeader = new SignatureHeader();
+        signatureHeader.setSignature(signature);
+        signatureHeader.setNonce(nonce);
+        signatureHeader.setSerial(serial);
+        signatureHeader.setTimeStamp(timestamp);
+        return signatureHeader;
+    }
+
+
+}

+ 9 - 108
qmjszx-system/src/main/java/beilv/system/domain/SysMember.java

@@ -3,6 +3,7 @@ package beilv.system.domain;
 import beilv.common.annotation.Excel;
 import beilv.common.core.domain.BaseEntity;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
@@ -15,6 +16,7 @@ import java.util.Date;
  * @author ruoyi
  * @date 2025-01-02
  */
+@Data
 public class SysMember extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
@@ -73,6 +75,12 @@ public class SysMember extends BaseEntity {
     private String email;
 
     /**
+     * 会员(1是 2否)
+     */
+    @Excel(name = "会员")
+    private String isVip;
+
+    /**
      * 出生日期
      */
     @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@@ -85,112 +93,5 @@ public class SysMember extends BaseEntity {
     private Integer delFlag;
 
 
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setMobile(String mobile) {
-        this.mobile = mobile;
-    }
-
-    public String getMobile() {
-        return mobile;
-    }
-
-    public Integer getSex() {
-        return sex;
-    }
-
-    public void setSex(Integer sex) {
-        this.sex = sex;
-    }
-
-    public String getCity() {
-        return city;
-    }
-
-    public void setCity(String city) {
-        this.city = city;
-    }
-
-    public String getAddress() {
-        return address;
-    }
-
-    public void setAddress(String address) {
-        this.address = address;
-    }
-
-    public void setIntegral(BigDecimal integral) {
-        this.integral = integral;
-    }
-
-    public BigDecimal getIntegral() {
-        return integral;
-    }
-
-    public void setDelFlag(Integer delFlag) {
-        this.delFlag = delFlag;
-    }
-
-    public Integer getDelFlag() {
-        return delFlag;
-    }
-
-    public void setOpenId(String openId) {
-        this.openId = openId;
-    }
-
-    public String getOpenId() {
-        return openId;
-    }
-
-    public String getEmail() {
-        return email;
-    }
-
-    public void setEmail(String email) {
-        this.email = email;
-    }
-
-    public Date getBirthday() {
-        return birthday;
-    }
-
-    public void setBirthday(Date birthday) {
-        this.birthday = birthday;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-                .append("id", getId())
-                .append("username", getUsername())
-                .append("mobile", getMobile())
-                .append("sex", getSex())
-                .append("address", getAddress())
-                .append("city", getCity())
-                .append("email", getEmail())
-                .append("birthday", getBirthday())
-                .append("createBy", getCreateBy())
-                .append("createTime", getCreateTime())
-                .append("updateBy", getUpdateBy())
-                .append("updateTime", getUpdateTime())
-                .append("integral", getIntegral())
-                .append("delFlag", getDelFlag())
-                .append("openId", getOpenId())
-                .toString();
-    }
+
 }