Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
2ac6fcb6
authored
Oct 29, 2025
by
lijinqi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
支付回调接口V0.1
parent
842a425c
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
147 additions
and
68 deletions
+147
-68
computility-module-compute/computility-module-compute-biz/src/main/java/com/luhu/computility/module/compute/controller/admin/notify/ComputeWpgjPayController.java
+124
-66
computility-module-pay/src/main/java/com/luhu/computility/module/pay/controller/admin/notify/vo/WpgjPayNotifyDTO.java
+23
-2
No files found.
computility-module-compute/computility-module-compute-biz/src/main/java/com/luhu/computility/module/compute/controller/admin/notify/ComputeWpgjPayController.java
View file @
2ac6fcb6
...
...
@@ -22,6 +22,9 @@ import java.time.format.DateTimeFormatter;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.crypto.digest.DigestUtil
;
import
com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties
;
import
java.util.Map
;
import
java.util.TreeMap
;
import
java.util.stream.Collectors
;
/**
* 算力资源模块 - WPGJ旺铺聚合支付回调Controller
...
...
@@ -78,6 +81,124 @@ public class ComputeWpgjPayController {
}
}
@PostMapping
(
"/new-notify"
)
@PermitAll
@Operation
(
summary
=
"WPGJ支付异步回调通知(新) - 按文档通用验签"
)
public
CommonResult
<
WpgjPayNotifyRespDTO
>
notifyWpgjPayNew
(
@RequestBody
WpgjPayNotifyDTO
notifyDTO
)
{
log
.
info
(
"[notifyWpgjPayNew][Compute] 收到WPGJ支付回调(新): {}"
,
"new-notify进来了进来了————————————————————————————————————————————————————————————————————————————————————————————————"
);
WpgjPayNotifyRespDTO
response
=
new
WpgjPayNotifyRespDTO
();
try
{
log
.
info
(
"[notifyWpgjPayNew][Compute] 收到WPGJ支付回调(新): {}"
,
JsonUtils
.
toJsonString
(
notifyDTO
));
// 1. 验证签名(对所有出现的非空字段,按 ASCII 升序拼接,排除 sign)
if
(!
verifyWpgjSignature
(
toPayloadMap
(
notifyDTO
)))
{
log
.
error
(
"[notifyWpgjPayNew][Compute] WPGJ回调签名验证失败"
);
response
.
setCode
(
"99"
);
response
.
setMsg
(
"签名验证失败"
);
response
.
setTimestamp
(
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMddHHmmssSSS"
)));
return
CommonResult
.
success
(
response
);
}
String
merOrderId
=
notifyDTO
.
getMerOrderId
();
String
orderStatus
=
notifyDTO
.
getOrderStatus
();
log
.
info
(
"[notifyWpgjPayNew] 处理WPGJ支付结果,商户订单号: {}, 订单状态: {}"
,
merOrderId
,
orderStatus
);
// 2. 幂等处理:根据订单状态更新(与老接口保持一致逻辑)
if
(
WpgjOrderStatusEnum
.
isSuccess
(
orderStatus
))
{
resourceOrderService
.
updateOrderPaidByWpgj
(
Long
.
parseLong
(
merOrderId
),
notifyDTO
);
log
.
info
(
"[notifyWpgjPayNew] 支付成功处理完成,商户订单号: {}"
,
merOrderId
);
}
else
if
(
WpgjOrderStatusEnum
.
isFailedOrClosed
(
orderStatus
))
{
resourceOrderService
.
updateOrderFailedByWpgj
(
Long
.
parseLong
(
merOrderId
),
notifyDTO
);
log
.
info
(
"[notifyWpgjPayNew] 支付失败/关闭处理完成,商户订单号: {}"
,
merOrderId
);
}
else
{
log
.
info
(
"[notifyWpgjPayNew] 订单状态无需处理,商户订单号: {}, 状态: {}"
,
merOrderId
,
orderStatus
);
}
// 3. 返回成功应答
response
.
setCode
(
"00"
);
response
.
setMsg
(
"成功"
);
response
.
setTimestamp
(
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMddHHmmssSSS"
)));
return
CommonResult
.
success
(
response
);
}
catch
(
Exception
e
)
{
log
.
error
(
"[notifyWpgjPayNew][Compute] WPGJ支付回调处理失败"
,
e
);
response
.
setCode
(
"99"
);
response
.
setMsg
(
"处理失败"
);
response
.
setTimestamp
(
LocalDateTime
.
now
().
format
(
DateTimeFormatter
.
ofPattern
(
"yyyyMMddHHmmssSSS"
)));
return
CommonResult
.
success
(
response
);
}
}
/**
* Map 形式的回调请求验签:对所有出现的非空字段(排除 sign)进行 ASCII 排序拼接后 + key,再 MD5(UTF-8) 大写
*/
private
boolean
verifyWpgjSignature
(
Map
<
String
,
Object
>
payload
)
{
try
{
Object
signObj
=
payload
.
get
(
"sign"
);
String
originSign
=
signObj
==
null
?
null
:
String
.
valueOf
(
signObj
);
if
(
StrUtil
.
isBlank
(
originSign
))
{
log
.
warn
(
"[verifyWpgjSignature][Compute] 回调缺少 sign 字段"
);
return
false
;
}
// 过滤掉 sign、自身为空的字段,按 ASCII 升序
TreeMap
<
String
,
String
>
sorted
=
new
TreeMap
<>();
for
(
Map
.
Entry
<
String
,
Object
>
e
:
payload
.
entrySet
())
{
String
k
=
e
.
getKey
();
if
(
"sign"
.
equals
(
k
))
{
continue
;
}
String
v
=
e
.
getValue
()
==
null
?
null
:
String
.
valueOf
(
e
.
getValue
());
if
(
StrUtil
.
isBlank
(
v
))
{
continue
;
}
sorted
.
put
(
k
,
v
);
}
String
dataStr
=
sorted
.
entrySet
().
stream
()
.
map
(
en
->
en
.
getKey
()
+
"="
+
en
.
getValue
())
.
collect
(
Collectors
.
joining
(
"&"
));
String
signStr
=
dataStr
+
"&key="
+
wpgjPayProperties
.
getSignKey
();
String
calculatedSign
=
DigestUtil
.
md5Hex
(
signStr
).
toUpperCase
();
log
.
info
(
"[verifyWpgjSignature][Compute-New] 待签名字符串: {}"
,
dataStr
);
log
.
info
(
"[verifyWpgjSignature][Compute-New] 加签前(带key): {}"
,
signStr
);
log
.
info
(
"[verifyWpgjSignature][Compute-New] 计算签名: {},原始签名: {}"
,
calculatedSign
,
originSign
);
return
calculatedSign
.
equalsIgnoreCase
(
originSign
);
}
catch
(
Exception
e
)
{
log
.
error
(
"[verifyWpgjSignature][Compute-New] 验签异常"
,
e
);
return
false
;
}
}
/**
* 将 DTO 转换为 Map(以供应商字段名 snake_case 作为 key),便于统一验签
*/
private
Map
<
String
,
Object
>
toPayloadMap
(
WpgjPayNotifyDTO
dto
)
{
TreeMap
<
String
,
Object
>
map
=
new
TreeMap
<>();
map
.
put
(
"device_no"
,
dto
.
getDeviceNo
());
map
.
put
(
"mer_no"
,
dto
.
getMerNo
());
map
.
put
(
"mer_code"
,
dto
.
getMerCode
());
map
.
put
(
"payway_code"
,
dto
.
getPaywayCode
());
map
.
put
(
"order_id"
,
dto
.
getOrderId
());
map
.
put
(
"mer_order_id"
,
dto
.
getMerOrderId
());
map
.
put
(
"gateway_mer_order_id"
,
dto
.
getGatewayMerOrderId
());
map
.
put
(
"order_time"
,
dto
.
getOrderTime
());
map
.
put
(
"order_amt"
,
dto
.
getOrderAmt
());
map
.
put
(
"order_status"
,
dto
.
getOrderStatus
());
map
.
put
(
"trade_no"
,
dto
.
getTradeNo
());
map
.
put
(
"trade_time"
,
dto
.
getTradeTime
());
map
.
put
(
"order_title"
,
dto
.
getOrderTitle
());
map
.
put
(
"fee"
,
dto
.
getFee
());
map
.
put
(
"act_amt"
,
dto
.
getActAmt
());
map
.
put
(
"buyer_id"
,
dto
.
getBuyerId
());
map
.
put
(
"trade_top_no"
,
dto
.
getTradeTopNo
());
map
.
put
(
"card_type"
,
dto
.
getCardType
());
map
.
put
(
"sign"
,
dto
.
getSign
());
return
map
;
}
/**
* 处理WPGJ支付结果
*/
...
...
@@ -112,69 +233,7 @@ public class ComputeWpgjPayController {
* 验证WPGJ回调签名
*/
private
boolean
verifyWpgjSignature
(
WpgjPayNotifyDTO
notifyDTO
)
{
try
{
// 1. 构建待签名字符串
StringBuilder
sb
=
new
StringBuilder
();
// 按照字段名ASCII码排序拼接
sb
.
append
(
"device_no="
).
append
(
notifyDTO
.
getDeviceNo
());
sb
.
append
(
"&mer_no="
).
append
(
notifyDTO
.
getMerNo
());
sb
.
append
(
"&payway_code="
).
append
(
notifyDTO
.
getPaywayCode
());
sb
.
append
(
"&order_id="
).
append
(
notifyDTO
.
getOrderId
());
sb
.
append
(
"&mer_order_id="
).
append
(
notifyDTO
.
getMerOrderId
());
sb
.
append
(
"&order_time="
).
append
(
notifyDTO
.
getOrderTime
());
sb
.
append
(
"&order_amt="
).
append
(
notifyDTO
.
getOrderAmt
());
sb
.
append
(
"&order_status="
).
append
(
notifyDTO
.
getOrderStatus
());
// 可选字段(非空才加入)
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getMerCode
()))
{
sb
.
append
(
"&mer_code="
).
append
(
notifyDTO
.
getMerCode
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getGatewayMerOrderId
()))
{
sb
.
append
(
"&gateway_mer_order_id="
).
append
(
notifyDTO
.
getGatewayMerOrderId
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getTradeNo
()))
{
sb
.
append
(
"&trade_no="
).
append
(
notifyDTO
.
getTradeNo
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getTradeTime
()))
{
sb
.
append
(
"&trade_time="
).
append
(
notifyDTO
.
getTradeTime
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getOrderTitle
()))
{
sb
.
append
(
"&order_title="
).
append
(
notifyDTO
.
getOrderTitle
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getFee
()))
{
sb
.
append
(
"&fee="
).
append
(
notifyDTO
.
getFee
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getActAmt
()))
{
sb
.
append
(
"&act_amt="
).
append
(
notifyDTO
.
getActAmt
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getBuyerId
()))
{
sb
.
append
(
"&buyer_id="
).
append
(
notifyDTO
.
getBuyerId
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getTradeTopNo
()))
{
sb
.
append
(
"&trade_top_no="
).
append
(
notifyDTO
.
getTradeTopNo
());
}
if
(
StrUtil
.
isNotBlank
(
notifyDTO
.
getCardType
()))
{
sb
.
append
(
"&card_type="
).
append
(
notifyDTO
.
getCardType
());
}
// 2. 添加key
sb
.
append
(
"&key="
).
append
(
wpgjPayProperties
.
getSignKey
());
// 3. 计算MD5签名
String
signStr
=
sb
.
toString
();
String
calculatedSign
=
DigestUtil
.
md5Hex
(
signStr
).
toUpperCase
();
log
.
info
(
"[verifyWpgjSignature][Compute] 待签名字符串: {}"
,
signStr
);
log
.
info
(
"[verifyWpgjSignature][Compute] 计算签名: {}"
,
calculatedSign
);
log
.
info
(
"[verifyWpgjSignature][Compute] 原始签名: {}"
,
notifyDTO
.
getSign
());
// 4. 验证签名
return
calculatedSign
.
equals
(
notifyDTO
.
getSign
());
}
catch
(
Exception
e
)
{
log
.
error
(
"[verifyWpgjSignature][Compute] WPGJ签名验证异常"
,
e
);
return
false
;
}
// 复用 Map 验签,确保与文档一致(ASCII 排序 + key)
return
verifyWpgjSignature
(
toPayloadMap
(
notifyDTO
));
}
}
\ No newline at end of file
}
computility-module-pay/src/main/java/com/luhu/computility/module/pay/controller/admin/notify/vo/WpgjPayNotifyDTO.java
View file @
2ac6fcb6
package
com
.
luhu
.
computility
.
module
.
pay
.
controller
.
admin
.
notify
.
vo
;
import
com.fasterxml.jackson.annotation.JsonAlias
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
lombok.Data
;
...
...
@@ -16,68 +18,88 @@ public class WpgjPayNotifyDTO {
@Schema
(
description
=
"旺铺平台唯一设备SN号"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"设备编号不能为空"
)
@JsonProperty
(
"device_no"
)
private
String
deviceNo
;
@Schema
(
description
=
"内部商户号"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"商户号不能为空"
)
@JsonProperty
(
"mer_no"
)
private
String
merNo
;
@Schema
(
description
=
"商户代码"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"商户代码不能为空"
)
@JsonProperty
(
"mer_code"
)
private
String
merCode
;
@Schema
(
description
=
"支付通道代码"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"支付通道代码不能为空"
)
@JsonProperty
(
"payway_code"
)
private
String
paywayCode
;
@Schema
(
description
=
"旺铺订单号"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"旺铺订单号不能为空"
)
@JsonProperty
(
"order_id"
)
private
String
orderId
;
@Schema
(
description
=
"商户订单id,系统唯一"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"商户订单号不能为空"
)
@JsonProperty
(
"mer_order_id"
)
private
String
merOrderId
;
@Schema
(
description
=
"网关商户订单号"
)
@JsonProperty
(
"gateway_mer_order_id"
)
private
String
gatewayMerOrderId
;
@Schema
(
description
=
"下单时间"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"下单时间不能为空"
)
@JsonProperty
(
"order_time"
)
private
String
orderTime
;
@Schema
(
description
=
"订单金额"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"订单金额不能为空"
)
@JsonProperty
(
"order_amt"
)
private
String
orderAmt
;
@Schema
(
description
=
"订单状态"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"订单状态不能为空"
)
@JsonProperty
(
"order_status"
)
private
String
orderStatus
;
@Schema
(
description
=
"通道交易流水号"
)
@JsonProperty
(
"trade_no"
)
private
String
tradeNo
;
@Schema
(
description
=
"交易时间,支付成功返回"
)
@JsonProperty
(
"trade_time"
)
private
String
tradeTime
;
@Schema
(
description
=
"订单标题"
)
@JsonProperty
(
"order_title"
)
private
String
orderTitle
;
@Schema
(
description
=
"订单手续费"
)
@JsonProperty
(
"fee"
)
private
String
fee
;
@Schema
(
description
=
"结算金额"
)
@JsonProperty
(
"act_amt"
)
private
String
actAmt
;
@Schema
(
description
=
"买家用户号"
)
@JsonProperty
(
"buyer_id"
)
@JsonAlias
(
"buyer_user_id"
)
private
String
buyerId
;
@Schema
(
description
=
"对应微信、支付宝小票上交易单号"
)
@JsonProperty
(
"trade_top_no"
)
private
String
tradeTopNo
;
@Schema
(
description
=
"交易账户类型 C-贷记卡,D-借记卡,U-未知"
)
@JsonProperty
(
"card_type"
)
private
String
cardType
;
@Schema
(
description
=
"报文签名值"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@NotBlank
(
message
=
"签名不能为空"
)
@JsonProperty
(
"sign"
)
private
String
sign
;
}
\ No newline at end of file
}
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