Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
qianhe-slsy
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xuwenhao
qianhe-slsy
Commits
925c72e9
Commit
925c72e9
authored
Mar 15, 2024
by
yuanchao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
20240315微信支付
parent
e42fd409
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
875 additions
and
418 deletions
+875
-418
qianhe-admin/pom.xml
+36
-5
qianhe-admin/src/main/java/com/qianhe/system/config/WechatPayConfig.java
+134
-0
qianhe-admin/src/main/java/com/qianhe/system/controller/CallbackController.java
+161
-0
qianhe-admin/src/main/java/com/qianhe/system/controller/PayController.java
+178
-0
qianhe-admin/src/main/java/com/qianhe/system/controller/WaterGoodsCartController.java
+2
-0
qianhe-admin/src/main/java/com/qianhe/system/controller/WeChatPayController.java
+0
-67
qianhe-admin/src/main/java/com/qianhe/system/domain/WaterGoods.java
+2
-0
qianhe-admin/src/main/java/com/qianhe/system/domain/WeChatPay.java
+0
-69
qianhe-admin/src/main/java/com/qianhe/system/service/impl/WeChatPayService.java
+0
-109
qianhe-admin/src/main/java/com/qianhe/system/utils/WeChatPayUrl.java
+0
-25
qianhe-admin/src/main/java/com/qianhe/system/utils/WechatPayRequest.java
+121
-0
qianhe-admin/src/main/java/com/qianhe/system/utils/WechatPayValidator.java
+192
-0
qianhe-admin/src/main/java/com/qianhe/system/utils/WxChatPayCommonUtil.java
+0
-128
qianhe-admin/src/main/resources/apiclient_key.pem
+28
-0
qianhe-admin/src/main/resources/application.yml
+21
-15
No files found.
qianhe-admin/pom.xml
View file @
925c72e9
...
...
@@ -75,11 +75,11 @@
<version>
5.7.16
</version>
</dependency>
<dependency>
<!--
<dependency>
<groupId>org.wso2.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.1.wso2v1</version>
</dependency>
</dependency>
-->
<dependency>
<groupId>
org.bouncycastle
</groupId>
...
...
@@ -114,15 +114,46 @@
<version>
6.6.14
</version>
</dependency>
<!--微信支付-->
<!-- 微信支付V3 目前新版本-->
<!-- 微信支付 SDK -->
<dependency>
<groupId>
com.github.wxpay
</groupId>
<artifactId>
wxpay-sdk
</artifactId>
<version>
0.0.3
</version>
</dependency>
<!-- <dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>-->
<dependency>
<groupId>
com.github.wechatpay-apiv3
</groupId>
<artifactId>
wechatpay-apache-httpclient
</artifactId>
<version>
0.4.
9
</version>
<version>
0.4.
7
</version>
</dependency>
<dependency>
<groupId>
com.baomidou
</groupId>
<artifactId>
mybatis-plus-boot-starter
</artifactId>
<version>
3.5.1
</version>
</dependency>
<dependency>
<groupId>
com.alibaba
</groupId>
<artifactId>
fastjson
</artifactId>
<version>
1.2.72
</version>
</dependency>
<dependency>
<groupId>
com.thoughtworks.xstream
</groupId>
<artifactId>
xstream
</artifactId>
<version>
1.4.4
</version>
</dependency>
<dependency>
<groupId>
org.projectlombok
</groupId>
<artifactId>
lombok
</artifactId>
<version>
1.18.24
</version>
...
...
qianhe-admin/src/main/java/com/qianhe/system/config/WechatPayConfig.java
0 → 100644
View file @
925c72e9
package
com
.
qianhe
.
system
.
config
;
import
com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder
;
import
com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner
;
import
com.wechat.pay.contrib.apache.httpclient.auth.Verifier
;
import
com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials
;
import
com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator
;
import
com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager
;
import
com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException
;
import
com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException
;
import
com.wechat.pay.contrib.apache.httpclient.util.PemUtil
;
import
lombok.Data
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.stereotype.Component
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.nio.charset.StandardCharsets
;
import
java.security.GeneralSecurityException
;
import
java.security.PrivateKey
;
/**
* @author yc
* @version 1.0
* @className WechatPayConfig
* @date 2024/3/15 14:55
* @description
*/
@Component
@Data
@Slf4j
@ConfigurationProperties
(
prefix
=
"wx"
)
public
class
WechatPayConfig
{
/**
* 应用编号
*/
private
String
appId
;
/**
* 商户号
*/
private
String
mchId
;
/**
* APIv3密钥
*/
private
String
apiV3Key
;
/**
* 支付通知回调地址
*/
private
String
notifyUrl
;
/**
* 退款回调地址
*/
private
String
refundNotifyUrl
;
/**
* API 证书中的 key.pem
*/
private
String
keyPemPath
;
/**
* 商户序列号
*/
private
String
serialNo
;
/**
* 获取商户的私钥文件
*
* @param keyPemPath
* @return
*/
public
PrivateKey
getPrivateKey
(
String
keyPemPath
)
{
InputStream
inputStream
=
this
.
getClass
().
getClassLoader
().
getResourceAsStream
(
keyPemPath
);
if
(
inputStream
==
null
)
{
throw
new
RuntimeException
(
"私钥文件不存在"
);
}
return
PemUtil
.
loadPrivateKey
(
inputStream
);
}
/**
* 获取证书管理器实例
*
* @return
*/
@Bean
public
Verifier
getVerifier
()
throws
GeneralSecurityException
,
IOException
,
HttpCodeException
,
NotFoundException
{
log
.
info
(
"获取证书管理器实例"
);
//获取商户私钥
PrivateKey
privateKey
=
getPrivateKey
(
keyPemPath
);
//私钥签名对象
PrivateKeySigner
privateKeySigner
=
new
PrivateKeySigner
(
serialNo
,
privateKey
);
//身份认证对象
WechatPay2Credentials
wechatPay2Credentials
=
new
WechatPay2Credentials
(
mchId
,
privateKeySigner
);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager
certificatesManager
=
CertificatesManager
.
getInstance
();
certificatesManager
.
putMerchant
(
mchId
,
wechatPay2Credentials
,
apiV3Key
.
getBytes
(
StandardCharsets
.
UTF_8
));
return
certificatesManager
.
getVerifier
(
mchId
);
}
/**
* 获取支付http请求对象
*
* @param verifier
* @return
*/
@Bean
(
name
=
"wxPayClient"
)
public
CloseableHttpClient
getWxPayClient
(
Verifier
verifier
)
{
//获取商户私钥
PrivateKey
privateKey
=
getPrivateKey
(
keyPemPath
);
WechatPayHttpClientBuilder
builder
=
WechatPayHttpClientBuilder
.
create
()
.
withMerchant
(
mchId
,
serialNo
,
privateKey
)
.
withValidator
(
new
WechatPay2Validator
(
verifier
));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return
builder
.
build
();
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/controller/CallbackController.java
0 → 100644
View file @
925c72e9
package
com
.
qianhe
.
system
.
controller
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.TypeReference
;
import
com.qianhe.system.config.WechatPayConfig
;
import
com.qianhe.system.utils.WechatPayValidator
;
import
com.wechat.pay.contrib.apache.httpclient.auth.Verifier
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
javax.annotation.Resource
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.concurrent.locks.ReentrantLock
;
/**
* @author yc
* @version 1.0
* @className CallbackController
* @date 2024/3/15 15:26
* @description
*/
@RestController
@Slf4j
@RequestMapping
(
"/callback"
)
public
class
CallbackController
{
@Resource
private
WechatPayConfig
wechatPayConfig
;
@Resource
private
Verifier
verifier
;
private
final
ReentrantLock
lock
=
new
ReentrantLock
();
/**
* 支付回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping
(
"/payNotify"
)
public
Map
<
String
,
String
>
payNotify
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
log
.
info
(
"支付回调"
);
// 处理通知参数
Map
<
String
,
Object
>
bodyMap
=
getNotifyBody
(
request
);
if
(
bodyMap
==
null
)
{
return
falseMsg
(
response
);
}
log
.
warn
(
"=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ==========="
);
if
(
lock
.
tryLock
())
{
try
{
// 解密resource中的通知数据
String
resource
=
bodyMap
.
get
(
"resource"
).
toString
();
Map
<
String
,
Object
>
resourceMap
=
WechatPayValidator
.
decryptFromResource
(
resource
,
wechatPayConfig
.
getApiV3Key
(),
1
);
String
orderNo
=
resourceMap
.
get
(
"out_trade_no"
).
toString
();
// String transactionId = resourceMap.get("transaction_id").toString();
// 更改状态 获取订单号 修改订单状态为已支付
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log
.
warn
(
"=========== 根据订单号,做幂等处理 ==========="
);
}
finally
{
//要主动释放锁
lock
.
unlock
();
}
}
//成功应答
return
trueMsg
(
response
);
}
/**
* 退款回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping
(
"/refundNotify"
)
public
Map
<
String
,
String
>
refundNotify
(
HttpServletRequest
request
,
HttpServletResponse
response
)
{
log
.
info
(
"退款回调"
);
// 处理通知参数
Map
<
String
,
Object
>
bodyMap
=
getNotifyBody
(
request
);
if
(
bodyMap
==
null
)
{
return
falseMsg
(
response
);
}
log
.
warn
(
"=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ==========="
);
if
(
lock
.
tryLock
())
{
try
{
// 解密resource中的通知数据
String
resource
=
bodyMap
.
get
(
"resource"
).
toString
();
Map
<
String
,
Object
>
resourceMap
=
WechatPayValidator
.
decryptFromResource
(
resource
,
wechatPayConfig
.
getApiV3Key
(),
2
);
String
orderNo
=
resourceMap
.
get
(
"out_trade_no"
).
toString
();
// String transactionId = resourceMap.get("transaction_id").toString();
log
.
info
(
"退款所有参数"
+
resourceMap
);
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
// 更改订单状态为已退款
log
.
warn
(
"=========== 根据订单号,做幂等处理 ==========="
);
}
finally
{
//要主动释放锁
lock
.
unlock
();
}
}
//成功应答
return
trueMsg
(
response
);
}
private
Map
<
String
,
Object
>
getNotifyBody
(
HttpServletRequest
request
)
{
//处理通知参数
String
body
=
WechatPayValidator
.
readData
(
request
);
log
.
info
(
"退款回调参数:{}"
,
body
);
// 转换为Map
Map
<
String
,
Object
>
bodyMap
=
JSONObject
.
parseObject
(
body
,
new
TypeReference
<
Map
<
String
,
Object
>>()
{
});
// 微信的通知ID(通知的唯一ID)
String
notifyId
=
bodyMap
.
get
(
"id"
).
toString
();
// 验证签名信息
WechatPayValidator
wechatPayValidator
=
new
WechatPayValidator
(
verifier
,
notifyId
,
body
);
if
(!
wechatPayValidator
.
validate
(
request
))
{
log
.
error
(
"通知验签失败"
);
return
null
;
}
log
.
info
(
"通知验签成功"
);
return
bodyMap
;
}
private
Map
<
String
,
String
>
falseMsg
(
HttpServletResponse
response
)
{
Map
<
String
,
String
>
resMap
=
new
HashMap
<>(
8
);
//失败应答
response
.
setStatus
(
500
);
resMap
.
put
(
"code"
,
"ERROR"
);
resMap
.
put
(
"message"
,
"通知验签失败"
);
return
resMap
;
}
private
Map
<
String
,
String
>
trueMsg
(
HttpServletResponse
response
)
{
Map
<
String
,
String
>
resMap
=
new
HashMap
<>(
8
);
//成功应答
response
.
setStatus
(
200
);
resMap
.
put
(
"code"
,
"SUCCESS"
);
resMap
.
put
(
"message"
,
"成功"
);
return
resMap
;
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/controller/PayController.java
0 → 100644
View file @
925c72e9
package
com
.
qianhe
.
system
.
controller
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.TypeReference
;
import
com.qianhe.common.core.domain.AjaxResult
;
import
com.qianhe.system.config.WechatPayConfig
;
import
com.qianhe.system.domain.WaterGoods
;
import
com.qianhe.system.utils.WechatPayRequest
;
import
io.lettuce.core.dynamic.annotation.Param
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
javax.annotation.Resource
;
import
java.nio.charset.StandardCharsets
;
import
java.security.PrivateKey
;
import
java.security.Signature
;
import
java.util.Base64
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.Random
;
import
static
com
.
qianhe
.
common
.
core
.
domain
.
AjaxResult
.
success
;
/**
* @author yc
* @version 1.0
* @className PayController
* @date 2024/3/15 15:03
* @description
*/
@Slf4j
@RestController
@RequestMapping
(
"/pay"
)
public
class
PayController
{
@Resource
private
WechatPayConfig
wechatPayConfig
;
@Resource
private
WechatPayRequest
wechatPayRequest
;
/**
* 预支付订单生成入口
*/
@GetMapping
(
"/transactions"
)
public
AjaxResult
transactions
(
WaterGoods
goods
)
{
// 统一参数封装
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
10
);
// 1,appid:公众号或移动应用的唯一标识符。
params
.
put
(
"appid"
,
wechatPayConfig
.
getAppId
());
// 2,mch_id:商户号,由微信支付分配。
params
.
put
(
"mchid"
,
wechatPayConfig
.
getMchId
());
// 3.description body:商品描述。
params
.
put
(
"description"
,
"奥迪a4l 2023-限量款"
);
// 4.out_trade_no:商户订单号,由商户自定义。
params
.
put
(
"out_trade_no"
,
"we56f45waf4w6a5fwa"
);
// 5.notify_url:接收微信支付异步通知回调地址。
params
.
put
(
"notify_url"
,
wechatPayConfig
.
getNotifyUrl
());
// 6.total_fee:订单总金额,单位为分。
Map
<
String
,
Object
>
amountMap
=
new
HashMap
<>(
4
);
// 金额单位为分
amountMap
.
put
(
"total"
,
999999
);
amountMap
.
put
(
"currency"
,
"CNY"
);
params
.
put
(
"amount"
,
amountMap
);
// 7.openid:用户在商户appid下的唯一标识。
Map
<
String
,
Object
>
payerMap
=
new
HashMap
<>(
4
);
// openid 需要前端小程序通过用户code 请求微信接口获取用户唯一openid 不懂的看官方文档:https://developers.weixin.qq.com/doc/aispeech/miniprogram/quickuse.html
payerMap
.
put
(
"openid"
,
goods
.
getOpenId
());
params
.
put
(
"payer"
,
payerMap
);
String
paramsStr
=
JSON
.
toJSONString
(
params
);
log
.
info
(
"请求参数 ===> {}"
+
paramsStr
);
// 微信预支付下单接口路径
String
payUrl
=
"https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"
;
// 获取支付 prepay_id参数
String
resStr
=
wechatPayRequest
.
wechatHttpOrderPost
(
payUrl
,
paramsStr
);
Map
<
String
,
Object
>
resMap
=
JSONObject
.
parseObject
(
resStr
,
new
TypeReference
<
Map
<
String
,
Object
>>()
{
});
Object
prepayId
=
resMap
.
get
(
"prepay_id"
);
// 得到当前系统时间搓
String
timeStamp
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
// 获取签名
String
paySign
;
try
{
StringBuilder
sb
=
new
StringBuilder
();
// 应用id
sb
.
append
(
wechatPayConfig
.
getAppId
()).
append
(
"\n"
);
// 支付签名时间戳
sb
.
append
(
timeStamp
).
append
(
"\n"
);
// 随机字符串
sb
.
append
(
"5w7er7wa4fwa5e"
).
append
(
"\n"
);
// 预支付交易会话ID 这个要注意 key = "prepay_id=xxxxxx"
sb
.
append
(
"prepay_id="
).
append
(
prepayId
).
append
(
"\n"
);
// 签名
Signature
sign
=
Signature
.
getInstance
(
"SHA256withRSA"
);
// 获取商户私钥并进行签名
PrivateKey
privateKey
=
wechatPayConfig
.
getPrivateKey
(
wechatPayConfig
.
getKeyPemPath
());
sign
.
initSign
(
privateKey
);
sign
.
update
(
sb
.
toString
().
getBytes
(
StandardCharsets
.
UTF_8
));
// 得到签名
paySign
=
Base64
.
getEncoder
().
encodeToString
(
sign
.
sign
());
}
catch
(
Exception
e
)
{
log
.
error
(
"支付模块_生成交易签名失败!"
+
e
);
return
success
(
new
HashMap
<>());
}
// 将签名时数据和签名一起返回前端用于前端吊起支付
Map
<
String
,
Object
>
map
=
new
HashMap
<>();
// 小程序id
map
.
put
(
"appId"
,
wechatPayConfig
.
getAppId
());
// 时间戳
map
.
put
(
"timeStamp"
,
timeStamp
);
// 随机字符串
map
.
put
(
"nonceStr"
,
"56523268632356"
);
// 预支付交易会话ID
map
.
put
(
"package"
,
"prepay_id="
+
prepayId
);
// 签名方式
map
.
put
(
"signType"
,
"RSA"
);
// 签名
map
.
put
(
"paySign"
,
paySign
);
System
.
out
.
println
(
map
);
return
success
(
map
);
}
/**
* 申请退款
*/
@GetMapping
(
"/refundOrder"
)
public
String
refundOrder
()
{
log
.
info
(
"根据订单号申请退款,订单号: {}"
,
"要退款的订单号 这里写死"
);
// 退款请求路径
String
url
=
"https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"
;
// 设置参数
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
2
);
// 要退款的订单编号订单编号
params
.
put
(
"out_trade_no"
,
"57984wera64"
);
// 商户自定义退款记录单号 用于退款记录的单号 跟退款订单号不是一样的
int
outRefundNo
=
new
Random
().
nextInt
(
999999999
);
log
.
info
(
"退款申请号:{}"
,
outRefundNo
);
params
.
put
(
"out_refund_no"
,
outRefundNo
+
""
);
// 退款原因
params
.
put
(
"reason"
,
"申请退款"
);
// 退款通知回调地址
params
.
put
(
"notify_url"
,
wechatPayConfig
.
getRefundNotifyUrl
());
Map
<
String
,
Object
>
amountMap
=
new
HashMap
<>();
//退款金额,单位:分
amountMap
.
put
(
"refund"
,
999999
);
//原订单金额,单位:分
amountMap
.
put
(
"total"
,
99999
);
//退款币种
amountMap
.
put
(
"currency"
,
"CNY"
);
params
.
put
(
"amount"
,
amountMap
);
String
paramsStr
=
JSON
.
toJSONString
(
params
);
// todo 插入一条退款记录到数据库
log
.
info
(
"请求参数 ===> {}"
+
paramsStr
);
String
res
=
wechatPayRequest
.
wechatHttpPost
(
url
,
paramsStr
);
log
.
info
(
"退款结果:{}"
,
res
);
return
res
;
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/controller/WaterGoodsCartController.java
View file @
925c72e9
...
...
@@ -39,6 +39,8 @@ public class WaterGoodsCartController extends BaseController {
* @param waterGoodsCart
* @return
*/
@GetMapping
(
"/list"
)
public
TableDataInfo
list
(
WaterGoodsCart
waterGoodsCart
){
//查询全部商品图片
...
...
qianhe-admin/src/main/java/com/qianhe/system/controller/WeChatPayController.java
deleted
100644 → 0
View file @
e42fd409
package
com
.
qianhe
.
system
.
controller
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.json.JSONUtil
;
import
com.github.wxpay.sdk.WXPayUtil
;
import
com.qianhe.common.core.domain.AjaxResult
;
import
com.qianhe.system.domain.WaterGoodsCart
;
import
com.qianhe.system.domain.WeChatPay
;
import
com.qianhe.system.service.impl.WeChatPayService
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
javax.annotation.Resource
;
import
java.util.HashMap
;
import
java.util.Map
;
import
static
com
.
qianhe
.
common
.
core
.
domain
.
AjaxResult
.
success
;
/**
* @author yc
* @version 1.0
* @className WeChatPayController
* @date 2024/3/4 15:26
* @description 支付下单
*/
@RestController
@RequestMapping
(
"/weChatPay"
)
public
class
WeChatPayController
{
@Value
(
"${wx.appId}"
)
private
String
appId
;
@Value
(
"${wx.mchId}"
)
private
String
mchId
;
@Value
(
"${wx.apiKey}"
)
private
String
apiKey
;
@GetMapping
(
"/pay"
)
public
AjaxResult
orderPay
(
@RequestBody
WaterGoodsCart
goods
){
Map
<
String
,
Object
>
ResultMap
=
new
HashMap
<
String
,
Object
>();
try
{
WeChatPay
weChatPay
=
new
WeChatPay
();
weChatPay
.
setAppid
(
appId
);
//公众号appid
weChatPay
.
setMch_id
(
mchId
);
weChatPay
.
setApi_key
(
apiKey
);
//api密钥
weChatPay
.
setNonce_str
(
WXPayUtil
.
generateNonceStr
());
// 32位随机字符串
weChatPay
.
setBody
(
goods
.
getGoodsName
()+
goods
.
getGoodsTypeName
()+
goods
.
getGoodsId
());
// 商品描述
weChatPay
.
setTotal_fee
(
String
.
valueOf
(
goods
.
getGoodsPrice
()));
//标价金额
weChatPay
.
setOut_trade_no
(
IdUtil
.
simpleUUID
());
// 商户订单号 唯一
weChatPay
.
setNotify_url
(
"https://notify_url"
);
//异步回调地址
weChatPay
.
setTrade_type
(
"JSAPI"
);
// 交易类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
weChatPay
.
setAttach
(
"附加数据NO.1"
);
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
ResultMap
=
WeChatPayService
.
Unifiedorder
(
weChatPay
);
//log.info("返回结果:{}",JSONUtil.toJsonStr(ResultMap));
}
catch
(
Exception
e
)
{
ResultMap
.
put
(
"code"
,
2
);
ResultMap
.
put
(
"msg"
,
"系统异常错误代码:"
+
e
.
getMessage
());
e
.
printStackTrace
();
}
return
success
(
JSONUtil
.
toJsonStr
(
ResultMap
));
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/domain/WaterGoods.java
View file @
925c72e9
...
...
@@ -64,4 +64,6 @@ public class WaterGoods
/** 状态(1上架0下架) */
@Excel
(
name
=
"状态"
,
readConverterExp
=
"1=上架0下架"
)
private
Integer
status
;
private
String
openId
;
}
qianhe-admin/src/main/java/com/qianhe/system/domain/WeChatPay.java
deleted
100644 → 0
View file @
e42fd409
package
com
.
qianhe
.
system
.
domain
;
import
lombok.Data
;
/**
* @author yc
* @version 1.0
* @className WeChatPay
* @date 2024/3/4 13:42
* @description 微信支付预下单实体类
*/
@Data
public
class
WeChatPay
{
//返回状态码 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
public
String
return_code
;
//返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
private
String
return_msg
;
//公众账号ID 调用接口提交的公众账号ID
private
String
appid
;
//商户号 调用接口提交的商户号
private
String
mch_id
;
//api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
private
String
api_key
;
//设备号 自定义参数,可以为请求支付的终端设备号等
private
String
device_info
;
//随机字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
private
String
nonce_str
;
//签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
private
String
sign
;
//业务结果 SUCCESS SUCCESS/FAIL
private
String
result_code
;
//错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
private
String
err_code
;
//错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
private
String
err_code_des
;
//交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private
String
trade_type
;
//预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
private
String
prepay_id
;
//二维码链接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
private
String
code_url
;
//商品描述 商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private
String
body
;
//商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private
String
out_trade_no
;
//标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private
String
total_fee
;
//终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
private
String
spbill_create_ip
;
//通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
private
String
notify_url
;
//子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
private
String
sub_mch_id
;
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
private
String
attach
;
//商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
private
String
out_refund_no
;
//退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
private
String
refund_fee
;
//退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
private
String
refund_desc
;
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
private
String
time_expire
;
//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
private
String
openid
;
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/service/impl/WeChatPayService.java
deleted
100644 → 0
View file @
e42fd409
package
com
.
qianhe
.
system
.
service
.
impl
;
import
cn.hutool.core.util.ObjectUtil
;
import
com.github.wxpay.sdk.WXPayConstants
;
import
com.github.wxpay.sdk.WXPayUtil
;
import
com.qianhe.system.domain.WeChatPay
;
import
com.qianhe.system.utils.WeChatPayUrl
;
import
com.qianhe.system.utils.WxChatPayCommonUtil
;
import
java.text.DecimalFormat
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.SortedMap
;
import
java.util.TreeMap
;
/**
* @author yc
* @version 1.0
* @className WeChatPayService
* @date 2024/3/4 13:45
* @description 微信支付接口封装服务
*/
public
class
WeChatPayService
{
private
static
final
DecimalFormat
df
=
new
DecimalFormat
(
"#"
);
/**
* 微信支付统一预下单接口 请查看接口规则 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=9_1
* @param weChatPay 参数值appid 商户id等等
* @return Map<String, Object> NATIVE支付则返回二维码扫描地址
* @throws Exception
*/
public
static
Map
<
String
,
Object
>
Unifiedorder
(
WeChatPay
weChatPay
)
throws
Exception
{
Map
<
String
,
Object
>
ResultMap
=
new
HashMap
<
String
,
Object
>();
//todo 创建请求参数
SortedMap
<
String
,
String
>
req
=
new
TreeMap
<
String
,
String
>();
req
.
put
(
"appid"
,
weChatPay
.
getAppid
());
//公众号
req
.
put
(
"mch_id"
,
weChatPay
.
getMch_id
());
// 商户号
req
.
put
(
"nonce_str"
,
WXPayUtil
.
generateNonceStr
());
// 32位随机字符串
req
.
put
(
"body"
,
weChatPay
.
getBody
());
// 商品描述
req
.
put
(
"out_trade_no"
,
weChatPay
.
getOut_trade_no
());
// 商户订单号
req
.
put
(
"total_fee"
,
df
.
format
(
Double
.
parseDouble
(
weChatPay
.
getTotal_fee
())
*
100
));
// 标价金额(分)
req
.
put
(
"spbill_create_ip"
,
weChatPay
.
getSpbill_create_ip
());
// 终端IP
req
.
put
(
"notify_url"
,
weChatPay
.
getNotify_url
());
// 回调地址
req
.
put
(
"trade_type"
,
weChatPay
.
getTrade_type
());
// 交易类型
req
.
put
(
"attach"
,
weChatPay
.
getAttach
());
// 签名
if
(
ObjectUtil
.
isNotEmpty
(
weChatPay
.
getSub_mch_id
()))
{
//todo 服务商模式
req
.
put
(
"sub_mch_id"
,
weChatPay
.
getSub_mch_id
());
//子商户号 微信支付 分配的子商户号
}
if
(
ObjectUtil
.
isNotEmpty
(
weChatPay
.
getTime_expire
()))
{
//todo 设置订单结束时间
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。
req
.
put
(
"time_expire"
,
weChatPay
.
getTime_expire
());
}
if
(
ObjectUtil
.
isNotEmpty
(
weChatPay
.
getOpenid
()))
{
//todo JSAPI支付
req
.
put
(
"openid"
,
weChatPay
.
getOpenid
());
//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
}
req
.
put
(
"sign"
,
WXPayUtil
.
generateSignature
(
req
,
weChatPay
.
getApi_key
(),
WXPayConstants
.
SignType
.
MD5
));
// 签名
//todo 生成要发送的 xml
String
xmlBody
=
WXPayUtil
.
generateSignedXml
(
req
,
weChatPay
.
getApi_key
());
System
.
err
.
println
(
String
.
format
(
"微信支付预下单请求 xml 格式:\n%s"
,
xmlBody
));
//todo 发送 POST 请求 统一下单 API 并携带 xmlBody 内容,然后获得返回接口结果
String
result
=
WxChatPayCommonUtil
.
httpsRequest
(
WeChatPayUrl
.
Uifiedorder
,
"POST"
,
xmlBody
);
System
.
err
.
println
(
String
.
format
(
"%s"
,
result
));
//todo 将返回结果从 xml 格式转换为 map 格式
Map
<
String
,
String
>
WxResultMap
=
WXPayUtil
.
xmlToMap
(
result
);
//todo 判断通信状态 此字段是通信标识,非交易标识
if
(
ObjectUtil
.
isNotEmpty
(
WxResultMap
.
get
(
"return_code"
))
&&
WxResultMap
.
get
(
"return_code"
).
equals
(
"SUCCESS"
))
{
//todo 业务结果
if
(
WxResultMap
.
get
(
"result_code"
).
equals
(
"SUCCESS"
))
{
//todo 预下单成功
ResultMap
.
put
(
"code"
,
0
);
ResultMap
.
put
(
"msg"
,
"预下单成功"
);
//微信订单号
ResultMap
.
put
(
"out_trade_no"
,
weChatPay
.
getOut_trade_no
());
switch
(
WxResultMap
.
get
(
"trade_type"
))
{
case
"NATIVE"
:
//二维码地址
ResultMap
.
put
(
"QrCode"
,
WxResultMap
.
get
(
"code_url"
));
break
;
case
"MWEB"
:
//二维码地址
ResultMap
.
put
(
"mweb_url"
,
WxResultMap
.
get
(
"mweb_url"
));
break
;
case
"JSAPI"
:
//预支付交易会话标识 微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时
ResultMap
.
put
(
"prepay_id"
,
WxResultMap
.
get
(
"prepay_id"
));
break
;
}
}
else
{
//todo 下单失败
ResultMap
.
put
(
"code"
,
2
);
ResultMap
.
put
(
"msg"
,
WxResultMap
.
get
(
"err_code_des"
));
}
}
else
{
//todo 通信异常
ResultMap
.
put
(
"code"
,
2
);
ResultMap
.
put
(
"msg"
,
WxResultMap
.
get
(
"return_msg"
));
//当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
}
return
ResultMap
;
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/utils/WeChatPayUrl.java
deleted
100644 → 0
View file @
e42fd409
package
com
.
qianhe
.
system
.
utils
;
/**
* @author yc
* @version 1.0
* @className WeChatPayUrl
* @date 2024/3/4 10:17
* @description 微信支付接口Url列表
*/
public
class
WeChatPayUrl
{
//统一下单预下单接口url
public
static
final
String
Uifiedorder
=
"https://api.mch.weixin.qq.com/pay/unifiedorder"
;
//订单状态查询接口URL
public
static
final
String
Orderquery
=
"https://api.mch.weixin.qq.com/pay/orderquery"
;
//订单申请退款
public
static
final
String
Refund
=
"https://api.mch.weixin.qq.com/secapi/pay/refund"
;
//付款码 支付
public
static
final
String
MicroPay
=
"https://api.mch.weixin.qq.com/pay/micropay"
;
//微信网页授权 获取“code”请求地址
public
static
final
String
GainCodeUrl
=
"https://open.weixin.qq.com/connect/oauth2/authorize"
;
//微信网页授权 获取“code” 回调地址
public
static
final
String
GainCodeRedirect_uri
=
"http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html"
;
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/utils/WechatPayRequest.java
0 → 100644
View file @
925c72e9
package
com
.
qianhe
.
system
.
utils
;
import
com.alibaba.fastjson.JSONObject
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.http.HttpEntity
;
import
org.apache.http.HttpStatus
;
import
org.apache.http.client.methods.CloseableHttpResponse
;
import
org.apache.http.client.methods.HttpPost
;
import
org.apache.http.entity.StringEntity
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.apache.http.util.EntityUtils
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.Resource
;
/**
* @author yc
* @version 1.0
* @className WechatPayRequest
* @date 2024/3/15 14:56
* @description
*/
@Component
@Slf4j
public
class
WechatPayRequest
{
@Resource
private
CloseableHttpClient
wxPayClient
;
/**
* 支付请求
*
* @param url
* @param paramsStr
* @return
*/
public
String
wechatHttpOrderPost
(
String
url
,
String
paramsStr
)
{
try
{
HttpPost
httpPost
=
new
HttpPost
(
url
);
StringEntity
stringEntity
=
new
StringEntity
(
paramsStr
,
"utf-8"
);
stringEntity
.
setContentType
(
"application/json"
);
httpPost
.
setEntity
(
stringEntity
);
httpPost
.
setHeader
(
"Accept"
,
"application/json"
);
CloseableHttpResponse
response
=
wxPayClient
.
execute
(
httpPost
);
//响应体
HttpEntity
entity
=
response
.
getEntity
();
String
body
=
entity
==
null
?
""
:
EntityUtils
.
toString
(
entity
);
//响应状态码
int
statusCode
=
response
.
getStatusLine
().
getStatusCode
();
//处理成功,204是,关闭订单时微信返回的正常状态码
if
(
statusCode
==
HttpStatus
.
SC_OK
||
statusCode
==
HttpStatus
.
SC_NO_CONTENT
)
{
log
.
info
(
"成功, 返回结果 = "
+
body
);
}
else
{
String
msg
=
"微信支付请求失败,响应码 = "
+
statusCode
+
",返回结果 = "
+
body
;
log
.
info
(
"支付模块-生成订单 = "
+
msg
);
throw
new
RuntimeException
(
msg
);
}
return
body
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
.
getMessage
());
}
}
/**
* 退款请求
*
* @param url
* @param paramsStr
* @return
*/
public
String
wechatHttpPost
(
String
url
,
String
paramsStr
)
{
try
{
HttpPost
httpPost
=
new
HttpPost
(
url
);
StringEntity
stringEntity
=
new
StringEntity
(
paramsStr
,
"utf-8"
);
stringEntity
.
setContentType
(
"application/json"
);
httpPost
.
setEntity
(
stringEntity
);
httpPost
.
setHeader
(
"Accept"
,
"application/json"
);
CloseableHttpResponse
response
=
wxPayClient
.
execute
(
httpPost
);
//响应体
HttpEntity
entity
=
response
.
getEntity
();
String
body
=
entity
==
null
?
""
:
EntityUtils
.
toString
(
entity
);
//响应状态码
int
statusCode
=
response
.
getStatusLine
().
getStatusCode
();
//处理成功,204是,关闭订单时微信返回的正常状态码
if
(
statusCode
==
HttpStatus
.
SC_OK
||
statusCode
==
HttpStatus
.
SC_NO_CONTENT
)
{
log
.
info
(
"成功, 返回结果 = "
+
body
);
// 请求成功或已处理成功,返回成功的响应
return
"退款处理中"
;
}
else
if
(
statusCode
==
HttpStatus
.
SC_BAD_REQUEST
||
statusCode
==
HttpStatus
.
SC_INTERNAL_SERVER_ERROR
)
{
// 请求参数错误或系统错误,返回失败的响应
JSONObject
json
=
JSONObject
.
parseObject
(
body
);
return
json
.
getString
(
"message"
);
}
else
if
(
statusCode
==
HttpStatus
.
SC_FORBIDDEN
)
{
// 权限问题,没有退款权限
return
"没有退款权限"
;
}
else
if
(
statusCode
==
HttpStatus
.
SC_NOT_FOUND
)
{
// 订单号不存在
return
"订单号不存在"
;
}
else
if
(
statusCode
==
429
)
{
// 频率限制
return
"退款请求频率过高,请稍后重试"
;
}
else
if
(
statusCode
==
HttpStatus
.
SC_PAYMENT_REQUIRED
)
{
// 余额不足
return
"商户余额不足,请充值后重试"
;
}
else
{
// 其他状态码,返回通用的失败响应
return
"退款失败,请稍后重试"
;
}
}
catch
(
Exception
e
)
{
log
.
info
(
"支付模块-退款失败 = "
+
e
.
getMessage
());
JSONObject
json
=
JSONObject
.
parseObject
(
e
.
getMessage
());
return
json
.
getString
(
"message"
);
}
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/utils/WechatPayValidator.java
0 → 100644
View file @
925c72e9
package
com
.
qianhe
.
system
.
utils
;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.TypeReference
;
import
com.wechat.pay.contrib.apache.httpclient.auth.Verifier
;
import
com.wechat.pay.contrib.apache.httpclient.util.AesUtil
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.http.HttpEntity
;
import
org.apache.http.client.methods.CloseableHttpResponse
;
import
org.apache.http.util.EntityUtils
;
import
javax.servlet.http.HttpServletRequest
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.nio.charset.StandardCharsets
;
import
java.time.DateTimeException
;
import
java.time.Duration
;
import
java.time.Instant
;
import
java.util.Map
;
/**
* @author yc
* @version 1.0
* @className WechatPayValidator
* @date 2024/3/15 14:58
* @description
*/
@Slf4j
public
class
WechatPayValidator
{
/**
* 应答超时时间,单位为分钟
*/
private
static
final
long
RESPONSE_EXPIRED_MINUTES
=
5
;
private
final
Verifier
verifier
;
private
final
String
requestId
;
private
final
String
body
;
public
WechatPayValidator
(
Verifier
verifier
,
String
requestId
,
String
body
)
{
this
.
verifier
=
verifier
;
this
.
requestId
=
requestId
;
this
.
body
=
body
;
}
protected
static
IllegalArgumentException
parameterError
(
String
message
,
Object
...
args
)
{
message
=
String
.
format
(
message
,
args
);
return
new
IllegalArgumentException
(
"parameter error: "
+
message
);
}
protected
static
IllegalArgumentException
verifyFail
(
String
message
,
Object
...
args
)
{
message
=
String
.
format
(
message
,
args
);
return
new
IllegalArgumentException
(
"signature verify fail: "
+
message
);
}
public
final
boolean
validate
(
HttpServletRequest
request
)
{
try
{
//处理请求参数
validateParameters
(
request
);
//构造验签名串
String
message
=
buildMessage
(
request
);
String
serial
=
request
.
getHeader
(
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_SERIAL
);
String
signature
=
request
.
getHeader
(
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_SIGNATURE
);
//验签
if
(!
verifier
.
verify
(
serial
,
message
.
getBytes
(
StandardCharsets
.
UTF_8
),
signature
))
{
throw
verifyFail
(
"serial=[%s] message=[%s] sign=[%s], request-id=[%s]"
,
serial
,
message
,
signature
,
requestId
);
}
}
catch
(
IllegalArgumentException
e
)
{
log
.
warn
(
e
.
getMessage
());
return
false
;
}
return
true
;
}
private
void
validateParameters
(
HttpServletRequest
request
)
{
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String
[]
headers
=
{
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_SERIAL
,
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_SIGNATURE
,
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_NONCE
,
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_TIMESTAMP
};
String
header
=
null
;
for
(
String
headerName
:
headers
)
{
header
=
request
.
getHeader
(
headerName
);
if
(
header
==
null
)
{
throw
parameterError
(
"empty [%s], request-id=[%s]"
,
headerName
,
requestId
);
}
}
//判断请求是否过期
String
timestampStr
=
header
;
try
{
Instant
responseTime
=
Instant
.
ofEpochSecond
(
Long
.
parseLong
(
timestampStr
));
// 拒绝过期请求
if
(
Duration
.
between
(
responseTime
,
Instant
.
now
()).
abs
().
toMinutes
()
>=
RESPONSE_EXPIRED_MINUTES
)
{
throw
parameterError
(
"timestamp=[%s] expires, request-id=[%s]"
,
timestampStr
,
requestId
);
}
}
catch
(
DateTimeException
|
NumberFormatException
e
)
{
throw
parameterError
(
"invalid timestamp=[%s], request-id=[%s]"
,
timestampStr
,
requestId
);
}
}
private
String
buildMessage
(
HttpServletRequest
request
)
{
String
timestamp
=
request
.
getHeader
(
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_TIMESTAMP
);
String
nonce
=
request
.
getHeader
(
com
.
wechat
.
pay
.
contrib
.
apache
.
httpclient
.
constant
.
WechatPayHttpHeaders
.
WECHAT_PAY_NONCE
);
return
timestamp
+
"\n"
+
nonce
+
"\n"
+
body
+
"\n"
;
}
private
String
getResponseBody
(
CloseableHttpResponse
response
)
throws
IOException
{
HttpEntity
entity
=
response
.
getEntity
();
return
(
entity
!=
null
&&
entity
.
isRepeatable
())
?
EntityUtils
.
toString
(
entity
)
:
""
;
}
/**
* 对称解密,异步通知的加密数据
*
* @param resource 加密数据
* @param apiV3Key apiV3密钥
* @param type 1-支付,2-退款
* @return
*/
public
static
Map
<
String
,
Object
>
decryptFromResource
(
String
resource
,
String
apiV3Key
,
Integer
type
)
{
String
msg
=
type
==
1
?
"支付成功"
:
"退款成功"
;
log
.
info
(
msg
+
",回调通知,密文解密"
);
try
{
//通知数据
Map
<
String
,
String
>
resourceMap
=
JSONObject
.
parseObject
(
resource
,
new
TypeReference
<
Map
<
String
,
String
>>()
{
});
//数据密文
String
ciphertext
=
resourceMap
.
get
(
"ciphertext"
);
//随机串
String
nonce
=
resourceMap
.
get
(
"nonce"
);
//附加数据
String
associatedData
=
resourceMap
.
get
(
"associated_data"
);
log
.
info
(
"密文: {}"
,
ciphertext
);
AesUtil
aesUtil
=
new
AesUtil
(
apiV3Key
.
getBytes
(
StandardCharsets
.
UTF_8
));
String
resourceStr
=
aesUtil
.
decryptToString
(
associatedData
.
getBytes
(
StandardCharsets
.
UTF_8
),
nonce
.
getBytes
(
StandardCharsets
.
UTF_8
),
ciphertext
);
log
.
info
(
msg
+
",回调通知,解密结果 : {}"
,
resourceStr
);
return
JSONObject
.
parseObject
(
resourceStr
,
new
TypeReference
<
Map
<
String
,
Object
>>()
{
});
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"回调参数,解密失败!"
);
}
}
/**
* 将通知参数转化为字符串
*
* @param request
* @return
*/
public
static
String
readData
(
HttpServletRequest
request
)
{
BufferedReader
br
=
null
;
try
{
StringBuilder
result
=
new
StringBuilder
();
br
=
request
.
getReader
();
for
(
String
line
;
(
line
=
br
.
readLine
())
!=
null
;
)
{
if
(
result
.
length
()
>
0
)
{
result
.
append
(
"\n"
);
}
result
.
append
(
line
);
}
return
result
.
toString
();
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
finally
{
if
(
br
!=
null
)
{
try
{
br
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
}
}
}
\ No newline at end of file
qianhe-admin/src/main/java/com/qianhe/system/utils/WxChatPayCommonUtil.java
deleted
100644 → 0
View file @
e42fd409
package
com
.
qianhe
.
system
.
utils
;
import
com.qianhe.common.utils.StringUtils
;
import
javax.net.ssl.HttpsURLConnection
;
import
javax.servlet.http.HttpServletRequest
;
import
java.io.*
;
import
java.net.URL
;
/**
* @author yc
* @version 1.0
* @className WxChatPayCommonUtil
* @date 2024/3/4 10:17
* @description 自定义微信支付工具类
*/
public
class
WxChatPayCommonUtil
{
/**
* 发送 http 请求
* @param requestUrl 请求路径
* @param requestMethod 请求方式(GET/POST/PUT/DELETE/...)
* @param outputStr 请求参数体
* @return 结果信息
*/
public
static
String
httpsRequest
(
String
requestUrl
,
String
requestMethod
,
String
outputStr
)
{
try
{
URL
url
=
new
URL
(
requestUrl
);
HttpsURLConnection
conn
=
(
HttpsURLConnection
)
url
.
openConnection
();
conn
.
setDoOutput
(
true
);
conn
.
setDoInput
(
true
);
conn
.
setUseCaches
(
false
);
// 设置请求方式(GET/POST)
conn
.
setRequestMethod
(
requestMethod
);
conn
.
setRequestProperty
(
"content-type"
,
"application/x-www-form-urlencoded"
);
// 当outputStr不为null时向输出流写数据
if
(
null
!=
outputStr
)
{
OutputStream
outputStream
=
conn
.
getOutputStream
();
// 注意编码格式
outputStream
.
write
(
outputStr
.
getBytes
(
"UTF-8"
));
outputStream
.
close
();
}
// 从输入流读取返回内容
InputStream
inputStream
=
conn
.
getInputStream
();
InputStreamReader
inputStreamReader
=
new
InputStreamReader
(
inputStream
,
"utf-8"
);
BufferedReader
bufferedReader
=
new
BufferedReader
(
inputStreamReader
);
String
str
=
null
;
StringBuffer
buffer
=
new
StringBuffer
();
while
((
str
=
bufferedReader
.
readLine
())
!=
null
)
{
buffer
.
append
(
str
);
}
// 释放资源
bufferedReader
.
close
();
inputStreamReader
.
close
();
inputStream
.
close
();
inputStream
=
null
;
conn
.
disconnect
();
return
buffer
.
toString
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
/**
* 获取ip
* @param request 请求
* @return ip 地址
*/
public
static
String
getIp
(
HttpServletRequest
request
)
{
if
(
request
==
null
)
{
return
""
;
}
String
ip
=
request
.
getHeader
(
"X-Requested-For"
);
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getHeader
(
"X-Forwarded-For"
);
}
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getHeader
(
"Proxy-Client-IP"
);
}
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getHeader
(
"WL-Proxy-Client-IP"
);
}
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getHeader
(
"HTTP_CLIENT_IP"
);
}
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getHeader
(
"HTTP_X_FORWARDED_FOR"
);
}
if
(
StringUtils
.
isEmpty
(
ip
)
||
"unknown"
.
equalsIgnoreCase
(
ip
))
{
ip
=
request
.
getRemoteAddr
();
}
return
ip
;
}
/**
* 从流中读取微信返回的xml数据
* @param httpServletRequest
* @return
* @throws IOException
*/
public
static
String
readXmlFromStream
(
HttpServletRequest
httpServletRequest
)
throws
IOException
,
IOException
{
InputStream
inputStream
=
httpServletRequest
.
getInputStream
();
BufferedReader
bufferedReader
=
new
BufferedReader
(
new
InputStreamReader
(
inputStream
));
final
StringBuffer
sb
=
new
StringBuffer
();
String
line
=
null
;
try
{
while
((
line
=
bufferedReader
.
readLine
())
!=
null
)
{
sb
.
append
(
line
);
}
}
finally
{
bufferedReader
.
close
();
inputStream
.
close
();
}
return
sb
.
toString
();
}
/**
* 设置返回给微信服务器的xml信息
* @param returnCode
* @param returnMsg
* @return
*/
public
static
String
setReturnXml
(
String
returnCode
,
String
returnMsg
)
{
return
"<xml><return_code><![CDATA["
+
returnCode
+
"]]></return_code><return_msg><![CDATA["
+
returnMsg
+
"]]></return_msg></xml>"
;
}
}
\ No newline at end of file
qianhe-admin/src/main/resources/apiclient_key.pem
0 → 100644
View file @
925c72e9
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+vQS+6VGcsX6i
Au5HhEXviSfTuI+F3ZHGD/9m/3sPNxIsiA/46E9vk0+Ulxmaxr5x6KNbyko/XVmQ
MN1vxFO/rGpqk24shDWZpxyMEwMxvw4EcbhbGmzRoxmrwUbujmJznIq6JcqqaBHa
Z9LwHzVWtOZsRwHVr6KivRZr27bN3aEv26nnMTDkg1tkwi0AKG4EmxlYU5zjnFXh
3TDkOZYZsq24bXllSCXlTCCWpBgPbNyBzAshT3M9nD4GeW+sqrnsVMEbaSxjd6xg
uB28OCxyOb1BJKXEZYQJBmQ+ik8GoNQtx/00tE1Y5GPH+QONs1sYlQhu4FFTtvvS
069DIOfjAgMBAAECggEBAKg8NkxhpS9tSwGBTkRcQgdGVY+kMUtkpCgrgh2J6DQC
YhBPLq9f0HjcWQv5vobLF72G8VeL9LMxFkddImNrqmbcn7xDL6EqN9DAGijeuCmP
l8CJwY7xntvFXWYmAvd1NRc+EwqfPMPTKTQX8XEERdqlkrwcYVzmHrAl0fnugK3Q
btsO931xHGJ98CSVOMURiRmTdjKAahXYIlWfU0Xdqb+16uIPD31tLC17Kf5+Odpd
Rv9rxLBrp6FxuA+fwkKT+f3bNxLOQfmsb+0nuE/8lNe3VslV96K+CUB7MELQ3wx3
hQ5BgVa3JNpOlY5TMjyP+Yi+OEA+KJ/xPMKsieP6fFECgYEA+FxERDwoAWjn5V5x
7yVAJ4Vnd5WaIxIU6J8YDjDX5CEzXsICoubxioAPbZoC93T/6h8WGWvm3s8FfIlp
dWRIAK/YA4fqAwcEyTuk6p2upU1IAyjz09n+OshIvBZ+j/b05xFk5nMM616HZrAn
mIB1isDn8UhKPKjhx2JatOvdBqsCgYEAxJsAoizmYHYeY0aW+HX2h9LqImxCuPmK
izaMZCxq9RBGwGT/UyHXo3CBr+SILvjAYtKDi5danIygiVp76yBWBQQmKh5f0z7m
FoPRwmf7o1qMV+KuFdydLuinA5864aONOEeoQOBuWP6fJ11+37ezVTsUyuC+fjhX
1+uNnhNqg6kCgYBjXxd5bdBb8AuI/Kb9lpv6tCfX8yW/DocLJEzNsMFQ5+/T5DCF
2X2feumxYsP5GvkiRdnjxgaT86UwVRK7A6rDi5gUoZcCKxbBJXow3XJ5dVhw2zvj
8f8EqgpgJ2fwVlFa+tyyUCpFKodmkOjm3c8p+1FSeygo6TGdQz8j0JOZDQKBgQCx
+2RybFVOZAAUfXX0jc3VneGpsfohPH7okkQ7914IZmi2iXf/CTcO1a4BztBePYDk
tZCykR1NdZ1rWsetzsMwnVXzcTXsphdjsIf3B21tr243rZVNYz/ElIeFpuOGGyqg
FXbC7KnY1QC9gU13N/UYJnknRJgO6fDUHoFA5nDZqQKBgADTGuWavhnw1SCRYZhz
QtonO5hXjNjwZHcmEaEneI3+9X64TQgXY1hQe3WWLUwDNyAhlsT8y1Jl1kgL0tJP
mxz0fsu47RdFR1rX+3ZIF5jNaxR6UIN2GQLlhvB7PCl7znKpph/nVthTO681Vx3V
mP/wQpwy8cWtmbyl7lcvVRN5
-----END PRIVATE KEY-----
qianhe-admin/src/main/resources/application.yml
View file @
925c72e9
...
...
@@ -9,7 +9,7 @@ ruoyi:
# 实例演示开关
demoEnabled
:
true
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile
:
D
:/ruoyi/uploadPath
profile
:
C
:/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled
:
false
# 验证码类型 math 数组计算 char 字符验证
...
...
@@ -71,13 +71,13 @@ spring:
# redis 配置
redis
:
# 地址
host
:
1
.116.38.25
host
:
1
27.0.0.1
# 端口,默认为6379
port
:
778
9
port
:
637
9
# 数据库索引
database
:
2
# 密码
password
:
qianheRedis2021
password
:
# 连接超时时间
timeout
:
10s
lettuce
:
...
...
@@ -108,7 +108,9 @@ mybatis:
mapperLocations
:
classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation
:
classpath:mybatis/mybatis-config.xml
#mybatis-plus:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# PageHelper分页插件
pagehelper
:
helperDialect
:
mysql
...
...
@@ -138,21 +140,25 @@ wx:
# appSecret: bd486fd54bd1ea5e9b198911d765ce6a
# access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wx.appId}&secret=${wx.appSecret}
#正式
#微信小程序qppid
#微信小程序qppid
@126.com
appId
:
wx3c0181d9800dfbf2
#小程序密钥
appSecret
:
d1382ba5e014038ee38eda00d737ac43
access-token-uri
:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${
sswx.appId}&secret=${ss
wx.appSecret}
access-token-uri
:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${
wx.appId}&secret=${
wx.appSecret}
#商户号
mchId
:
xxx
mchId
:
1668408731
#证书序列号
mch-serial-no
:
xxx
serialNo
:
2562AB35D9BFE5CB875FA73954B6F51421E0F364
#api密钥
api-key
:
xxx
#回调接口地址
notify-url
:
xxx
apiV3Key
:
2562AB35D9BFE5CB875FA73954B6F5WW
#微信支付v3密钥
#apiKey: asdkjfhakjsdhf12321349898aksjhdj #微信支付v2密钥
#支付通知回调
notifyUrl
:
http://localhost:5125/callback/payNotify
#退款通知回调
refundNotifyUrl
:
http://localhost:5125/callback/refundNotify
#证书地址
key-path
:
xxx
keyPemPath
:
apiclient_key.pem
# 送水端小程序
sswx
:
...
...
@@ -160,7 +166,7 @@ sswx:
# appId: wxc89600b5b0aee68d
# appSecret: 27ebe3778435c719cc1b97f260b7e026
# access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${sswx.appId}&secret=${sswx.appSecret}
#正式
#正式
@163.com
appId
:
wx75635671bf9fe9bb
appSecret
:
5ac4f25af14d7cfb3e62870fa1719459
access-token-uri
:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${
wx.appId}&secret=${
wx.appSecret}
access-token-uri
:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${
sswx.appId}&secret=${ss
wx.appSecret}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment