├── .gitignore ├── LICENSE.txt ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java └── com │ └── leone │ └── pay │ ├── PayApplication.java │ ├── ali │ ├── controller │ │ └── AliPayController.java │ └── service │ │ └── AliPayService.java │ ├── common │ ├── Result.java │ ├── config │ │ ├── AppConfig.java │ │ └── SwaggerConfig.java │ ├── enums │ │ ├── ResultEnum.java │ │ ├── status │ │ │ └── OrderStatus.java │ │ └── type │ │ │ ├── OrderType.java │ │ │ └── WxPayType.java │ ├── exception │ │ ├── ExceptionMessage.java │ │ ├── ExceptionResult.java │ │ ├── GlobalExceptionHandler.java │ │ └── ValidateException.java │ └── property │ │ ├── AliProperties.java │ │ ├── AppProperties.java │ │ └── WxProperties.java │ ├── entity │ ├── Goods.java │ ├── Order.java │ ├── OrderBill.java │ ├── OrderItem.java │ └── User.java │ ├── mapper │ ├── GoodsMapper.java │ ├── OrderBillMapper.java │ ├── OrderItemMapper.java │ ├── OrderMapper.java │ └── UserMapper.java │ ├── service │ ├── OrderBillService.java │ ├── OrderService.java │ └── UserService.java │ ├── utils │ ├── HttpUtil.java │ ├── PaymentUtils.java │ └── RandomUtil.java │ └── wx │ ├── controller │ └── WxPayController.java │ ├── pojo │ └── WxUser.java │ └── service │ └── WxPayService.java └── resources └── application.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.jar 4 | *.war 5 | *.zip 6 | *.tar 7 | *.tar.gz 8 | 9 | # eclipse ignore 10 | .settings/ 11 | .project 12 | .classpath 13 | 14 | # idea ignore 15 | .idea/ 16 | *.ipr 17 | *.iml 18 | *.iws 19 | 20 | # temp ignore 21 | *.log 22 | *.cache 23 | *.diff 24 | *.patch 25 | *.tmp 26 | 27 | # system ignore 28 | .DS_Store 29 | Thumbs.db 30 | 31 | # else 32 | .springBeans 33 | out 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-pay 2 | 基于 Spring Boot 的各种支付对接微信App支付、小程序支付、微信扫码支付、微信退款、支付宝App支付、扫码支付,提现、退款 3 | 4 | 5 | ## 微信支付 6 | 7 | **微信支付流程** 8 | 9 | ### 微信App支付 10 | 11 | **场景介绍** 12 | 13 | 适用于商户在移动端APP中集成微信支付功能。商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。目前微信支付支持手机系统有:IOS(苹果)、Android(安卓)和WP(Windows Phone)。 14 | 15 | **交互细节** 16 | 17 | * 步骤1:用户进入商户APP,选择商品下单、确认购买,进入支付环节。商户服务后台生成支付订单,签名后将数据传输到APP端。 18 | 19 | * 步骤2:用户点击后发起支付操作,进入到微信界面,调起微信支付,出现确认支付界面。 20 | 21 | * 步骤3:用户确认收款方和金额,点击立即支付后出现输入密码界面,可选择零钱或银行卡支付。 22 | 23 | * 第四步:输入正确密码后,支付完成,用户端微信出现支付详情页面。 24 | 25 | * 第五步:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。 26 | 27 | **业务流程图** 28 | 29 | ![app支付业务流程图](http://baocangwh.cn/t6/702/1555984684x2918527082.png) 30 | 31 | 32 | **商户系统和微信支付系统交互说明** 33 | 34 | * 1.用户在商户APP中选择商品,提交订单,选择微信支付。 35 | 36 | * 2.商户后台收到用户支付单,调用微信支付统一下单接口。 37 | 38 | * 3.统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay 39 | 40 | * 4.商户APP调起微信支付。api参见本章节。 41 | 42 | * 5.商户后台接收支付通知。api参见。 43 | 44 | * 6.商户后台查询支付结果。 45 | 46 | 47 | ### 微信小程序支付 48 | 49 | 小程序支付需要先有个小程序,并且好要有个一商户号,这样才能获取到一些必须要的信息。 50 | 51 | **业务流程** 52 | 53 | 1、小程序内调用登录接口,获取到用户的openid。 54 | 55 | 2、商户server调用支付统一下单。 56 | 57 | 3、商户server调用再次签名。 58 | 59 | 4、商户server接收支付通知,并回复微信收到通知。 60 | 61 | 5、商户server查询支付结果。 62 | 63 | ![小程序支付流程图](https://s2.ax1x.com/2019/04/23/EAvzIe.md.png) 64 | 65 | 66 | 小程序支付后App支付流程差不多都是现在后台向微信预下单然后返回调用返回的数据并进行签名前端支付成功后,微信会回调商户的后台服务,商户的后台服务做相应的处理。 67 | 68 | 69 | ### 微信扫码支付 70 | 71 | **场景介绍** 72 | 73 | * 1.商户根据微信支付的规则,为不同商品生成不同的二维码,展示在各种场景,用于用户扫描购买。 74 | 75 | * 2.用户使用微信“扫一扫”扫描二维码后,获取商品支付信息,引导用户完成支付。 76 | 77 | * 3.用户确认支付,输入支付密码。 78 | 79 | * 4.支付完成后会提示用户支付成功,商户后台得到支付成功的通知,然后进行发货处理。 80 | 81 | **业务流程图** 82 | 83 | ![微信扫码支付流程](https://s2.ax1x.com/2019/04/23/EAvkEF.md.png) 84 | 85 | **业务刘成刚说明** 86 | 87 | 业务流程说明: 88 | 89 | * 1.商户后台系统根据用户选购的商品生成订单。 90 | 91 | * 2.用户确认支付后调用微信支付【统一下单API】生成预支付交易; 92 | 93 | * 3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。 94 | 95 | * 4.商户后台系统根据返回的code_url生成二维码。 96 | 97 | * 5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。 98 | 99 | * 6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。 100 | 101 | * 7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。 102 | 103 | * 8.微信支付系统根据用户授权完成支付交易。 104 | 105 | * 9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。 106 | 107 | * 10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。 108 | 109 | 110 | ### 微信公众号支付 111 | 112 | 113 | ### 微信退款 114 | 115 | **应用场景** 116 | 117 | 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。 118 | 119 | 注意: 120 | 121 | * 1、交易时间超过一年的订单无法提交退款 122 | 123 | * 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号 124 | 125 | * 3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次,错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次 126 | 127 | * 4、每个支付订单的部分退款次数不能超过50次 128 | 129 | * 5、微信退款需要双向证书 130 | 131 | 132 | 133 | ## 支付宝支付 134 | 135 | ### 支付宝App支付 136 | 137 | ### 支付宝扫码支付 138 | 139 | ### 支付宝退款 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.leone.pay 8 | java-pay 9 | 1.2.0.RELEASE 10 | 11 | 12 | 13 | 14 | io.spring.platform 15 | platform-bom 16 | Cairo-SR6 17 | pom 18 | import 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | 31 | org.mybatis.spring.boot 32 | mybatis-spring-boot-starter 33 | 2.0.1 34 | 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | 8.0.28 40 | 41 | 42 | 43 | com.alipay.sdk 44 | alipay-sdk-java 45 | 3.7.26.ALL 46 | 47 | 48 | 49 | com.google.zxing 50 | javase 51 | 3.3.3 52 | 53 | 54 | 55 | org.apache.httpcomponents 56 | httpclient 57 | 4.5.13 58 | 59 | 60 | 61 | dom4j 62 | dom4j 63 | 1.6.1 64 | 65 | 66 | 67 | io.springfox 68 | springfox-swagger2 69 | 2.9.2 70 | 71 | 72 | 73 | io.springfox 74 | springfox-swagger-ui 75 | 2.9.2 76 | 77 | 78 | 79 | org.projectlombok 80 | lombok 81 | 1.18.2 82 | 83 | 84 | 85 | 86 | 87 | 88 | spring-boot-pay 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-compiler-plugin 93 | 3.7.0 94 | 95 | 1.8 96 | 1.8 97 | UTF-8 98 | 99 | 100 | 101 | 102 | org.codehaus.mojo 103 | versions-maven-plugin 104 | 2.7 105 | 106 | 107 | 108 | org.springframework.boot 109 | spring-boot-maven-plugin 110 | 2.0.7.RELEASE 111 | 112 | com.leone.pay.PayApplication 113 | ZIP 114 | 115 | 116 | 117 | 118 | repackage 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | spring-snapshots 129 | Spring Snapshots 130 | https://repo.spring.io/snapshot 131 | 132 | true 133 | 134 | 135 | 136 | spring-milestones 137 | Spring Milestones 138 | https://repo.spring.io/milestone 139 | 140 | false 141 | 142 | 143 | 144 | 145 | 146 | 147 | spring-snapshots 148 | Spring Snapshots 149 | https://repo.spring.io/snapshot 150 | 151 | true 152 | 153 | 154 | 155 | spring-milestones 156 | Spring Milestones 157 | https://repo.spring.io/milestone 158 | 159 | false 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/PayApplication.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 7 | 8 | /** 9 | * spring-boot 支付 demo 10 | * nohup java -jar spring-boot-pay.jar 1>web.log 2>&1 & 11 | */ 12 | @EnableSwagger2 13 | @SpringBootApplication 14 | @MapperScan(basePackages = "com.andy.pay.mapper") 15 | public class PayApplication { 16 | public static void main(String[] args) { 17 | SpringApplication.run(PayApplication.class, args); 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/ali/controller/AliPayController.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.ali.controller; 2 | 3 | import com.leone.pay.ali.service.AliPayService; 4 | import com.leone.pay.common.Result; 5 | import io.swagger.annotations.Api; 6 | import io.swagger.annotations.ApiOperation; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | /** 19 | * @author leone 20 | * @since 2018-06-16 21 | **/ 22 | @Slf4j 23 | @RestController 24 | @Api(tags = "支付宝支付") 25 | @RequestMapping(value = "/api/pay/ali") 26 | public class AliPayController { 27 | 28 | @Resource 29 | private AliPayService aliPayService; 30 | 31 | @GetMapping("/app") 32 | @ApiOperation("支付宝App支付预下单") 33 | public Result appPay(Long orderId, HttpServletRequest request, HttpServletResponse response) { 34 | return Result.success(aliPayService.appPay(orderId, request)); 35 | } 36 | 37 | @GetMapping("/qrcode") 38 | @ApiOperation("支付宝扫码支付预下单") 39 | public Result qrCodePay(Long orderId, HttpServletResponse response) throws Exception { 40 | return aliPayService.aliQrCodePay(orderId, response); 41 | } 42 | 43 | @ApiOperation("支付宝App支付退款") 44 | @GetMapping("/refund") 45 | public Result appRefund(Long orderId, HttpServletRequest request) throws Exception { 46 | return aliPayService.aliRefund(orderId, request); 47 | } 48 | 49 | 50 | @ApiOperation("支付宝支付通知") 51 | @RequestMapping(value = "/notify", method = {RequestMethod.GET, RequestMethod.POST}) 52 | public void aliNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { 53 | aliPayService.aliNotify(request, response); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/ali/service/AliPayService.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.ali.service; 2 | 3 | import com.alipay.api.AlipayApiException; 4 | import com.alipay.api.AlipayClient; 5 | import com.alipay.api.DefaultAlipayClient; 6 | import com.alipay.api.domain.AlipayFundTransToaccountTransferModel; 7 | import com.alipay.api.domain.AlipayTradeAppPayModel; 8 | import com.alipay.api.request.*; 9 | import com.alipay.api.response.AlipayFundTransToaccountTransferResponse; 10 | import com.alipay.api.response.AlipayTradeAppPayResponse; 11 | import com.alipay.api.response.AlipayTradePrecreateResponse; 12 | import com.alipay.api.response.AlipayTradeRefundResponse; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | import com.leone.pay.common.Result; 15 | import com.leone.pay.common.enums.status.OrderStatus; 16 | import com.leone.pay.common.exception.ExceptionMessage; 17 | import com.leone.pay.common.exception.ValidateException; 18 | import com.leone.pay.common.property.AppProperties; 19 | import com.leone.pay.entity.Order; 20 | import com.leone.pay.entity.OrderBill; 21 | import com.leone.pay.service.OrderBillService; 22 | import com.leone.pay.service.OrderService; 23 | import com.leone.pay.service.UserService; 24 | import com.leone.pay.utils.PaymentUtils; 25 | import com.leone.pay.utils.RandomUtil; 26 | import lombok.extern.slf4j.Slf4j; 27 | import org.springframework.stereotype.Service; 28 | import org.springframework.util.Assert; 29 | import org.springframework.util.ObjectUtils; 30 | 31 | import javax.annotation.PostConstruct; 32 | import javax.annotation.Resource; 33 | import javax.servlet.http.HttpServletRequest; 34 | import javax.servlet.http.HttpServletResponse; 35 | import java.io.BufferedOutputStream; 36 | import java.io.IOException; 37 | import java.util.Map; 38 | import java.util.TreeMap; 39 | 40 | /** 41 | *

