@ -1,12 +1,21 @@
package com.ruoyi.applet.service.impl ;
package com.ruoyi.applet.service.impl ;
import cn.hutool.core.io.IoUtil ;
import com.alibaba.fastjson.JSON ;
import com.baomidou.mybatisplus.core.toolkit.IdWorker ;
import com.baomidou.mybatisplus.core.toolkit.IdWorker ;
import com.ruoyi.applet.transfer.TransferDetailEntityNew ;
import com.ruoyi.applet.transfer.TransferToUser ;
import com.ruoyi.applet.transfer.TransferToUser ;
import com.ruoyi.common.core.sms.AliyunSmsUtils ;
import com.ruoyi.common.core.sms.AliyunSmsUtils ;
import com.ruoyi.common.exception.ServiceException ;
import com.ruoyi.model.domain.AppUsers ;
import com.ruoyi.model.domain.AppUsers ;
import com.ruoyi.model.domain.AppletAmountLog ;
import com.ruoyi.model.domain.AppletAmountLog ;
import com.ruoyi.model.service.IAppUsersService ;
import com.ruoyi.model.service.IAppUsersService ;
import com.ruoyi.model.service.IAppletAmountLogService ;
import com.ruoyi.model.service.IAppletAmountLogService ;
import com.wechat.pay.java.core.Config ;
import com.wechat.pay.java.core.RSAPublicKeyConfig ;
import com.wechat.pay.java.core.notification.NotificationConfig ;
import com.wechat.pay.java.core.notification.NotificationParser ;
import com.wechat.pay.java.core.notification.RequestParam ;
import lombok.extern.slf4j.Slf4j ;
import lombok.extern.slf4j.Slf4j ;
import org.apache.commons.collections4.map.HashedMap ;
import org.apache.commons.collections4.map.HashedMap ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.beans.factory.annotation.Autowired ;
@ -14,16 +23,22 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component ;
import org.springframework.stereotype.Component ;
import org.springframework.transaction.annotation.Transactional ;
import org.springframework.transaction.annotation.Transactional ;
import javax.servlet.ServletException ;
import javax.servlet.http.HttpServletRequest ;
import java.io.BufferedReader ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.InputStreamReader ;
import java.math.BigDecimal ;
import java.math.BigDecimal ;
import java.time.LocalDateTime ;
import java.time.LocalDateTime ;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Map ;
import java.util.Map ;
@Slf4j
@Slf4j
@Component
@Component
public class AppletAmountService {
public class AppletAmountService {
@Autowired
@Autowired
private IAppUsersService appUsersService ;
private IAppUsersService appUsersService ;
@ -49,6 +64,10 @@ public class AppletAmountService {
/ / 商户证书序列号
/ / 商户证书序列号
@Value ( "${wechat-admin.merchantSerialNumber}" )
@Value ( "${wechat-admin.merchantSerialNumber}" )
private String certiticateSerialNo ;
private String certiticateSerialNo ;
@Value ( "${wechat.apiV3key}" )
private String apiV3key ;
@Value ( "${wechat.notifyUrlForTransfer}" )
private String notifyUrlForTransfer ;
/*************************************************************************************/
/*************************************************************************************/
/ * *
/ * *
* 微信提现基础参数
* 微信提现基础参数
@ -81,6 +100,110 @@ public class AppletAmountService {
}
}
public TransferDetailEntityNew wxPaySuccessCallback ( HttpServletRequest request ) {
String requestBody = getBodyString ( request , "UTF-8" ) ;
/ / 证书序列号 ( 微信平台 ) 验签的 “ 微信支付平台证书 ” 所对应的平台证书序列号
String wechatPaySerial = request . getHeader ( "Wechatpay-Serial" ) ;
/ / 微信传递过来的签名 验签的签名值
String wechatSignature = request . getHeader ( "Wechatpay-Signature" ) ;
/ / 验签的时间戳
String wechatTimestamp = request . getHeader ( "Wechatpay-Timestamp" ) ;
/ / 验签的随机字符串
String wechatpayNonce = request . getHeader ( "Wechatpay-Nonce" ) ;
/ / 1 . 构造 RequestParam
RequestParam requestParam = new RequestParam . Builder ( )
. serialNumber ( wechatPaySerial )
. nonce ( wechatpayNonce )
. signature ( wechatSignature )
. timestamp ( wechatTimestamp )
. body ( requestBody )
. build ( ) ;
/ / 2 . 构建Config RSAPublicKeyConfig
Config config =
new RSAPublicKeyConfig . Builder ( )
. merchantId ( mchid ) / / 微信支付的商户号
. privateKeyFromPath ( privateKeyFilePath ) / / 商户API证书私钥的存放路径
. publicKeyFromPath ( wechatPayPublicKeyFilePath ) / / 微信支付公钥的存放路径
. publicKeyId ( wechatPayPublicKeyId ) / / 微信支付公钥ID
. merchantSerialNumber ( certiticateSerialNo ) / / 商户API证书序列号
. apiV3Key ( apiV3key ) / / APIv3密钥
. build ( ) ;
log . info ( "WxPayService.wxPaySuccessCallback request : wechatPaySerial is [{}] , wechatSignature is [{}] , wechatTimestamp is [{}] , wechatpayNonce is [{}] , requestBody is [{}]" , wechatPaySerial , wechatSignature , wechatTimestamp , wechatpayNonce , requestBody ) ;
/ / 3 . 初始化 NotificationParser
NotificationParser parser = new NotificationParser ( ( NotificationConfig ) config ) ;
try {
TransferDetailEntityNew entity = parser . parse ( requestParam , TransferDetailEntityNew . class ) ;
AppletAmountLog byId = appletAmountLogService . getById ( entity . getOutBillNo ( ) ) ;
/ / ACCEPTED : 单据已受理
/ / PROCESSING : 单据处理中 , 转账结果尚未明确 , 如一直处于此状态 , 建议检查账户余额是否足够
/ / WAIT_USER_CONFIRM : 待收款用户确认 , 可拉起微信收款确认页面进行收款确认
/ / TRANSFERING : 转账中 , 转账结果尚未明确 , 可拉起微信收款确认页面再次重试确认收款
/ / SUCCESS : 转账成功
/ / FAIL : 转账失败
/ / CANCELING : 撤销中
/ / CANCELLED : 已撤销
if ( byId = = null ) {
if ( "SUCCESS" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现成功:{}" , entity ) ;
} else if ( "WAIT_USER_CONFIRM" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现待收款用户确认,可拉起微信收款确认页面进行收款确认:{}" , entity ) ;
} else if ( "TRANSFERING" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现转账中,转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款:{}" , entity ) ;
} else if ( "FAIL" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现转账失败:{}" , entity ) ;
} else if ( "CANCELING" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现撤销中:{}" , entity ) ;
} else if ( "CANCELLED" . equals ( entity . getState ( ) ) ) {
log . info ( "微信提现已撤销:{}" , entity ) ;
}
}
log . info ( "WxPayService.wxPaySuccessCallback responseBody: {}" , entity ! = null ? JSON . toJSONString ( entity ) : null ) ;
return entity ;
} catch ( Exception e ) {
log . error ( "Exception occurred while processing" , e ) ;
throw new ServiceException ( "系统内部错误" ) ;
}
}
/ * *
* 获取post请求中的Body
*
* @param request httpRequest
* @return body字符串
* /
public static String getBodyString ( HttpServletRequest request , String charSet ) {
StringBuilder sb = new StringBuilder ( ) ;
InputStream inputStream = null ;
BufferedReader reader = null ;
try {
inputStream = request . getInputStream ( ) ;
/ / 读取流并将流写出去 , 避免数据流中断 ;
reader = new BufferedReader ( new InputStreamReader ( inputStream , charSet ) ) ;
String line ;
while ( ( line = reader . readLine ( ) ) ! = null ) {
sb . append ( line ) ;
}
} catch ( IOException e ) {
log . error ( "获取requestBody异常" , e ) ;
} finally {
IoUtil . close ( inputStream ) ;
IoUtil . close ( reader ) ;
}
return sb . toString ( ) ;
}
public TransferToUser . TransferToUserResponse cashOut ( AppletAmountLog appletAmountLog ) {
public TransferToUser . TransferToUserResponse cashOut ( AppletAmountLog appletAmountLog ) {
AppUsers user = appUsersService . selectAppUsersByUserId ( appletAmountLog . getUserId ( ) ) ;
AppUsers user = appUsersService . selectAppUsersByUserId ( appletAmountLog . getUserId ( ) ) ;
@ -91,8 +214,6 @@ public class AppletAmountService {
map . put ( "openid" , user . getOpenid ( ) ) ; / / 用户openid ( 小程序 )
map . put ( "openid" , user . getOpenid ( ) ) ; / / 用户openid ( 小程序 )
map . put ( "userName" , appletAmountLog . getNameValue ( ) ) ; / / 用户真实姓名
map . put ( "userName" , appletAmountLog . getNameValue ( ) ) ; / / 用户真实姓名
map . put ( "transferAmount" , appletAmountLog . getAmount ( ) ) ; / / 提现金额 , 单位为 “ 分 ”
map . put ( "transferAmount" , appletAmountLog . getAmount ( ) ) ; / / 提现金额 , 单位为 “ 分 ”
String idStr = "H" + IdWorker . getIdStr ( ) ;
map . put ( "outBillNo" , idStr ) ; / / 商户单号
TransferToUser client = new TransferToUser (
TransferToUser client = new TransferToUser (
map . get ( "mchid" ) . toString ( ) , / / 商户号 , 是由微信支付系统生成并分配给每个商户的唯一标识符 , 商户号获取方式参考 https : / / pay . weixin . qq . com / doc / v3 / merchant / 4013070756
map . get ( "mchid" ) . toString ( ) , / / 商户号 , 是由微信支付系统生成并分配给每个商户的唯一标识符 , 商户号获取方式参考 https : / / pay . weixin . qq . com / doc / v3 / merchant / 4013070756
@ -105,13 +226,13 @@ public class AppletAmountService {
/ / 2 、 场景信息
/ / 2 、 场景信息
TransferToUser . TransferToUserRequest request = new TransferToUser . TransferToUserRequest ( ) ;
TransferToUser . TransferToUserRequest request = new TransferToUser . TransferToUserRequest ( ) ;
request . appid = map . get ( "appid" ) . toString ( ) ;
request . appid = map . get ( "appid" ) . toString ( ) ;
request . outBillNo = m ap. get ( "outBillNo" ) . toString ( ) ;
request . outBillNo = appletAmountLog . getId ( ) . toString ( ) ;
request . transferSceneId = map . get ( "transferSceneId" ) . toString ( ) ;
request . transferSceneId = map . get ( "transferSceneId" ) . toString ( ) ;
request . openid = map . get ( "openid" ) . toString ( ) ;
request . openid = map . get ( "openid" ) . toString ( ) ;
request . userName = client . encrypt ( map . get ( "userName" ) . toString ( ) ) ;
request . userName = client . encrypt ( map . get ( "userName" ) . toString ( ) ) ;
request . transferAmount = appletAmountLog . getAmount ( ) . multiply ( new BigDecimal ( 100 ) ) . longValue ( ) ; / / 单位为分
request . transferAmount = appletAmountLog . getAmount ( ) . multiply ( new BigDecimal ( 100 ) ) . longValue ( ) ; / / 单位为分
request . transferRemark = map . get ( "transferRemark" ) . toString ( ) ;
request . transferRemark = map . get ( "transferRemark" ) . toString ( ) ;
request . notifyUrl = map . get ( "notifyUrl" ) . toString ( ) ;
request . notifyUrl = notifyUrlForTransfer ;
request . userRecvPerception = map . get ( "userRecvPerception" ) . toString ( ) ;
request . userRecvPerception = map . get ( "userRecvPerception" ) . toString ( ) ;
request . transferSceneReportInfos = new ArrayList < > ( ) ;
request . transferSceneReportInfos = new ArrayList < > ( ) ;
{
{
@ -223,6 +344,7 @@ public class AppletAmountService {
appletAmountLog . setAuditStatus ( 2 ) ; / / 2 - 审核不通过
appletAmountLog . setAuditStatus ( 2 ) ; / / 2 - 审核不通过
appletAmountLog . setState ( 2 ) ; / / 2 - 已退回
appletAmountLog . setState ( 2 ) ; / / 2 - 已退回
appletAmountLog . setRemark ( auditRemark ) ; / / 设置审核备注
appletAmountLog . setRemark ( auditRemark ) ; / / 设置审核备注
appletAmountLog . setAuditTime ( LocalDateTime . now ( ) ) ; / / 设置审核时间
appletAmountLogService . updateById ( appletAmountLog ) ;
appletAmountLogService . updateById ( appletAmountLog ) ;
/ / 6 . 记录退款流水
/ / 6 . 记录退款流水
@ -257,6 +379,132 @@ public class AppletAmountService {
}
}
}
}
/ * *
* 批量处理超时提现申请
* 处理审核通过后超过48小时未领取的提现申请 , 自动退回到用户账户
* @return 处理的记录数量
* /
@Transactional
public int processTimeoutWithdrawals ( ) {
try {
/ / 查询审核通过后超过48小时未领取的提现记录
/ / 条件 : 审核状态为通过 ( 1 ) , 处理状态为处理中 ( 0 ) , 审核时间超过48小时
LocalDateTime timeoutThreshold = LocalDateTime . now ( ) . minusHours ( 47 ) . minusMinutes ( 50 ) ;
List < AppletAmountLog > timeoutLogs = appletAmountLogService . lambdaQuery ( )
. eq ( AppletAmountLog : : getAuditStatus , 1 ) / / 审核通过
. eq ( AppletAmountLog : : getState , 0 ) / / 处理中 ( 未领取 )
. eq ( AppletAmountLog : : getType , 1 ) / / 支出类型 ( 提现 )
. isNotNull ( AppletAmountLog : : getAuditTime ) / / 审核时间不为空
. le ( AppletAmountLog : : getAuditTime , timeoutThreshold ) / / 审核时间超过47小时
. list ( ) ;
if ( timeoutLogs . isEmpty ( ) ) {
log . info ( "没有找到超时的提现记录" ) ;
return 0 ;
}
int processedCount = 0 ;
for ( AppletAmountLog timeoutLog : timeoutLogs ) {
try {
/ / 处理单个超时提现记录
boolean success = processTimeoutWithdrawal ( timeoutLog ) ;
if ( success ) {
processedCount + + ;
log . info ( "成功处理超时提现记录,ID: {}, 用户ID: {}, 金额: {}" ,
timeoutLog . getId ( ) , timeoutLog . getUserId ( ) , timeoutLog . getAmount ( ) ) ;
} else {
log . error ( "处理超时提现记录失败,ID: {}, 用户ID: {}, 金额: {}" ,
timeoutLog . getId ( ) , timeoutLog . getUserId ( ) , timeoutLog . getAmount ( ) ) ;
}
} catch ( Exception e ) {
log . error ( "处理单个超时提现记录异常,ID: {}, 用户ID: {}, 金额: {}" ,
timeoutLog . getId ( ) , timeoutLog . getUserId ( ) , timeoutLog . getAmount ( ) , e ) ;
}
}
log . info ( "批量处理超时提现完成,总共处理: {} 条记录" , processedCount ) ;
return processedCount ;
} catch ( Exception e ) {
log . error ( "批量处理超时提现异常" , e ) ;
return 0 ;
}
}
/ * *
* 处理单个超时提现记录
* @param timeoutLog 超时的提现记录
* @return 处理结果
* /
@Transactional
public boolean processTimeoutWithdrawal ( AppletAmountLog timeoutLog ) {
try {
/ / 1 . 查询用户信息
AppUsers user = appUsersService . selectAppUsersByUserId ( timeoutLog . getUserId ( ) ) ;
if ( user = = null ) {
log . error ( "用户不存在,userId: {}" , timeoutLog . getUserId ( ) ) ;
return false ;
}
/ / 2 . 退还金额到用户账户
BigDecimal refundAmount = timeoutLog . getAmount ( ) ;
if ( timeoutLog . getMoneyType ( ) = = 0 ) {
/ / 合伙人钱包 - 退还金额
user . setMoney ( user . getMoney ( ) . add ( refundAmount ) ) ;
log . info ( "合伙人钱包退还超时提现金额: {}, 用户ID: {}, 原余额: {}, 新余额: {}" ,
refundAmount , user . getUserId ( ) , user . getMoney ( ) . subtract ( refundAmount ) , user . getMoney ( ) ) ;
} else if ( timeoutLog . getMoneyType ( ) = = 1 ) {
/ / 伴宠师钱包 - 退还金额
user . setPrice ( user . getPrice ( ) . add ( refundAmount ) ) ;
log . info ( "伴宠师钱包退还超时提现金额: {}, 用户ID: {}, 原余额: {}, 新余额: {}" ,
refundAmount , user . getUserId ( ) , user . getPrice ( ) . subtract ( refundAmount ) , user . getPrice ( ) ) ;
} else if ( timeoutLog . getMoneyType ( ) = = 2 ) {
/ / 保证金 - 退还金额
user . setBaoPrice ( user . getBaoPrice ( ) . add ( refundAmount ) ) ;
log . info ( "保证金退还超时提现金额: {}, 用户ID: {}, 原余额: {}, 新余额: {}" ,
refundAmount , user . getUserId ( ) , user . getBaoPrice ( ) . subtract ( refundAmount ) , user . getBaoPrice ( ) ) ;
}
/ / 3 . 更新用户余额
appUsersService . updateAppUsers ( user ) ;
/ / 4 . 更新提现记录状态为超时退回
timeoutLog . setState ( 2 ) ; / / 2 - 失败 / 退回
timeoutLog . setRemark ( "审核通过后超过48小时未领取,系统自动退回" ) ;
timeoutLog . setErrorInfo ( "审核通过后超时未领取,系统自动退回到用户账户" ) ;
appletAmountLogService . updateById ( timeoutLog ) ;
/ / 5 . 记录退款流水
AppletAmountLog refundLog = new AppletAmountLog ( ) ;
refundLog . setUserId ( user . getUserId ( ) ) ;
if ( timeoutLog . getMoneyType ( ) = = 0 ) {
refundLog . setTitle ( "合伙人钱包提现超时退款" ) ;
} else if ( timeoutLog . getMoneyType ( ) = = 1 ) {
refundLog . setTitle ( "伴宠师钱包提现超时退款" ) ;
} else if ( timeoutLog . getMoneyType ( ) = = 2 ) {
refundLog . setTitle ( "保证金提现超时退款" ) ;
}
refundLog . setAmount ( refundAmount ) ;
refundLog . setType ( 0 ) ; / / 0 - 收入 ( 退款 )
refundLog . setState ( 1 ) ; / / 1 - 成功
refundLog . setCreateTime ( LocalDateTime . now ( ) ) ;
refundLog . setMoneyType ( timeoutLog . getMoneyType ( ) ) ;
refundLog . setNameValue ( timeoutLog . getNameValue ( ) ) ;
refundLog . setAuditStatus ( 1 ) ; / / 1 - 审核通过 ( 退款记录自动通过 )
appletAmountLogService . save ( refundLog ) ;
log . info ( "超时提现处理完成,退还金额: {}, 用户ID: {}" , refundAmount , user . getUserId ( ) ) ;
return true ;
} catch ( Exception e ) {
log . error ( "处理超时提现失败,用户ID: {}, 金额: {}" , timeoutLog . getUserId ( ) , timeoutLog . getAmount ( ) , e ) ;
return false ;
}
}
/ * *
/ * *
* 审核通过 , 更新提现记录状态并执行微信提现
* 审核通过 , 更新提现记录状态并执行微信提现
* @param appletAmountLog 提现记录
* @param appletAmountLog 提现记录
@ -276,6 +524,7 @@ public class AppletAmountService {
appletAmountLog . setAuditStatus ( 1 ) ; / / 1 - 审核通过
appletAmountLog . setAuditStatus ( 1 ) ; / / 1 - 审核通过
appletAmountLog . setState ( 0 ) ; / / 0 - 处理中
appletAmountLog . setState ( 0 ) ; / / 0 - 处理中
appletAmountLog . setRemark ( auditRemark ) ; / / 设置审核备注
appletAmountLog . setRemark ( auditRemark ) ; / / 设置审核备注
appletAmountLog . setAuditTime ( LocalDateTime . now ( ) ) ; / / 设置审核时间
appletAmountLogService . updateById ( appletAmountLog ) ;
appletAmountLogService . updateById ( appletAmountLog ) ;
/ / 3 . 执行实际的微信提现操作
/ / 3 . 执行实际的微信提现操作
@ -418,5 +667,4 @@ public class AppletAmountService {
log . error ( "处理微信失败退款异常,用户ID: {},金额: {}" , appletAmountLog . getUserId ( ) , appletAmountLog . getAmount ( ) , ex ) ;
log . error ( "处理微信失败退款异常,用户ID: {},金额: {}" , appletAmountLog . getUserId ( ) , appletAmountLog . getAmount ( ) , ex ) ;
}
}
}
}
}
}