支付宝支付 42 | * 43 | * @author leone 44 | * @since 2018-05-27 45 | **/ 46 | @Slf4j 47 | @Service 48 | public class AliPayService { 49 | 50 | @Resource 51 | private OrderService orderService; 52 | 53 | @Resource 54 | private UserService userService; 55 | 56 | @Resource 57 | private AppProperties appProperties; 58 | 59 | @Resource 60 | private ObjectMapper objectMapper; 61 | 62 | @Resource 63 | private OrderBillService orderBillService; 64 | 65 | // 阿里 sdk 封装 66 | private AlipayClient alipayClient; 67 | 68 | @PostConstruct 69 | public void initMethod() { 70 | alipayClient = new DefaultAlipayClient( 71 | appProperties.getAli().getServer_url(), 72 | appProperties.getAli().getApp_id(), 73 | appProperties.getAli().getAlipay_private_key(), 74 | appProperties.getAli().getFormat(), 75 | appProperties.getAli().getCharset(), 76 | appProperties.getAli().getAlipay_public_key(), 77 | appProperties.getAli().getSign_type()); 78 | } 79 | 80 | /** 81 | * 支付宝提现 82 | * 83 | * @param orderId 84 | */ 85 | public Result deposit(Long orderId) { 86 | // 校验订单信息 87 | Order order = orderService.findOne(orderId); 88 | if (order.getStatus() != OrderStatus.CREATE.getStatus()) { 89 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 90 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 91 | } 92 | 93 | AlipayFundTransToaccountTransferModel transferModel = new AlipayFundTransToaccountTransferModel(); 94 | 95 | transferModel.setOutBizNo(1 + RandomUtil.randomNum(15)); 96 | transferModel.setAmount(order.getTotalFee().toString()); 97 | transferModel.setPayeeAccount("real account"); 98 | transferModel.setPayeeRealName("real name"); 99 | transferModel.setPayerShowName("from name"); 100 | transferModel.setRemark("remark"); 101 | transferModel.setPayeeType("ALIPAY_LOGONID"); 102 | try { 103 | AlipayFundTransToaccountTransferRequest request = new AlipayFundTransToaccountTransferRequest(); 104 | request.setBizModel(transferModel); 105 | AlipayFundTransToaccountTransferResponse response = alipayClient.execute(request); 106 | } catch (AlipayApiException e) { 107 | log.info("ali deposit error message:{}", e.getMessage()); 108 | return Result.success(ExceptionMessage.ALI_DEPOSIT_SUCCESS); 109 | } 110 | return Result.error(ExceptionMessage.ALI_DEPOSIT_FAILED); 111 | } 112 | 113 | 114 | /** 115 | * 支付宝扫码支付生成二维码响应到浏览器 116 | * 117 | * @param orderId 118 | * @param response 119 | * @return 120 | */ 121 | public Result aliQrCodePay(Long orderId, HttpServletResponse response) throws Exception { 122 | // 校验订单信息 123 | Order order = orderService.findOne(orderId); 124 | if (order.getStatus() != OrderStatus.CREATE.getStatus()) { 125 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 126 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 127 | } 128 | 129 | AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); 130 | Map params = new TreeMap<>(); 131 | params.put("out_trade_no", order.getOutTradeNo()); 132 | params.put("total_amount", order.getTotalFee().toString()); 133 | params.put("subject", "备注"); 134 | params.put("body", "详情"); 135 | params.put("store_id", "NJ_2031"); 136 | params.put("timeout_express", "90m"); 137 | request.setBizContent(objectMapper.writeValueAsString(params)); 138 | request.setNotifyUrl(appProperties.getAli().getNotify_url()); 139 | 140 | AlipayTradePrecreateResponse responseData = alipayClient.execute(request); 141 | log.info("response:{}", responseData.getBody()); 142 | String qrCode = responseData.getQrCode(); 143 | if (!ObjectUtils.isEmpty(qrCode)) { 144 | PaymentUtils.createQRCode(qrCode, response); 145 | return Result.success(ExceptionMessage.SUCCESS); 146 | } 147 | return Result.error(ExceptionMessage.ALI_PAY_CREATE_QR_CODE_SUCCESS); 148 | } 149 | 150 | /** 151 | * 支付宝退款 152 | * 153 | * @param orderId 154 | * @param servletRequest 155 | * @return 156 | */ 157 | public Result aliRefund(Long orderId, HttpServletRequest servletRequest) throws Exception { 158 | // 校验订单信息 159 | Order order = orderService.findOne(orderId); 160 | if (order.getStatus() < OrderStatus.PAY.getStatus()) { 161 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 162 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 163 | } 164 | // 创建退款请求builder,设置请求参数 165 | AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); 166 | Map params = new TreeMap<>(); 167 | // 必须 商户订单号 168 | params.put("out_trade_no", order.getOutTradeNo()); 169 | // 必须 支付宝交易号 170 | params.put("trade_no", order.getTotalFee().toString()); 171 | // 必须 退款金额 172 | params.put("refund_amount", order.getTotalFee().toString()); 173 | // 可选 代表 退款的原因说明 174 | params.put("refund_reason", "退款的原因说明"); 175 | // 可选 标识一次退款请求,同一笔交易多次退款需要保证唯一(就是out_request_no在2次退款一笔交易时,要不一样),如需部分退款,则此参数必传 176 | params.put("out_request_no", 1 + RandomUtil.randomNum(11)); 177 | // 可选 代表 商户的门店编号 178 | params.put("store_id", "90m"); 179 | request.setBizContent(objectMapper.writeValueAsString(params)); 180 | AlipayTradeRefundResponse responseData = alipayClient.execute(request); 181 | if (responseData.isSuccess()) { 182 | log.info("ali refund success tradeNo:{}", order.getOutTradeNo()); 183 | return Result.success(ExceptionMessage.SUCCESS); 184 | } 185 | log.info("ali refund failed tradeNo:{}", order.getOutTradeNo()); 186 | return Result.error(ExceptionMessage.ALI_PAY_REFUND_FAILED); 187 | } 188 | 189 | 190 | /** 191 | * 阿里pc支付 192 | * 193 | * @param orderId 194 | * @param servletRequest 195 | * @return 196 | */ 197 | public String aliPcPay(Long orderId, HttpServletRequest servletRequest) throws Exception { 198 | Order order = orderService.findOne(orderId); 199 | AlipayTradePagePayRequest payRequest = new AlipayTradePagePayRequest(); 200 | // 前台通知 201 | payRequest.setReturnUrl(appProperties.getAli().getReturn_url()); 202 | // 后台回调 203 | payRequest.setNotifyUrl(appProperties.getAli().getNotify_url()); 204 | Map params = new TreeMap<>(); 205 | params.put("out_trade_no", order.getOutTradeNo()); 206 | // 订单金额:元 207 | params.put("total_amount", order.getTotalFee().toString()); 208 | params.put("subject", "订单标题"); 209 | // 实际收款账号,一般填写商户PID即可 210 | params.put("seller_id", appProperties.getAli().getMch_id()); 211 | // 电脑网站支付 212 | params.put("product_code", "FAST_INSTANT_TRADE_PAY"); 213 | params.put("body", "两个橘子"); 214 | payRequest.setBizContent(objectMapper.writeValueAsString(params)); 215 | log.info("业务参数:" + payRequest.getBizContent()); 216 | String result = ExceptionMessage.ERROR.getMessage(); 217 | try { 218 | result = alipayClient.pageExecute(payRequest).getBody(); 219 | } catch (AlipayApiException e) { 220 | log.error("ali pay error message:{}", e.getMessage()); 221 | } 222 | return result; 223 | } 224 | 225 | /** 226 | * 支付宝App支付 227 | * 228 | * @param orderId 229 | * @param servletRequest 230 | * @return 231 | */ 232 | public String appPay(Long orderId, HttpServletRequest servletRequest) { 233 | Order order = orderService.findOne(orderId); 234 | AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); 235 | // SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。 236 | AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); 237 | model.setBody("描述"); 238 | model.setSubject("商品名称"); 239 | model.setOutTradeNo(order.getOutTradeNo()); 240 | model.setTimeoutExpress("30m"); 241 | model.setTotalAmount(order.getTotalFee().toString()); 242 | model.setProductCode("QUICK_MSECURITY_PAY"); 243 | request.setBizModel(model); 244 | request.setNotifyUrl(appProperties.getAli().getNotify_url()); 245 | try { 246 | // 这里和普通的接口调用不同,使用的是sdkExecute 247 | AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request); 248 | //就是orderString 可以直接给客户端请求,无需再做处理。 249 | log.info("orderString:{}", response.getBody()); 250 | return response.getBody(); 251 | } catch (AlipayApiException e) { 252 | e.printStackTrace(); 253 | } 254 | return null; 255 | } 256 | 257 | /** 258 | * 支付回调通知 259 | * 260 | * @param request 261 | * @param response 262 | */ 263 | public void aliNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { 264 | String requestXml = PaymentUtils.getRequestData(request); 265 | Map requestMap = PaymentUtils.xmlToMap(requestXml); 266 | Assert.notNull(requestMap, ExceptionMessage.XML_DATA_INCORRECTNESS.getMessage()); 267 | 268 | // 当返回的return_code为SUCCESS则回调成功 269 | if (requestMap.get("code").equals(10000)) { 270 | 271 | // TODO 保存订单流水 具体细节待实现 272 | orderBillService.save(new OrderBill()); 273 | log.info("notify success"); 274 | } else { 275 | log.error("notify failed"); 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/Result.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common; 2 | 3 | import com.leone.pay.common.exception.ExceptionMessage; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | *

9 | * 10 | * @author leone 11 | **/ 12 | public class Result implements Serializable { 13 | 14 | private static final long serialVersionUID = 1813924894386442775L; 15 | 16 | private String messages; 17 | 18 | private Integer code; 19 | 20 | private T data; 21 | 22 | private Result() { 23 | } 24 | 25 | private Result(String messages, Integer code, T data) { 26 | this.messages = messages; 27 | this.code = code; 28 | this.data = data; 29 | } 30 | 31 | private Result(ExceptionMessage exceptionMessage, T data) { 32 | this.messages = exceptionMessage.getMessage(); 33 | this.code = exceptionMessage.getCode(); 34 | this.data = data; 35 | } 36 | 37 | private Result(String messages, Integer code) { 38 | this.messages = messages; 39 | this.code = code; 40 | } 41 | 42 | public static Result build(String message, Integer code, T data) { 43 | return new Result<>(message, code, data); 44 | } 45 | 46 | public static Result error(String message, Integer code) { 47 | return new Result<>(message, code, null); 48 | } 49 | 50 | public static Result error(ExceptionMessage exceptionMessage) { 51 | return new Result<>(exceptionMessage, null); 52 | } 53 | 54 | public static Result success(T data) { 55 | return new Result<>(ExceptionMessage.SUCCESS, data); 56 | } 57 | 58 | public static Result success(String messages) { 59 | return new Result<>(messages, 20000); 60 | } 61 | 62 | public String getMessages() { 63 | return messages; 64 | } 65 | 66 | public void setMessages(String messages) { 67 | this.messages = messages; 68 | } 69 | 70 | public Integer getCode() { 71 | return code; 72 | } 73 | 74 | public void setCode(Integer code) { 75 | this.code = code; 76 | } 77 | 78 | public T getData() { 79 | return data; 80 | } 81 | 82 | public void setData(T data) { 83 | this.data = data; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | /** 8 | * @author leone 9 | * @since 2018-05-22 10 | **/ 11 | @Configuration 12 | public class AppConfig { 13 | 14 | @Bean 15 | public RestTemplate restTemplate() { 16 | return new RestTemplate(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.ParameterBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.schema.ModelRef; 10 | import springfox.documentation.service.ApiInfo; 11 | import springfox.documentation.service.Contact; 12 | import springfox.documentation.service.Parameter; 13 | import springfox.documentation.spi.DocumentationType; 14 | import springfox.documentation.spring.web.plugins.Docket; 15 | 16 | import java.util.Collections; 17 | 18 | /** 19 | * @author leone 20 | * @since 2018-08-15 21 | **/ 22 | @Configuration 23 | public class SwaggerConfig { 24 | @Bean 25 | public Docket webApi() { 26 | Parameter parameter = new ParameterBuilder() 27 | .name("Authorization") 28 | .description("token") 29 | .modelRef(new ModelRef("string")) 30 | .parameterType("header") 31 | .required(false) 32 | .defaultValue("token ") 33 | .build(); 34 | return new Docket(DocumentationType.SWAGGER_2) 35 | .groupName("服务API接口文档") 36 | .apiInfo(apiInfo()) 37 | .globalOperationParameters(Collections.singletonList(parameter)) 38 | .select() 39 | .apis(RequestHandlerSelectors.basePackage("com.leone.pay.web")) 40 | .paths(PathSelectors.any()) 41 | .build(); 42 | } 43 | 44 | @Bean 45 | public Docket aliApi() { 46 | return new Docket(DocumentationType.SWAGGER_2) 47 | .groupName("支付宝API接口文档") 48 | .apiInfo(apiInfo()) 49 | .select() 50 | .apis(RequestHandlerSelectors.basePackage("com.leone.pay.ali")) 51 | .paths(PathSelectors.any()) 52 | .build(); 53 | } 54 | 55 | @Bean 56 | public Docket weiXinApi() { 57 | Parameter parameter = new ParameterBuilder() 58 | .name("Authorization") 59 | .description("token") 60 | .modelRef(new ModelRef("string")) 61 | .parameterType("header") 62 | .required(false) 63 | .defaultValue("token ") 64 | .build(); 65 | 66 | return new Docket(DocumentationType.SWAGGER_2) 67 | .groupName("微信API接口文档") 68 | .globalOperationParameters(Collections.singletonList(parameter)) 69 | .apiInfo(apiInfo()) 70 | .select() 71 | .apis(RequestHandlerSelectors.basePackage("com.andy.pay.wx")) 72 | .paths(PathSelectors.any()) 73 | .build(); 74 | } 75 | 76 | @Bean 77 | public Docket unionApi() { 78 | return new Docket(DocumentationType.SWAGGER_2) 79 | .groupName("银联API接口文档") 80 | .apiInfo(apiInfo()) 81 | .select() 82 | .apis(RequestHandlerSelectors.basePackage("com.andy.pay.unionpay")) 83 | .paths(PathSelectors.any()) 84 | .build(); 85 | } 86 | 87 | private ApiInfo apiInfo() { 88 | return new ApiInfoBuilder() 89 | .title("支付系统") 90 | .description("微信、支付宝、银联支付服务") 91 | .termsOfServiceUrl("https://leone.com") 92 | .version("v1.0.1") 93 | .license("Apache2.0") 94 | .contact(new Contact("lyon", "https://leone.com", "exklin@gmail.com")) 95 | .licenseUrl("http://www.apache.org") 96 | .build(); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/enums/ResultEnum.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.enums; 2 | 3 | /** 4 | * @author leone 5 | * @since 2018-06-03 6 | **/ 7 | public enum ResultEnum { 8 | 9 | SUCCESS(20000, "success"), 10 | ERROR(50000, "error"), 11 | USER_NOT_FOUND(40001, "用户不存在"), 12 | USER_NAME_EXIST(40002, "用户名存在"), 13 | USERNAME_PASSWORD_FAIL(40003, "用户名或密码错误"), 14 | USER_TOKEN_VALIDATE_FAIL(40004, "用户token校验失败"), 15 | USER_ID_TOKEN_NOT_MATCHING(40005, "用户id和token不匹配"), 16 | USER_ACCOUNT_STYLE_FAIL(40006, "账号格式有误"), 17 | USER_PASSWORD_IS_EMPTY(40007, "用户密码为空"), 18 | USER_PASSWORD_LENGTH_FAIL(40008, "用户密码长度必须为6-16位"), 19 | CODE_MISMATCHING(40009, "验证码不匹配"), 20 | VALIDATE_CODE_EXPIRED(40010, "验证已码过期"), 21 | GET_VALIDATE_CODE_ERROR(50010, "获取短信验证码失败"), 22 | PRODUCT_NOT_EXIST(41001, "商品不存在"), 23 | PRODUCT_STOCK_ERROR(41002, "商品库存不正确"), 24 | ORDER_NOT_EXIST(41003, "订单不存在"), 25 | ORDER_DETAIL_NOT_EXIST(41004, "订单详情不存在"), 26 | ORDER_STATUS_ERROR(41005, "订单状态不正确"), 27 | ORDER_UPDATE_FAIL(41006, "订单更新失败"), 28 | ORDER_DETAIL_EMPTY(41007, "订单详情为空"), 29 | ORDER_PAY_STATUS_ERROR(41008, "订单支付状态不正确"), 30 | CART_EMPTY(41009, "购物车为空"), 31 | ORDER_OWNER_ERROR(41010, "该订单不属于该用户"), 32 | PARAM_ERROR(41011, "参数不正确"), 33 | ORDER_CANCEL_SUCCESS(41012, "订单取消成功"), 34 | ORDER_FINISH_SUCCESS(41013, "订单完结成功"), 35 | ; 36 | 37 | private Integer code; 38 | 39 | private String message; 40 | 41 | ResultEnum(Integer code, String message) { 42 | this.code = code; 43 | this.message = message; 44 | } 45 | 46 | public Integer getCode() { 47 | return code; 48 | } 49 | 50 | public String getMessage() { 51 | return message; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/enums/status/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.enums.status; 2 | 3 | /** 4 | * @author leone 5 | * @since 2018-05-20 6 | **/ 7 | public enum OrderStatus { 8 | 9 | CREATE(0, "创建"),PAY(1, "支付"),DELIVER(2, "发货"),DELIVERY(3, "运输中"),RECEIVING(4, "收货"),CANCEL(5, "取消"),CLOSE(6, "关闭"),DELETE(7, "删除"); 10 | 11 | private int status; 12 | 13 | private String declare; 14 | 15 | OrderStatus(int status, String declare) { 16 | this.status = status; 17 | this.declare = declare; 18 | } 19 | 20 | public int getStatus() { 21 | return status; 22 | } 23 | 24 | public String getDeclare() { 25 | return declare; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/enums/type/OrderType.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.enums.type; 2 | 3 | /** 4 | *

5 | * 6 | * @author leone 7 | * @since 2019-04-23 8 | **/ 9 | public enum OrderType { 10 | 11 | CREATE(0, "微信支付"), PAY(1, "支付宝支付"), DELIVER(2, "银联支付"); 12 | 13 | private int status; 14 | 15 | private String payType; 16 | 17 | OrderType() { 18 | } 19 | 20 | OrderType(int status, String payType) { 21 | this.status = status; 22 | this.payType = payType; 23 | } 24 | 25 | public int getStatus() { 26 | return status; 27 | } 28 | 29 | public String getPayType() { 30 | return payType; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/enums/type/WxPayType.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.enums.type; 2 | 3 | /** 4 | *

5 | * 6 | * @author leone 7 | * @since 2019-04-23 8 | **/ 9 | public enum WxPayType { 10 | 11 | CREATE(0, "APP"), PAY(1, "NATIVE"), DELIVER(2, "JS_API"); 12 | 13 | private int status; 14 | 15 | private String payType; 16 | 17 | 18 | WxPayType(int status, String payType) { 19 | this.status = status; 20 | this.payType = payType; 21 | } 22 | 23 | WxPayType() { 24 | } 25 | 26 | public int getStatus() { 27 | return status; 28 | } 29 | 30 | public String getPayType() { 31 | return payType; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/exception/ExceptionMessage.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.exception; 2 | 3 | /** 4 | * @author leone 5 | **/ 6 | public enum ExceptionMessage { 7 | 8 | ERROR(50000, "异常"), 9 | SUCCESS(20000, "成功"), 10 | PERMISSION_DENIED(40001, "权限不足"), 11 | PHONE_LAYOUT_FAIL(40002, "手机号码格式不正确"), 12 | AUTH_TOKEN(40010, "auth.token.wrong"), 13 | AUTH_TOKEN_EMPTY(40011, "auth.token.empty"), 14 | AUTH_ACCOUNT_PASSWORD_WRONG(40012, "账号或密码错误"), 15 | ACCOUNT_HAS_DISABLED(40013, "账号已被禁用"), 16 | AUTH_PERMISSION(40014, "权限不足"), 17 | REQUEST_ARGUMENT(40015, "request.argument"), 18 | INTERNAL_SERVER_ERROR(40016, "server.internal"), 19 | AUTH_CAPTCHA_WRONG(40017, "验证码错误"), 20 | AUTH_CAPTCHA_LOST(40018, "验证码已失效"), 21 | DELETE_IDS_FAIL(40020, "删除的id列表有误"), 22 | DELETE_FAIL(40021, "批量删除失败"), 23 | COLLECT_TYPE_FAIL(40022, "收藏类型错误"), 24 | USER_NOT_EXIST(40019, "user.not.exist"), 25 | CAPTCHA_FAIL(40023, "验证码有误"), 26 | ACTIVITY_ORDER_STATUS_FAIL(40024, "活动订单状态不正确"), 27 | REFUNDS_CANNOT_BE_GREATER_THAN_ORDERS(40025, "退款商品不能大于订单商品数量"), 28 | ORDER_STATUS_FAIL(40027, "订单状态不正确"), 29 | AFTER_SALES_ORDER_ALREADY_EXISTS(40028, "售后订单已存在"), 30 | AVAILABLE_INTEGRAL_DEFICIENCY(40029, "可用积分不足"), 31 | USER_ID_AND_SHOPPING_CART_ID_DO_NOT_MATCH(40030, "用户id和购物车id不匹配"), 32 | THE_SHIPPING_ADDRESS_ALREADY_EXISTS(40031, "用户收货地址已存在"), 33 | WRONG_VALUE_OF_DISCOUNT_COUPONS(40032, "折扣券数值有误"), 34 | INCORRECT_COUPON_STATUS(40033, "优惠券状态有误"), 35 | INVENTORY_SHORTAGE(40034, "商品库存不足"), 36 | ORDER_NOT_EXIST(40035, "订单不存在"), 37 | PAYMENT_FAILURE(40036, "支付失败"), 38 | SIGNATURE_VERIFICATION_FAILED(40037, "签名校验失败"), 39 | WEI_XIN_PAY_FAIL(40038, "微信发起支付失败"), 40 | ACTIVITY_CLOSED(40039, "活动已结束"), 41 | THE_CAMPAIGN_HAS_ALREADY_STARTED(40041, "活动已经开始"), 42 | THE_START_TIME_CANNOT_BE_GREATER_THAN_THE_END_TIME(40041, "开始时间不能大于结束时间"), 43 | THE_START_TIME_CANNOT_BE_LESS_THAN_THE_CURRENT_TIME(40042, "开始时间不能小于当前时间"), 44 | CURRENT_ORDER_IS_NOT_SHIPPED(40043, "当前订单未发货"), 45 | XML_DATA_INCORRECTNESS(40044, "xml数据格式不正确"), 46 | WX_NATIVE_PRE_PAY_FAILED(40045, "微信扫码支付预下单失败"), 47 | WX_REFUND_FAILED(40046, "微信退款失败"), 48 | ALI_DEPOSIT_FAILED(40060, "支付宝提现失败"), 49 | ALI_DEPOSIT_SUCCESS(40061, "支付宝提现成功"), 50 | ALI_PAY_CREATE_QR_CODE_SUCCESS(40062, "支付宝生成二维码成功"), 51 | ALI_PAY_REFUND_FAILED(40063, "支付宝退款失败"), 52 | ORDER_STATUS_INCORRECTNESS(40090, "订单状态不正确") 53 | 54 | ; 55 | 56 | 57 | private Integer code; 58 | 59 | private String message; 60 | 61 | ExceptionMessage(Integer code, String message) { 62 | this.code = code; 63 | this.message = message; 64 | } 65 | 66 | ExceptionMessage() { 67 | } 68 | 69 | public Integer getCode() { 70 | return code; 71 | } 72 | 73 | public void setCode(Integer code) { 74 | this.code = code; 75 | } 76 | 77 | public String getMessage() { 78 | return message; 79 | } 80 | 81 | public void setMessage(String message) { 82 | this.message = message; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/exception/ExceptionResult.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.exception; 2 | 3 | /** 4 | * @author leone 5 | * @since 2018-08-09 6 | **/ 7 | public class ExceptionResult { 8 | 9 | private Integer code; 10 | 11 | private String message; 12 | 13 | public ExceptionResult(Integer code) { 14 | this.code = code; 15 | } 16 | 17 | public ExceptionResult(Integer code, String message) { 18 | this.code = code; 19 | this.message = message; 20 | } 21 | 22 | public Integer getCode() { 23 | return code; 24 | } 25 | 26 | public String getMessage() { 27 | return message; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.exception; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.MethodArgumentNotValidException; 7 | import org.springframework.web.bind.MissingServletRequestParameterException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | import javax.security.auth.login.FailedLoginException; 13 | 14 | /** 15 | * @author leone 16 | * @since 2018-08-09 17 | **/ 18 | @RestControllerAdvice 19 | public class GlobalExceptionHandler { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); 22 | 23 | @ResponseStatus(HttpStatus.BAD_REQUEST) 24 | @ExceptionHandler(ValidateException.class) 25 | public ExceptionResult handleBaseException(ValidateException e) { 26 | logger.error("{}", e.getMessage()); 27 | return new ExceptionResult(e.getCode(), e.getMessage()); 28 | } 29 | 30 | // @ResponseStatus(HttpStatus.FORBIDDEN) 31 | // @ExceptionHandler(AuthorizationException.class) 32 | // public ExceptionResult handleAuthorizationException(Throwable e) { 33 | // logger.error("{}", e.getMessage()); 34 | // return new ExceptionResult(ExceptionMessage.PERMISSION_DENIED.getCode(), ExceptionMessage.PERMISSION_DENIED.getMessage()); 35 | // } 36 | // 37 | // @ResponseStatus(HttpStatus.UNAUTHORIZED) 38 | // @ExceptionHandler(AuthenticationException.class) 39 | // public ExceptionResult handleAuthenticationException(Throwable e) { 40 | // logger.error("{}", e.getMessage()); 41 | // return new ExceptionResult(40003, e.getMessage()); 42 | // } 43 | 44 | @ResponseStatus(HttpStatus.BAD_REQUEST) 45 | @ExceptionHandler(FailedLoginException.class) 46 | public ExceptionResult handleFailedLoginException(FailedLoginException e) { 47 | logger.error("{}", e.getMessage()); 48 | return new ExceptionResult(40010, e.getMessage()); 49 | } 50 | 51 | @ResponseStatus(HttpStatus.BAD_REQUEST) 52 | @ExceptionHandler(IllegalArgumentException.class) 53 | public ExceptionResult handleBaseException(IllegalArgumentException e) { 54 | logger.error("{}", e.getMessage()); 55 | return new ExceptionResult(40000, e.getMessage()); 56 | } 57 | 58 | // @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 59 | // @ExceptionHandler(Throwable.class) 60 | // public ExceptionResult handleBaseException(Throwable e) { 61 | // logger.error("{}", e.getMessage()); 62 | // return new ExceptionResult(50000, e.getMessage()); 63 | // } 64 | 65 | @ResponseStatus(HttpStatus.BAD_REQUEST) 66 | @ExceptionHandler(MethodArgumentNotValidException.class) 67 | public ExceptionResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { 68 | logger.error("{}", e.getMessage()); 69 | return new ExceptionResult(40007, e.getMessage()); 70 | } 71 | 72 | @ResponseStatus(HttpStatus.BAD_REQUEST) 73 | @ExceptionHandler(MissingServletRequestParameterException.class) 74 | public ExceptionResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { 75 | logger.error("{}", e.getMessage()); 76 | return new ExceptionResult(40005, e.getMessage()); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/exception/ValidateException.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.exception; 2 | 3 | /** 4 | * @author leone 5 | * @since 2018-08-03 6 | **/ 7 | public class ValidateException extends RuntimeException { 8 | 9 | private Integer code; 10 | 11 | private String message; 12 | 13 | public ValidateException(Integer code, String message) { 14 | super(message); 15 | this.code = code; 16 | this.message = message; 17 | } 18 | 19 | public ValidateException() { 20 | } 21 | 22 | public ValidateException(ExceptionMessage messageEnum) { 23 | this.code = messageEnum.getCode(); 24 | this.message = messageEnum.getMessage(); 25 | } 26 | 27 | public Integer getCode() { 28 | return code; 29 | } 30 | 31 | @Override 32 | public String getMessage() { 33 | return message; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/property/AliProperties.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.property; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author leone 7 | * @since 2018-06-03 8 | **/ 9 | @Data 10 | public class AliProperties { 11 | 12 | /*应用id*/ 13 | public String app_id; 14 | 15 | /*商家id*/ 16 | private String mch_id; 17 | 18 | /*调用接口的url*/ 19 | private String server_url; 20 | 21 | /*应用私钥*/ 22 | public String alipay_private_key; 23 | 24 | /*支付宝公钥*/ 25 | public String alipay_public_key; 26 | 27 | /*字符编码*/ 28 | public String charset; 29 | 30 | /*签名方式*/ 31 | public String sign_type ; 32 | 33 | /*数据格式*/ 34 | public String format; 35 | 36 | /*支付回调url*/ 37 | private String notify_url; 38 | 39 | /*pc支付前台通知*/ 40 | private String return_url; 41 | 42 | /*退款url*/ 43 | private String refund_url; 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/property/AppProperties.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | /** 7 | * @author leone 8 | * @since 2018-07-31 9 | **/ 10 | @Configuration 11 | @ConfigurationProperties(prefix = "app.pay") 12 | public class AppProperties { 13 | 14 | private AliProperties ali = new AliProperties(); 15 | 16 | private WxProperties wx = new WxProperties(); 17 | 18 | public AliProperties getAli() { 19 | return ali; 20 | } 21 | 22 | public void setAli(AliProperties ali) { 23 | this.ali = ali; 24 | } 25 | 26 | public WxProperties getWx() { 27 | return wx; 28 | } 29 | 30 | public void setWx(WxProperties wx) { 31 | this.wx = wx; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/common/property/WxProperties.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.common.property; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author leone 7 | * @since 2018-06-03 8 | **/ 9 | @Data 10 | public class WxProperties { 11 | 12 | /*app id*/ 13 | private String app_id; 14 | 15 | /*商户id*/ 16 | private String mch_id; 17 | 18 | /*app secret*/ 19 | private String app_secret; 20 | 21 | /*app key*/ 22 | private String app_key; 23 | 24 | /*api key*/ 25 | private String api_key; 26 | 27 | /*交易类型*/ 28 | private String trade_type; 29 | 30 | /*合作商key*/ 31 | private String partner_key; 32 | 33 | /*退款url*/ 34 | private String notify_url; 35 | 36 | /*退款通知url*/ 37 | private String refund_notify_url; 38 | 39 | /*创建订单url*/ 40 | private String create_order_url; 41 | 42 | /*退款url*/ 43 | private String refund_url; 44 | 45 | /*授权url*/ 46 | private String auth_url; 47 | 48 | /*获取token的url*/ 49 | private String token_url; 50 | 51 | /*获取openid的url*/ 52 | private String sessionKey_url; 53 | 54 | /*证书路径*/ 55 | private String certificate_path; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/entity/Goods.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.entity; 2 | 3 | import io.swagger.annotations.ApiModelProperty; 4 | import lombok.Data; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | *

10 | * 11 | * @author leone 12 | * @since 2018-08-23 13 | **/ 14 | @Data 15 | public class Goods { 16 | 17 | @ApiModelProperty("商品id") 18 | private Long goodsId; 19 | 20 | @ApiModelProperty("分类id") 21 | private Long categoryId; 22 | 23 | @ApiModelProperty("商品名称") 24 | private String goodsName; 25 | 26 | @ApiModelProperty("商品价格 单位分") 27 | private Long goodsPrice; 28 | 29 | @ApiModelProperty("商品条码") 30 | private String goodsBarcode; 31 | 32 | @ApiModelProperty("商品库存") 33 | private Integer goodsInventory; 34 | 35 | @ApiModelProperty("商品描述") 36 | private String description; 37 | 38 | @ApiModelProperty("图片url") 39 | private String goodsPicture; 40 | 41 | @ApiModelProperty("状态") 42 | private Integer status; 43 | 44 | @ApiModelProperty("创建时间") 45 | private Date createTime; 46 | 47 | @ApiModelProperty("修改时间") 48 | private Date updateTime; 49 | 50 | @ApiModelProperty("是否删除") 51 | private boolean deleted; 52 | 53 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | *

10 | * 11 | * @author leone 12 | * @since 2018-10-27 13 | **/ 14 | @Data 15 | public class Order implements Serializable { 16 | 17 | private static final long serialVersionUID = -4683295717374444772L; 18 | 19 | private Long orderId; 20 | 21 | private Long userId; 22 | 23 | private String consignee; 24 | 25 | private Integer postFee; 26 | 27 | private Integer totalFee; 28 | 29 | private Byte status; 30 | 31 | private String remark; 32 | 33 | private String outTradeNo; 34 | 35 | private String transactionId; 36 | 37 | private String createIp; 38 | 39 | private Date createTime; 40 | 41 | private Date finishTime; 42 | 43 | private Date payTime; 44 | 45 | private boolean deleted; 46 | 47 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/entity/OrderBill.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.entity; 2 | 3 | import com.leone.pay.common.enums.type.OrderType; 4 | import lombok.Data; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | *

10 | * 11 | * @author leone 12 | * @since 2019-04-23 13 | **/ 14 | @Data 15 | public class OrderBill { 16 | 17 | private Long orderBillId; 18 | 19 | private Long userId; 20 | 21 | private OrderType orderType; 22 | 23 | private Long payAmount; 24 | 25 | private String outTradeNo; 26 | 27 | private Date createTime; 28 | 29 | private Date updateTime; 30 | 31 | private Boolean deleted; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/entity/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | *

10 | * 11 | * @author leone 12 | * @since 2018-10-23 13 | **/ 14 | @Data 15 | public class OrderItem implements Serializable { 16 | 17 | private static final long serialVersionUID = -7529968762980756395L; 18 | 19 | private String orderItemId; 20 | 21 | private String orderId; 22 | 23 | private String goodsId; 24 | 25 | private String goodsName; 26 | 27 | private Long goodsPrice; 28 | 29 | private Integer goodsCount; 30 | 31 | private String goodsPicture; 32 | 33 | private Date createTime; 34 | 35 | private Date updateTime; 36 | 37 | private Boolean deleted; 38 | 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | /** 7 | * @author leone 8 | * @since 2018-10-23 9 | **/ 10 | public class User implements Serializable { 11 | 12 | private static final long serialVersionUID = -2694291552469030037L; 13 | private Long userId; 14 | 15 | private String account; 16 | 17 | private String password; 18 | 19 | private String description; 20 | 21 | private Integer age; 22 | 23 | private String openid; 24 | 25 | private Date createTime; 26 | 27 | private boolean deleted; 28 | 29 | public User() { 30 | } 31 | 32 | public User(String account, String password, String description, Integer age, Date createTime, Boolean deleted) { 33 | this.account = account; 34 | this.password = password; 35 | this.description = description; 36 | this.age = age; 37 | this.createTime = createTime; 38 | this.deleted = deleted; 39 | } 40 | 41 | public User(Long userId, String account, String password, String description, Integer age, Date createTime, Boolean deleted) { 42 | this.userId = userId; 43 | this.account = account; 44 | this.password = password; 45 | this.description = description; 46 | this.age = age; 47 | this.createTime = createTime; 48 | this.deleted = deleted; 49 | } 50 | 51 | public Long getUserId() { 52 | return userId; 53 | } 54 | 55 | public void setUserId(Long userId) { 56 | this.userId = userId; 57 | } 58 | 59 | public String getAccount() { 60 | return account; 61 | } 62 | 63 | public void setAccount(String account) { 64 | this.account = account; 65 | } 66 | 67 | public String getPassword() { 68 | return password; 69 | } 70 | 71 | public void setPassword(String password) { 72 | this.password = password; 73 | } 74 | 75 | public String getDescription() { 76 | return description; 77 | } 78 | 79 | public void setDescription(String description) { 80 | this.description = description; 81 | } 82 | 83 | public Integer getAge() { 84 | return age; 85 | } 86 | 87 | public void setAge(Integer age) { 88 | this.age = age; 89 | } 90 | 91 | public Date getCreateTime() { 92 | return createTime; 93 | } 94 | 95 | public void setCreateTime(Date createTime) { 96 | this.createTime = createTime; 97 | } 98 | 99 | public boolean isDeleted() { 100 | return deleted; 101 | } 102 | 103 | public void setDeleted(boolean deleted) { 104 | this.deleted = deleted; 105 | } 106 | 107 | public String getOpenid() { 108 | return openid; 109 | } 110 | 111 | public void setOpenid(String openid) { 112 | this.openid = openid; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/mapper/GoodsMapper.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.mapper; 2 | 3 | import com.leone.pay.entity.Goods; 4 | import org.mapstruct.Mapper; 5 | 6 | /** 7 | *

8 | * 9 | * @author leone 10 | * @since 2019-04-23 11 | **/ 12 | @Mapper 13 | public interface GoodsMapper { 14 | 15 | int deleteByPrimaryKey(Long id); 16 | 17 | int insert(Goods record); 18 | 19 | int insertSelective(Goods record); 20 | 21 | Goods selectByPrimaryKey(Long id); 22 | 23 | int updateByPrimaryKeySelective(Goods record); 24 | 25 | int updateByPrimaryKey(Goods record); 26 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/mapper/OrderBillMapper.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.mapper; 2 | 3 | 4 | import com.leone.pay.entity.OrderBill; 5 | import org.apache.ibatis.annotations.Insert; 6 | import org.apache.ibatis.annotations.Param; 7 | import org.mapstruct.Mapper; 8 | 9 | /** 10 | *

11 | * 12 | * @author leone 13 | * @since 2019-04-23 14 | **/ 15 | @Mapper 16 | public interface OrderBillMapper { 17 | 18 | @Insert("insert into t_order_bill() values()") 19 | int save(@Param("bill") OrderBill orderBill); 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/mapper/OrderItemMapper.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.mapper; 2 | 3 | import com.leone.pay.entity.OrderItem; 4 | import org.mapstruct.Mapper; 5 | 6 | /** 7 | *

8 | * 9 | * @author leone 10 | * @since 2019-04-23 11 | **/ 12 | @Mapper 13 | public interface OrderItemMapper { 14 | 15 | int deleteByPrimaryKey(String id); 16 | 17 | int insert(OrderItem record); 18 | 19 | int insertSelective(OrderItem record); 20 | 21 | OrderItem selectByPrimaryKey(String id); 22 | 23 | int updateByPrimaryKeySelective(OrderItem record); 24 | 25 | int updateByPrimaryKey(OrderItem record); 26 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/mapper/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.mapper; 2 | 3 | import com.leone.pay.entity.Order; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.apache.ibatis.annotations.Select; 7 | 8 | /** 9 | *

10 | * 11 | * @author leone 12 | * @since 2019-04-23 13 | **/ 14 | @Mapper 15 | public interface OrderMapper { 16 | 17 | int deleteByPrimaryKey(Long id); 18 | 19 | int insert(Order record); 20 | 21 | int insertSelective(Order record); 22 | 23 | Order selectByPrimaryKey(Long id); 24 | 25 | @Select("select * from t_order where orderId = #{orderId}") 26 | Order findByOrderId(@Param("orderId") Long orderId); 27 | 28 | int updateByPrimaryKeySelective(Order record); 29 | 30 | int updateByPrimaryKey(Order record); 31 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.mapper; 2 | 3 | import com.leone.pay.entity.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | /** 7 | *

8 | * 9 | * @author leone 10 | * @since 2019-04-23 11 | **/ 12 | @Mapper 13 | public interface UserMapper { 14 | 15 | int deleteByPrimaryKey(Long id); 16 | 17 | int insert(User record); 18 | 19 | int insertSelective(User record); 20 | 21 | User findByUserId(Long id); 22 | 23 | int updateByPrimaryKeySelective(User record); 24 | 25 | int updateByPrimaryKey(User record); 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/service/OrderBillService.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.service; 2 | 3 | import com.leone.pay.entity.OrderBill; 4 | import com.leone.pay.mapper.OrderBillMapper; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.annotation.Resource; 8 | 9 | /** 10 | *

11 | * 12 | * @author leone 13 | * @since 2019-04-23 14 | **/ 15 | @Service 16 | public class OrderBillService { 17 | 18 | @Resource 19 | private OrderBillMapper orderBillMapper; 20 | 21 | /** 22 | * 保存订单流水 23 | * @param orderBill 24 | * @return 25 | */ 26 | public int save(OrderBill orderBill) { 27 | return orderBillMapper.save(orderBill); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.service; 2 | 3 | import com.leone.pay.common.exception.ExceptionMessage; 4 | import com.leone.pay.entity.Order; 5 | import com.leone.pay.mapper.OrderMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.Assert; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author leone 14 | * @since 2018-06-03 15 | **/ 16 | @Slf4j 17 | @Service 18 | public class OrderService { 19 | 20 | @Resource 21 | private OrderMapper orderMapper; 22 | 23 | /** 24 | * 查找订单 25 | * 26 | * @param orderId 27 | * @return 28 | */ 29 | public Order findOne(Long orderId) { 30 | Order order = orderMapper.findByOrderId(orderId); 31 | Assert.notNull(order, ExceptionMessage.ORDER_NOT_EXIST.getMessage()); 32 | return order; 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.service; 2 | 3 | import com.leone.pay.common.exception.ExceptionMessage; 4 | import com.leone.pay.entity.User; 5 | import com.leone.pay.mapper.UserMapper; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.Assert; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author leone 14 | * @since 2018-06-03 15 | **/ 16 | @Slf4j 17 | @Service 18 | public class UserService { 19 | 20 | @Resource 21 | private UserMapper userMapper; 22 | 23 | /** 24 | * 25 | * @param userId 26 | * @return 27 | */ 28 | public User findOne(Long userId) { 29 | User user = userMapper.findByUserId(userId); 30 | Assert.notNull(user, ExceptionMessage.USER_NOT_EXIST.getMessage()); 31 | return user; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/utils/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.http.*; 5 | import org.apache.http.client.HttpRequestRetryHandler; 6 | import org.apache.http.client.config.RequestConfig; 7 | import org.apache.http.client.methods.HttpGet; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.client.utils.URIBuilder; 10 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 11 | import org.apache.http.entity.StringEntity; 12 | import org.apache.http.impl.client.CloseableHttpClient; 13 | import org.apache.http.impl.client.HttpClients; 14 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 15 | import org.apache.http.message.BasicNameValuePair; 16 | import org.apache.http.protocol.HttpContext; 17 | import org.apache.http.ssl.SSLContexts; 18 | import org.apache.http.util.EntityUtils; 19 | 20 | import javax.net.ssl.SSLContext; 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.net.SocketException; 26 | import java.nio.charset.StandardCharsets; 27 | import java.security.KeyStore; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | /** 33 | * http请求工具类 34 | * 35 | * @author leone 36 | **/ 37 | @Slf4j 38 | public class HttpUtil { 39 | 40 | private HttpUtil() { 41 | } 42 | 43 | private final static String UTF8 = StandardCharsets.UTF_8.displayName(); 44 | 45 | private static CloseableHttpClient httpClient; 46 | 47 | static { 48 | RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000).setExpectContinueEnabled(true).build(); 49 | PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(); 50 | pool.setMaxTotal(300); 51 | pool.setDefaultMaxPerRoute(50); 52 | HttpRequestRetryHandler retryHandler = (IOException exception, int executionCount, HttpContext context) -> { 53 | if (executionCount > 1) { 54 | return false; 55 | } 56 | if (exception instanceof NoHttpResponseException) { 57 | log.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); 58 | return true; 59 | } else if (exception instanceof SocketException) { 60 | log.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]"); 61 | return true; 62 | } 63 | return false; 64 | }; 65 | httpClient = HttpClients.custom().setConnectionManager(pool).setDefaultRequestConfig(requestConfig).setRetryHandler(retryHandler).build(); 66 | } 67 | 68 | /** 69 | * @param certPath 70 | * @param password 71 | * @return 72 | * @throws Exception 73 | */ 74 | public static CloseableHttpClient sslHttpsClient(String certPath, String password) throws Exception { 75 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 76 | try (InputStream inputStream = new FileInputStream(new File(certPath))) { 77 | keyStore.load(inputStream, password.toCharArray()); 78 | } 79 | SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build(); 80 | SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 81 | return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build(); 82 | } 83 | 84 | 85 | /** 86 | * 设置请求头信息 87 | * 88 | * @param headers 89 | * @param request 90 | * @return 91 | */ 92 | private static void setHeaders(Map headers, HttpRequest request) { 93 | if (null != headers && headers.size() > 0) { 94 | for (Map.Entry entry : headers.entrySet()) { 95 | request.addHeader(entry.getKey(), entry.getValue().toString()); 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * 发送post请求请求体为xml 102 | * 103 | * @param url 104 | * @param xml 105 | * @param headers 106 | * @return 107 | */ 108 | public static String sendPostXml(String url, String xml, Map headers) { 109 | String result = null; 110 | try { 111 | HttpPost httpPost = new HttpPost(url); 112 | setHeaders(headers, httpPost); 113 | StringEntity entity = new StringEntity(xml, StandardCharsets.UTF_8); 114 | httpPost.addHeader("Content-Type", "text/xml"); 115 | httpPost.setEntity(entity); 116 | HttpResponse response = httpClient.execute(httpPost); 117 | HttpEntity responseData = response.getEntity(); 118 | result = EntityUtils.toString(responseData, StandardCharsets.UTF_8); 119 | } catch (IOException e) { 120 | e.printStackTrace(); 121 | } 122 | return result; 123 | } 124 | 125 | /** 126 | * 发送json请求 127 | * 128 | * @param url 129 | * @param json 130 | * @return 131 | */ 132 | public static String sendPostJson(String url, String json, Map headers) { 133 | String result = null; 134 | try { 135 | HttpPost httpPost = new HttpPost(url); 136 | setHeaders(headers, httpPost); 137 | StringEntity stringEntity = new StringEntity(json, StandardCharsets.UTF_8); 138 | stringEntity.setContentType("application/json"); 139 | httpPost.setEntity(stringEntity); 140 | HttpResponse response = httpClient.execute(httpPost); 141 | HttpEntity responseData = response.getEntity(); 142 | result = EntityUtils.toString(responseData, StandardCharsets.UTF_8); 143 | } catch (IOException e) { 144 | e.printStackTrace(); 145 | } 146 | return result; 147 | } 148 | 149 | /** 150 | * 发送get请求 151 | * 152 | * @param url 153 | * @param params 154 | * @param header 155 | * @return 156 | */ 157 | public static String sendGet(String url, Map params, Map header) { 158 | String result = null; 159 | try { 160 | URIBuilder builder = new URIBuilder(url); 161 | if (params != null && params.size() > 0) { 162 | List pairs = new ArrayList<>(); 163 | for (Map.Entry entry : params.entrySet()) { 164 | pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString())); 165 | } 166 | builder.setParameters(pairs); 167 | } 168 | HttpGet httpGet = new HttpGet(builder.build()); 169 | setHeaders(header, httpGet); 170 | HttpResponse response = httpClient.execute(httpGet); 171 | result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); 172 | } catch (Exception e) { 173 | e.printStackTrace(); 174 | } 175 | return result; 176 | } 177 | 178 | 179 | /** 180 | * 发送get请求 181 | * 182 | * @param url 183 | * @param xml 184 | * @param headers 185 | * @return 186 | */ 187 | public static String sendSslXmlPost(String url, String xml, Map headers, CloseableHttpClient httpClient) { 188 | String result = null; 189 | try { 190 | HttpPost httpPost = new HttpPost(url); 191 | setHeaders(headers, httpPost); 192 | StringEntity entity = new StringEntity(xml, StandardCharsets.UTF_8); 193 | httpPost.addHeader("Content-Type", "text/xml"); 194 | httpPost.setEntity(entity); 195 | HttpResponse response = httpClient.execute(httpPost); 196 | HttpEntity responseData = response.getEntity(); 197 | result = EntityUtils.toString(responseData, StandardCharsets.UTF_8); 198 | } catch (Exception e) { 199 | e.printStackTrace(); 200 | } 201 | return result; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/utils/PaymentUtils.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.utils; 2 | 3 | import com.google.zxing.BarcodeFormat; 4 | import com.google.zxing.EncodeHintType; 5 | import com.google.zxing.MultiFormatWriter; 6 | import com.google.zxing.client.j2se.MatrixToImageWriter; 7 | import com.google.zxing.common.BitMatrix; 8 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Element; 12 | import org.w3c.dom.Node; 13 | import org.w3c.dom.NodeList; 14 | 15 | import javax.crypto.KeyGenerator; 16 | import javax.crypto.Mac; 17 | import javax.crypto.SecretKey; 18 | import javax.crypto.spec.SecretKeySpec; 19 | import javax.imageio.ImageIO; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import javax.xml.bind.annotation.adapters.HexBinaryAdapter; 23 | import javax.xml.parsers.DocumentBuilder; 24 | import javax.xml.parsers.DocumentBuilderFactory; 25 | import java.awt.image.BufferedImage; 26 | import java.io.*; 27 | import java.net.URLDecoder; 28 | import java.net.URLEncoder; 29 | import java.nio.charset.StandardCharsets; 30 | import java.security.MessageDigest; 31 | import java.util.*; 32 | import java.util.regex.Matcher; 33 | import java.util.regex.Pattern; 34 | 35 | /** 36 | *

37 | * 38 | * @author leone 39 | * @since 2019-04-23 40 | **/ 41 | @Slf4j 42 | public class PaymentUtils { 43 | 44 | private PaymentUtils() { 45 | } 46 | 47 | /** 48 | * 读取 request 中的数据 49 | * 50 | * @param request 51 | * @return 52 | */ 53 | public static String getRequestData(HttpServletRequest request) { 54 | StringBuilder sb = new StringBuilder(); 55 | BufferedReader br = null; 56 | String line; 57 | try { 58 | // 接收request数据流,并指定编码格式接收 59 | br = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); 60 | while ((line = br.readLine()) != null) { 61 | sb.append(line); 62 | } 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } finally { 66 | if (br != null) { 67 | try { 68 | br.close(); 69 | } catch (IOException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | } 74 | return sb.toString(); 75 | } 76 | 77 | 78 | /** 79 | * 加密url 80 | * 81 | * @param value 82 | * @return 83 | */ 84 | public static String urlEncoder(String value) { 85 | try { 86 | return URLEncoder.encode(value, StandardCharsets.UTF_8.displayName()); 87 | } catch (UnsupportedEncodingException e) { 88 | e.printStackTrace(); 89 | } 90 | return null; 91 | } 92 | 93 | /** 94 | * 解码url 95 | * 96 | * @param value 97 | * @return 98 | */ 99 | public static String urlDecoder(String value) { 100 | try { 101 | return URLDecoder.decode(value, StandardCharsets.UTF_8.displayName()); 102 | } catch (UnsupportedEncodingException e) { 103 | e.printStackTrace(); 104 | } 105 | return null; 106 | } 107 | 108 | /** 109 | * 校验手机号 110 | * 111 | * @param phone 112 | * @return 113 | */ 114 | public static boolean isMobile(String phone) { 115 | Pattern pattern = Pattern.compile("^[1][3,4,5,7,8,9][0-9]{9}$"); 116 | Matcher matcher = pattern.matcher(phone); 117 | return matcher.matches(); 118 | } 119 | 120 | /** 121 | * 匹配ip是否合法 122 | * 123 | * @param ip 124 | * @return 125 | */ 126 | public static Boolean isIp(String ip) { 127 | String re = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; 128 | Pattern pattern = Pattern.compile(re); 129 | Matcher matcher = pattern.matcher(ip); 130 | return matcher.matches(); 131 | } 132 | 133 | 134 | /** 135 | * 支付参数生成签名 136 | * 137 | * @param params 138 | * @param apiKey 139 | * @return 140 | */ 141 | public static String sign(Map params, String apiKey) { 142 | StringBuilder sb = new StringBuilder(); 143 | Set> set = new TreeMap<>(params).entrySet(); 144 | for (Map.Entry entry : set) { 145 | String k = entry.getKey(); 146 | Object v = entry.getValue(); 147 | sb.append(k).append("=").append(v).append("&"); 148 | } 149 | sb.append("key=").append(apiKey); 150 | return Objects.requireNonNull(MD5(sb.toString())).toUpperCase(); 151 | } 152 | 153 | 154 | /** 155 | * 支付参数生成签名 156 | * 157 | * @param params 158 | * @return 159 | */ 160 | public static String sign(Map params) { 161 | StringBuilder sb = new StringBuilder(); 162 | Set> set = new TreeMap<>(params).entrySet(); 163 | for (Map.Entry entry : set) { 164 | String k = entry.getKey(); 165 | Object v = entry.getValue(); 166 | sb.append(k).append("=").append(v).append("&"); 167 | } 168 | String s = sb.toString(); 169 | return Objects.requireNonNull(MD5(s.substring(0, s.length() - 1))).toUpperCase(); 170 | } 171 | 172 | /** 173 | * 生成md5摘要 174 | * 175 | * @param content 176 | * @return 177 | */ 178 | public static String MD5(String content) { 179 | try { 180 | MessageDigest messageDigest = MessageDigest.getInstance("MD5"); 181 | messageDigest.update(content.getBytes(StandardCharsets.UTF_8)); 182 | byte[] hashCode = messageDigest.digest(); 183 | return new HexBinaryAdapter().marshal(hashCode).toLowerCase(); 184 | } catch (Exception e) { 185 | e.printStackTrace(); 186 | } 187 | return null; 188 | } 189 | 190 | /** 191 | * 生成 HMAC_SHA256 192 | * 193 | * @param content 194 | * @param api_key 195 | * @return 196 | * @throws Exception 197 | */ 198 | public static String HMAC_SHA256(String content, String api_key) { 199 | try { 200 | KeyGenerator generator = KeyGenerator.getInstance("HmacSHA256"); 201 | SecretKey secretKey = generator.generateKey(); 202 | byte[] key = secretKey.getEncoded(); 203 | SecretKey secretKeySpec = new SecretKeySpec(api_key.getBytes(), "HmacSHA256"); 204 | Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm()); 205 | mac.init(secretKeySpec); 206 | byte[] digest = mac.doFinal(content.getBytes()); 207 | return new HexBinaryAdapter().marshal(digest).toLowerCase(); 208 | } catch (Exception e) { 209 | e.printStackTrace(); 210 | } 211 | return null; 212 | 213 | } 214 | 215 | 216 | /** 217 | * XML格式字符串转换为Map 218 | * 219 | * @param xmlStr 220 | * @return 221 | */ 222 | public static Map xmlToMap(String xmlStr) { 223 | try (InputStream inputStream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8))) { 224 | Map data = new HashMap<>(); 225 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 226 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 227 | Document doc = documentBuilder.parse(inputStream); 228 | doc.getDocumentElement().normalize(); 229 | NodeList nodeList = doc.getDocumentElement().getChildNodes(); 230 | for (int idx = 0; idx < nodeList.getLength(); ++idx) { 231 | Node node = nodeList.item(idx); 232 | if (node.getNodeType() == Node.ELEMENT_NODE) { 233 | Element element = (Element) node; 234 | data.put(element.getNodeName(), element.getTextContent()); 235 | } 236 | } 237 | return data; 238 | } catch (Exception ex) { 239 | log.warn("xml convert to map failed message: {}", ex.getMessage()); 240 | return null; 241 | } 242 | } 243 | 244 | /** 245 | * map转换为xml格式 246 | * 247 | * @param params 248 | * @return 249 | */ 250 | public static String mapToXml(Map params) { 251 | StringBuilder sb = new StringBuilder(); 252 | Set> es = params.entrySet(); 253 | Iterator> it = es.iterator(); 254 | sb.append(""); 255 | while (it.hasNext()) { 256 | Map.Entry entry = it.next(); 257 | String k = entry.getKey(); 258 | Object v = entry.getValue(); 259 | sb.append("<").append(k).append(">").append(v).append(""); 260 | } 261 | sb.append(""); 262 | return sb.toString(); 263 | } 264 | 265 | public static void main(String[] args) { 266 | Map map = new HashMap<>(); 267 | map.put("e", "value1"); 268 | map.put("f", "value2"); 269 | map.put("a", "value3"); 270 | map.put("c", "value4"); 271 | System.out.println(sign(map)); 272 | } 273 | 274 | /** 275 | * 获得request的ip 276 | * 277 | * @param request 278 | * @return 279 | */ 280 | public static String getIpAddress(HttpServletRequest request) { 281 | String ip = request.getHeader("X-Forwarded-For"); 282 | if (Objects.nonNull(ip) && !"unKnown".equalsIgnoreCase(ip)) { 283 | // 多次反向代理后会有多个ip值,第一个ip才是真实ip 284 | int index = ip.indexOf(","); 285 | if (index != -1) { 286 | return ip.substring(0, index); 287 | } else { 288 | return ip; 289 | } 290 | } 291 | ip = request.getHeader("X-Real-IP"); 292 | if (Objects.nonNull(ip) && !"unKnown".equalsIgnoreCase(ip)) { 293 | return ip; 294 | } 295 | return request.getRemoteAddr(); 296 | } 297 | 298 | /** 299 | * 过滤掉关键参数 300 | * 301 | * @param param 302 | * @return 303 | */ 304 | public static HashMap paramFilter(Map param) { 305 | HashMap result = new HashMap<>(); 306 | if (param == null || param.size() <= 0) { 307 | return result; 308 | } 309 | for (String key : param.keySet()) { 310 | String value = param.get(key); 311 | if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { 312 | continue; 313 | } 314 | result.put(key, value); 315 | } 316 | return result; 317 | } 318 | 319 | /** 320 | * 把Request中的数据解析为xml 321 | * 322 | * @param request 323 | * @return 324 | */ 325 | public static String requestDataToXml(HttpServletRequest request) { 326 | try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()))) { 327 | String line; 328 | StringBuilder sb = new StringBuilder(); 329 | while ((line = bufferedReader.readLine()) != null) { 330 | sb.append(line); 331 | } 332 | return sb.toString(); 333 | } catch (Exception e) { 334 | e.printStackTrace(); 335 | } 336 | return null; 337 | } 338 | 339 | 340 | /** 341 | * 生成二维码并响应到浏览器 342 | * 343 | * @param content 344 | * @param response 345 | */ 346 | public static void createQRCode(String content, HttpServletResponse response) { 347 | int width = 300, height = 300; 348 | String format = "png"; 349 | Map hashMap = new HashMap<>(); 350 | hashMap.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8); 351 | hashMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); 352 | hashMap.put(EncodeHintType.MARGIN, 1); 353 | try { 354 | response.setHeader("Cache-control", "no-cache"); 355 | response.setHeader("Pragma", "no-cache"); 356 | response.setHeader("content-type", "image/png"); 357 | response.setCharacterEncoding(StandardCharsets.UTF_8.displayName()); 358 | response.setDateHeader("Expires", 0); 359 | BitMatrix bitMatrix = new MultiFormatWriter() 360 | .encode(content, BarcodeFormat.QR_CODE, width, height, hashMap); 361 | BufferedImage img = MatrixToImageWriter.toBufferedImage(bitMatrix); 362 | ImageIO.write(img, format, response.getOutputStream()); 363 | } catch (Exception e) { 364 | log.warn("create QRCode error message:{}", e.getMessage()); 365 | } 366 | } 367 | 368 | } 369 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/utils/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.utils; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.format.DateTimeFormatter; 5 | import java.util.Random; 6 | import java.util.UUID; 7 | 8 | /** 9 | * @author leone 10 | * @since 2018-07-04 11 | **/ 12 | public class RandomUtil { 13 | 14 | /** 15 | * 生成随机字符串 16 | * 17 | * @param length 18 | * @return 19 | */ 20 | public static String randomNum(Integer length) { 21 | if (length < 0 || length > 512) { 22 | length = 32; 23 | } 24 | StringBuilder result = new StringBuilder(); 25 | final String sources = "0123456789"; 26 | Random rand = new Random(); 27 | for (int i = 0; i < length; i++) { 28 | result.append(sources.charAt(rand.nextInt(9))); 29 | } 30 | return result.toString(); 31 | } 32 | 33 | 34 | /** 35 | * 生成32位随机数字 36 | * 37 | * @param length 38 | * @return 39 | */ 40 | public static String randomStr(Integer length) { 41 | if (length <= 0 || length > 32) { 42 | length = 32; 43 | } 44 | return UUID.randomUUID().toString().replace("-", "").substring(0, length); 45 | } 46 | 47 | /** 48 | * 获取当前时间戳,单位秒(10位) 49 | **/ 50 | public static long getTimestamp() { 51 | return System.currentTimeMillis() / 1000; 52 | } 53 | 54 | /** 55 | * 获取带格式的日期字符串 56 | * 57 | * @param length 58 | * @return 59 | */ 60 | public static String getDateStr(Integer length) { 61 | LocalDateTime localDateTime = LocalDateTime.now(); 62 | String result = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); 63 | if (length < 1 || length > 17) { 64 | return result; 65 | } else { 66 | return result.substring(0, length); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/wx/controller/WxPayController.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.wx.controller; 2 | 3 | import com.leone.pay.common.Result; 4 | import com.leone.pay.wx.service.WxPayService; 5 | import io.swagger.annotations.Api; 6 | import io.swagger.annotations.ApiOperation; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | /** 19 | * 微信支付 20 | * 21 | * @author leone 22 | * @since 2018-05-20 23 | **/ 24 | @Slf4j 25 | @RestController 26 | @Api(tags = "微信支付接口") 27 | @RequestMapping("/api/wx/pay") 28 | public class WxPayController { 29 | 30 | @Resource 31 | private WxPayService wxPayService; 32 | 33 | @GetMapping("/app") 34 | @ApiOperation("微信App支付预下单") 35 | public Result appPay(Long orderId, HttpServletRequest request, HttpServletResponse response) { 36 | return Result.success(wxPayService.appPay(request, orderId)); 37 | } 38 | 39 | @ApiOperation("微信App支付退款") 40 | @GetMapping("/app/refund") 41 | public Result appRefund(Long orderId) throws Exception { 42 | return wxPayService.wxRefund(orderId); 43 | } 44 | 45 | @GetMapping("/qrcode") 46 | @ApiOperation("微信扫码支付预下单") 47 | public Result qrCodePay(Long orderId, HttpServletRequest request, HttpServletResponse response) { 48 | return wxPayService.qrCodePay(orderId, response, request); 49 | } 50 | 51 | @GetMapping("/xcx") 52 | @ApiOperation("小程序扫支付预下单") 53 | public Result xcxPay(Long orderId, HttpServletRequest request, HttpServletResponse response) { 54 | return wxPayService.xcxPay(orderId, request); 55 | } 56 | 57 | @ApiOperation("微信支付通知") 58 | @RequestMapping(value = "/notify", method = {RequestMethod.GET, RequestMethod.POST}) 59 | public void appNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { 60 | wxPayService.notify(request, response); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/wx/pojo/WxUser.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.wx.pojo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 微信用户信息 7 | * 8 | * @author leone 9 | * @since 2018-05-19 10 | **/ 11 | @Data 12 | public class WxUser { 13 | 14 | // 普通用户的标识,对当前开发者帐号唯一 15 | private String openid; 16 | 17 | // 普通用户昵称 18 | private String nickname; 19 | 20 | // 语言 21 | private String language; 22 | 23 | // 普通用户性别,1为男性,2为女性 24 | private String sex; 25 | 26 | // 普通用户个人资料填写的省份 27 | private String province; 28 | 29 | // 普通用户个人资料填写的城市 30 | private String city; 31 | 32 | // 国家,如中国为CN 33 | private String country; 34 | 35 | // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 36 | private String headimgurl; 37 | 38 | // 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) 39 | private String[] privilege; 40 | 41 | // 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 42 | private String unionid; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/leone/pay/wx/service/WxPayService.java: -------------------------------------------------------------------------------- 1 | package com.leone.pay.wx.service; 2 | 3 | import com.leone.pay.common.Result; 4 | import com.leone.pay.common.enums.status.OrderStatus; 5 | import com.leone.pay.common.exception.ExceptionMessage; 6 | import com.leone.pay.common.exception.ValidateException; 7 | import com.leone.pay.common.property.AppProperties; 8 | import com.leone.pay.entity.Order; 9 | import com.leone.pay.entity.OrderBill; 10 | import com.leone.pay.entity.User; 11 | import com.leone.pay.service.OrderBillService; 12 | import com.leone.pay.service.OrderService; 13 | import com.leone.pay.service.UserService; 14 | import com.leone.pay.utils.HttpUtil; 15 | import com.leone.pay.utils.PaymentUtils; 16 | import com.leone.pay.utils.RandomUtil; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.apache.http.impl.client.CloseableHttpClient; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.util.Assert; 21 | 22 | import javax.annotation.Resource; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | import java.io.BufferedOutputStream; 26 | import java.io.IOException; 27 | import java.util.*; 28 | 29 | /** 30 | * @author leone 31 | * @since 2018-05-22 32 | **/ 33 | @Slf4j 34 | @Service 35 | public class WxPayService { 36 | 37 | @Resource 38 | private OrderService orderService; 39 | 40 | @Resource 41 | private UserService userService; 42 | 43 | @Resource 44 | private OrderBillService orderBillService; 45 | 46 | @Resource 47 | private AppProperties appProperties; 48 | 49 | /** 50 | * 微信App支付,请求微信下单接口生成预付款信息返回 prepay_id 给客户端 51 | * 52 | * @param request 53 | * @param orderId 54 | */ 55 | public Map appPay(HttpServletRequest request, Long orderId) { 56 | // 校验订单信息 57 | Order order = orderService.findOne(orderId); 58 | if (order.getStatus() != OrderStatus.CREATE.getStatus()) { 59 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 60 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 61 | } 62 | 63 | // 设置客户端的ip地址 64 | String spbill_create_ip = PaymentUtils.getIpAddress(request); 65 | if (!PaymentUtils.isIp(spbill_create_ip)) { 66 | spbill_create_ip = "127.0.0.1"; 67 | } 68 | 69 | String nonce_str = 1 + RandomUtil.randomStr(15); 70 | 71 | // 微信app支付十个必须要传入的参数 72 | Map params = new HashMap<>(); 73 | 74 | // 应用ID 75 | params.put("appid", appProperties.getWx().getApp_id()); 76 | // 商户号 77 | params.put("mch_id", appProperties.getWx().getMch_id()); 78 | // 随机字符串 79 | params.put("nonce_str", nonce_str); 80 | // 商品描述 81 | params.put("body", "App weChat pay!"); 82 | // 商户订单号 83 | params.put("out_trade_no", order.getOutTradeNo()); 84 | // 总金额(分) 85 | params.put("total_fee", order.getTotalFee().toString()); 86 | // 终端IP 87 | params.put("spbill_create_ip", spbill_create_ip); 88 | // 通知地址 89 | params.put("notify_url", appProperties.getWx().getNotify_url()); 90 | // 交易类型:JS_API=公众号支付、NATIVE=扫码支付、APP=app支付 91 | params.put("trade_type", "APP"); 92 | // 签名 93 | String sign = PaymentUtils.sign(params, appProperties.getWx().getApi_key()); 94 | params.put("sign", "sign"); 95 | 96 | String xmlData = PaymentUtils.mapToXml(params); 97 | 98 | // 向微信发起预支付 99 | String wxRetXmlData = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order_url(), xmlData, null); 100 | 101 | Map wxRetMapData = PaymentUtils.xmlToMap(wxRetXmlData); 102 | Assert.notNull(wxRetMapData, ExceptionMessage.XML_DATA_INCORRECTNESS.getMessage()); 103 | log.info("weChat pre pay result data: {}", wxRetMapData); 104 | 105 | // 封装参数返回App端 106 | Map result = new HashMap<>(); 107 | result.put("appid", appProperties.getWx().getApp_id()); 108 | result.put("partnerid", appProperties.getWx().getMch_id()); 109 | result.put("prepayid", wxRetMapData.get("prepay_id").toString()); 110 | result.put("noncestr", nonce_str); 111 | result.put("timestamp", RandomUtil.getDateStr(13)); 112 | result.put("package", "Sign=WXPay"); 113 | // 对返回给App端的数据进行签名 114 | result.put("sign", PaymentUtils.sign(result, appProperties.getWx().getApi_key())); 115 | return result; 116 | } 117 | 118 | /** 119 | * 微信支付通知接口 回调数据示例 120 | * 121 | * 122 | * 123 | * 124 | * 125 | * 126 | * 127 | * 128 | * 129 | * 130 | * 131 | * 132 | * 133 | * 134 | * 135 | * 136 | * 1 137 | * 138 | * 139 | * 140 | * 141 | *

142 | * 收到微信通知后应返回微信已经收到通知,不然微信会回调多次 可能会出现保存多次订单流水的现象 143 | *

144 | * 返回给微信通知示例如下 145 | * 146 | * 147 | * 148 | * 149 | * 150 | * 151 | * @param request 152 | * @param response 153 | */ 154 | public void notify(HttpServletRequest request, HttpServletResponse response) throws IOException { 155 | String wxRetXml = PaymentUtils.getRequestData(request); 156 | Map wxRetMap = PaymentUtils.xmlToMap(wxRetXml); 157 | Assert.notNull(wxRetMap, ExceptionMessage.XML_DATA_INCORRECTNESS.getMessage()); 158 | 159 | // 当返回的return_code为SUCCESS则回调成功 160 | if ("SUCCESS".equalsIgnoreCase(wxRetMap.get("return_code"))) { 161 | // 通知微信收到回调 162 | String resXml = ""; 163 | BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); 164 | out.write(resXml.getBytes()); 165 | out.flush(); 166 | out.close(); 167 | 168 | // TODO 保存订单流水 具体细节待实现 169 | orderBillService.save(new OrderBill()); 170 | 171 | log.info("notify success"); 172 | } else { 173 | log.error("notify failed"); 174 | } 175 | } 176 | 177 | /** 178 | * { 179 | * "appId":"wxxxx", 180 | * "partnerid":"xxxx", 181 | * "noncestr":"f7382ae04f15cf4e5fd5fbecf342", 182 | * "prepayid":"xxxx", 183 | * "timeStamp":"20180906095441" 184 | * "package":"Sign=WXPay", 185 | * "sign":"AE3E21CCB1DF50B65A0531000E9EF788" 186 | * } 187 | */ 188 | 189 | /** 190 | * 微信扫码支付 191 | * 192 | * @param orderId 193 | * @param response 194 | * @param request 195 | * @return 196 | * @throws Exception 197 | */ 198 | public Result qrCodePay(Long orderId, HttpServletResponse response, HttpServletRequest request) { 199 | // 校验订单信息 200 | Order order = orderService.findOne(orderId); 201 | if (order.getStatus() != OrderStatus.CREATE.getStatus()) { 202 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 203 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 204 | } 205 | 206 | String nonce_str = RandomUtil.randomStr(12); 207 | String outTradeNo = 1 + RandomUtil.randomNum(15); 208 | String spbill_create_ip = PaymentUtils.getIpAddress(request); 209 | if (!PaymentUtils.isIp(spbill_create_ip)) { 210 | spbill_create_ip = "127.0.0.1"; 211 | } 212 | Map params = new TreeMap<>(); 213 | // 扫码支付需要参数 214 | params.put("appid", appProperties.getWx().getApp_id()); 215 | params.put("mch_id", appProperties.getWx().getMch_id()); 216 | params.put("nonce_str", nonce_str); 217 | params.put("body", "微信扫码支付"); 218 | params.put("out_trade_no", outTradeNo); 219 | params.put("total_fee", order.getTotalFee()); 220 | params.put("spbill_create_ip", spbill_create_ip); 221 | params.put("notify_url", appProperties.getWx().getRefund_url()); 222 | // 交易类型:JS_API=公众号支付、NATIVE=扫码支付、APP=app支付 223 | params.put("trade_type", "NATIVE"); 224 | String sign = PaymentUtils.sign(params, appProperties.getWx().getApi_key()); 225 | params.put("sign", sign); 226 | 227 | String responseXml = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order_url(), PaymentUtils.mapToXml(params), null); 228 | Map responseMap = PaymentUtils.xmlToMap(responseXml); 229 | 230 | Assert.notNull(responseMap, ExceptionMessage.XML_DATA_INCORRECTNESS.getMessage()); 231 | // return_code为微信返回的状态码,SUCCESS表示成功,return_msg 如非空,为错误原因 签名失败 参数格式校验错误 232 | if ("SUCCESS".equalsIgnoreCase(responseMap.get("return_code")) && "SUCCESS".equals(responseMap.get("result_code"))) { 233 | log.info("wx pre pay success response: {}", responseMap); 234 | // 二维码中需要包含微信返回的信息 235 | PaymentUtils.createQRCode(responseMap.get("code_url"), response); 236 | // TODO 保存订单信息 237 | 238 | return Result.success(ExceptionMessage.SUCCESS.getMessage()); 239 | } 240 | log.error("wx pre pay error response: {}", responseMap); 241 | return Result.error(ExceptionMessage.WX_NATIVE_PRE_PAY_FAILED); 242 | } 243 | 244 | 245 | /** 246 | * 微信退款 247 | * 248 | * @param orderId 249 | * @return 250 | * @throws Exception 251 | */ 252 | public Result wxRefund(Long orderId) throws Exception { 253 | 254 | // 校验订单信息 255 | Order order = orderService.findOne(orderId); 256 | if (order.getStatus() != OrderStatus.CREATE.getStatus()) { 257 | log.error(ExceptionMessage.ORDER_STATUS_INCORRECTNESS + " orderId: {}", orderId); 258 | throw new ValidateException(ExceptionMessage.ORDER_STATUS_INCORRECTNESS); 259 | } 260 | 261 | String nonceStr = RandomUtil.randomStr(15); 262 | String out_refund_no = RandomUtil.randomStr(15); 263 | 264 | SortedMap params = new TreeMap<>(); 265 | // 公众账号ID 266 | params.put("appid", appProperties.getWx().getApp_id()); 267 | // 商户号 268 | params.put("mch_id", appProperties.getWx().getMch_id()); 269 | // 随机字符串 270 | params.put("nonce_str", nonceStr); 271 | // 商户订单号 272 | params.put("out_trade_no", order.getOutTradeNo()); 273 | // 订单金额 274 | params.put("total_fee", order.getTotalFee().toString()); 275 | // 商户退款单号 276 | params.put("out_refund_no", out_refund_no); 277 | // 退款原因 278 | params.put("refund_fee", order.getTotalFee().toString()); 279 | // 退款结果通知url 280 | params.put("notify_url", appProperties.getWx().getRefund_notify_url()); 281 | // 签名 282 | params.put("sign", PaymentUtils.sign(params, appProperties.getWx().getApi_key())); 283 | String data = PaymentUtils.mapToXml(params); 284 | 285 | // 微信退款需要证书 286 | CloseableHttpClient httpClient = HttpUtil.sslHttpsClient(appProperties.getWx().getCertificate_path(), appProperties.getWx().getApi_key()); 287 | 288 | // 向微信发起退款 289 | String responseXml = HttpUtil.sendSslXmlPost(appProperties.getWx().getRefund_url(), data, null, httpClient); 290 | 291 | Map responseMap = PaymentUtils.xmlToMap(responseXml); 292 | Assert.notNull(responseMap, ExceptionMessage.XML_DATA_INCORRECTNESS.getMessage()); 293 | // return_code为微信返回的状态码,SUCCESS表示申请退款成功,return_msg 如非空,为错误原因 签名失败 参数格式校验错误 294 | if ("SUCCESS".equalsIgnoreCase(responseMap.get("return_code"))) { 295 | log.info("wx refund success response:{}", responseMap); 296 | // 修改订单状态为退款保存退款订单等操作 297 | return Result.error(ExceptionMessage.SUCCESS); 298 | } 299 | log.error("wx refund error response:{}", responseMap); 300 | return Result.error(ExceptionMessage.WX_REFUND_FAILED); 301 | } 302 | 303 | /** 304 | * 微信小程序支付 305 | * 306 | * @param orderId 307 | * @param request 308 | */ 309 | public Result xcxPay(Long orderId, HttpServletRequest request) { 310 | Order order = orderService.findOne(orderId); 311 | User user = userService.findOne(order.getUserId()); 312 | String nonce_str = RandomUtil.randomNum(12); 313 | String outTradeNo = 1 + RandomUtil.randomNum(11); 314 | String spbill_create_ip = PaymentUtils.getIpAddress(request); 315 | if (!PaymentUtils.isIp(spbill_create_ip)) { 316 | spbill_create_ip = "127.0.0.1"; 317 | } 318 | // 小程序支付需要参数 319 | SortedMap reqMap = new TreeMap<>(); 320 | reqMap.put("appid", appProperties.getWx().getApp_id()); 321 | reqMap.put("mch_id", appProperties.getWx().getMch_id()); 322 | reqMap.put("nonce_str", nonce_str); 323 | reqMap.put("body", "Mimi Programing Pay"); 324 | reqMap.put("out_trade_no", outTradeNo); 325 | reqMap.put("total_fee", order.getTotalFee().toString()); 326 | reqMap.put("spbill_create_ip", spbill_create_ip); 327 | reqMap.put("notify_url", appProperties.getWx().getNotify_url()); 328 | reqMap.put("trade_type", appProperties.getWx().getTrade_type()); 329 | reqMap.put("openid", user.getOpenid()); 330 | String sign = PaymentUtils.sign(reqMap, appProperties.getWx().getApi_key()); 331 | reqMap.put("sign", sign); 332 | String xml = PaymentUtils.mapToXml(reqMap); 333 | String result = HttpUtil.sendPostXml(appProperties.getWx().getCreate_order_url(), xml, null); 334 | Map resData = PaymentUtils.xmlToMap(result); 335 | log.info("resData:{}", resData); 336 | if ("SUCCESS".equals(resData.get("return_code"))) { 337 | Map resultMap = new LinkedHashMap<>(); 338 | //返回的预付单信息 339 | String prepay_id = resData.get("prepay_id"); 340 | resultMap.put("appId", appProperties.getWx().getApp_id()); 341 | resultMap.put("nonceStr", nonce_str); 342 | resultMap.put("package", "prepay_id=" + prepay_id); 343 | resultMap.put("signType", "MD5"); 344 | resultMap.put("timeStamp", RandomUtil.getDateStr(14)); 345 | String paySign = PaymentUtils.sign(resultMap, appProperties.getWx().getApi_key()); 346 | resultMap.put("paySign", paySign); 347 | log.info("return data:{}", resultMap); 348 | return Result.success(resultMap); 349 | } 350 | return Result.error(ExceptionMessage.WEI_XIN_PAY_FAIL); 351 | } 352 | 353 | } 354 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.jdbc.Driver 4 | url: jdbc:mysql://localhost:3306/payment?useSSL=false 5 | username: root 6 | password: root 7 | thymeleaf: 8 | cache: false 9 | prefix: classpath:/templates/ 10 | suffix: .html 11 | mode: HTML5 12 | encoding: UTF-8 13 | servlet: 14 | content-type: text/html 15 | app: 16 | pay: 17 | wx: 18 | notify_url: http://localhost/wx/pay/order/notify 19 | redirect_url: http://xxxx/wechat/auth 20 | app_id: xxxxxx 21 | app_secret: xxxxxxxx 22 | mch_id: xxxxxxx 23 | api_key: xxxxxx 24 | cert_path: apiclient_cert.p12 25 | create_order_url: https://api.mch.wx.qq.com/pay/unifiedorder # 统一下单url 26 | refund_url: https://api.mch.wx.qq.com/secapi/pay/refund # 申请退款url 27 | 28 | ali: 29 | app_id: xxxxxxxx 30 | mch_id: xxxxxxxxx 31 | server_url: https://openapi.alipay.com/gateway.do 32 | alipay_private_key: xxxxx 33 | alipay_public_key: https://openapi.alipay.com/gateway.do 34 | charset: UTF-8 35 | sign_type: RSA2 36 | format: format 37 | notify_url: http://localhost:8080/api/pay/ali/notify 38 | return_url: http://localhost:8080/api 39 | refund_url: http://localhost:8080/api --------------------------------------------------------------------------------