[fix]修改红包和提现

This commit is contained in:
wangqx 2025-09-22 18:31:55 +08:00
parent 2e5cc3afbb
commit a74ed5dde0
19 changed files with 321 additions and 185 deletions

View File

@ -111,8 +111,8 @@ public class AppPayController {
}
@Operation(summary = "查询支付结果")
@GetMapping(value = "/result")
public R<Boolean> paymentResult(PayParam payParam) {
@PostMapping(value = "/result")
public R<Boolean> paymentResult(@RequestBody PayParam payParam) {
return R.ok(cashierSupport.paymentResult(payParam));
}
}

View File

@ -1,14 +1,19 @@
package com.wzj.soopin.order.controller;
package org.dromara.app.customer;
import com.wzj.soopin.order.domain.query.GrabRedPacketRequest;
import com.wzj.soopin.order.domain.query.SendRedPacketRequest;
import com.wzj.soopin.order.service.RedPacketService;
import com.wzj.soopin.transaction.service.RedPacketService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@ -16,9 +21,9 @@ import java.util.Map;
* 红包功能控制器
*/
@RestController
@RequestMapping("/api/packet")
@RequestMapping("/app/customer/packet")
@Tag(name = "红包功能接口")
public class RedPacketController {
public class AppRedPacketController {
@Autowired
private RedPacketService redPacketService;
@ -31,6 +36,11 @@ public class RedPacketController {
@PostMapping("/send")
@Operation(summary = "发红包")
public R<Map<String, Object>> sendRedPacket(@RequestBody SendRedPacketRequest request) {
LoginUser user = LoginHelper.getLoginUser();
if (user == null) {
return R.notLogin();
}
request.setSenderId(user.getUserId());
Map<String, Object> result = redPacketService.sendRedPacket(request);
return R.ok("红包发送成功", result);
}
@ -42,6 +52,11 @@ public class RedPacketController {
@PostMapping("/grab")
@Operation(summary = "抢红包")
public R<Map<String, Object>> grabRedPacket(@RequestBody GrabRedPacketRequest request) {
LoginUser user = LoginHelper.getLoginUser();
if (user == null) {
return R.notLogin();
}
request.setMemberId(user.getUserId());
Map<String, Object> result = redPacketService.grabRedPacket(request);
return R.ok("红包领取成功", result);
}

View File

@ -381,7 +381,7 @@ wechat:
mch-serial-no: 6BA681D9B219034D6F7851F57D61BE9317AB48FD # 商户证书序列号
api-v3-key: T9iE71aHSmjtM35z4bDLuU3gFX8s2I2h # APIv3密钥
private-key-path: "classpath:cert/apiclient_key.pem" # 商户私钥文件路径
transfer-notify-url: https://a94aeb5582c2.ngrok-free.app/no-auth/wechat/notify # 转账回调地址
transfer-notify-url: https://wuzhongjie.com.cn/prod-api/api/transfer/callback # 转账回调地址
app-id: wxebcdaea31881caab # 应用ID
secret: your_wechat_secret # 应用密钥
mini-program:

View File

@ -79,7 +79,7 @@ public class WxPayController {
request.setTransferSceneReportInfos(transferDetailList);
//异步接收微信支付结果通知的回调地址通知url必须为公网可访问的URL必须为HTTPS不能携带参数
// 注意此处需要填写公网可访问的服务器地址不能携带任何参数
request.setNotifyUrl("https://2e879e23cc0e.ngrok-free.app/api/transfer/callback");
request.setNotifyUrl("https://wuzhongjie.com.cn/prod-api/api/transfer/callback");
try{
response = wxPayService.initiateBatchTransferNew(request);
}catch (ServiceException e){

View File

@ -76,4 +76,6 @@ public class Charge extends BaseAudit {
* 审核状态
*/
private Integer auditStatus;
private String payNo;
}

View File

@ -5,9 +5,8 @@ import lombok.Getter;
@Getter
public enum ChargeStatus {
WAITING(0, "未支付"),
PENDING(1, "待验证"),
SUCCESS(2, "充值成功"),
FAIL(3, "充值失败");
SUCCESS(1, "充值成功"),
FAIL(2, "充值失败");
private Integer code;
private String message;

View File

@ -191,11 +191,9 @@ public class CashierSupport {
* @return
*/
public Boolean paymentResult(PayParam payParam) {
for (CashierExecute cashierExecute : cashierExecuteList) {
if (cashierExecute.cashierEnum().name().equals(payParam.getOrderType())) {
return cashierExecute.paymentResult(payParam);
}
}
PaymentMethodEnum paymentMethodEnum = PaymentMethodEnum.valueOf(payParam.getPaymentMethod());
Payment payment = (Payment) SpringUtils.getBean( paymentMethodEnum.getPlugin());
payment.searchByOutTradeNo(payParam.getSn());
return false;
}
@ -204,6 +202,8 @@ public class CashierSupport {
paramInterface.paymentSuccess(payParam);
log.info("订单编号{}支付成功", payParam.getReceivableNo());
}
return true;
}
}

View File

@ -153,4 +153,13 @@ public interface Payment {
return api + "/buyer/payment/cashierRefund/notify/" + paymentMethodEnum.name();
}
/**
* 订单查询
*
* @param outTradeNo 订单编号
* @return 订单详情
*/
default R<Object> searchByOutTradeNo(String outTradeNo) {
throw new ServiceException(ResultCode.PAY_ERROR);
}
}

View File

@ -124,7 +124,7 @@ public class WxPayKit {
* @param params 需要签名的参数
* @return 签名后的数据
*/
public static String createAppSign(Map<String, String> params, String privateKey) {
public static String createAppSign(Map<String, String> params, String keyPath) {
String appid = params.get("appid");
String timestamp = params.get("timestamp");
@ -134,7 +134,7 @@ public class WxPayKit {
String encrypt = appid + "\n" + timestamp + "\n" + noncestr + "\n" + prepayid + "\n";
try {
return PayKit.createSign(encrypt, privateKey);
return PayKit.createSign(encrypt, keyPath);
} catch (Exception e) {
throw new ServiceException(ResultCode.ERROR);
}
@ -377,9 +377,9 @@ public class WxPayKit {
if (signType == null) {
signType = SignType.MD5;
}
String packageSign = createSign(packageParams, partnerKey, signType);
// String packageSign = createSign(packageParams, partnerKey, signType);
// 部分微信APP支付 提示签名错误 解开下方注释 替换上边的代码就好
// String packageSign = createAppSign(packageParams, partnerKey);
String packageSign = createAppSign(packageParams, partnerKey);
packageParams.put("sign", packageSign);
return packageParams;
}

View File

@ -66,7 +66,7 @@ public class RechargeCashier implements CashierExecute {
if (!recharge.getStatus().equals(ChargeStatus.WAITING.getCode())) {
throw new ServiceException(ResultCode.PAY_DOUBLE_ERROR);
}
cashierParam.setPrice( recharge.getMoney().longValue());
cashierParam.setPrice( recharge.getMoney().multiply(BigDecimal.valueOf(100)).longValue());
try {
cashierParam.setTitle("在线充值");
@ -75,6 +75,7 @@ public class RechargeCashier implements CashierExecute {
}
cashierParam.setDetail("余额充值");
cashierParam.setCreateTime(recharge.getCreateTime());
cashierParam.setOrderSns(payParam.getSn());
return cashierParam;
}

View File

@ -29,6 +29,7 @@ import com.wzj.soopin.transaction.service.PaymentService;
import com.wzj.soopin.transaction.service.RefundLogService;
import com.wzj.soopin.transaction.util.CurrencyUtil;
import com.wzj.soopin.transaction.util.SnowFlake;
import com.wzj.soopin.transaction.wechat.WXPayUtility;
import com.wzj.soopin.transaction.wechat.WechatPayConfig;
import com.wzj.soopin.transaction.wechat.WechatPayException;
import jakarta.servlet.http.HttpServletRequest;
@ -255,7 +256,7 @@ public class WechatPlugin implements Payment {
//支付金额
Integer fen = cashierParam.getPrice().intValue();
//第三方付款订单
String outOrderNo = SnowFlake.getIdStr();
String outOrderNo = cashierParam.getOrderSns();
//过期时间
ZonedDateTime zonedDateTime = LocalDateTime.now().plusMinutes(3).atZone(ZoneId.of("UTC"));
@ -263,9 +264,9 @@ public class WechatPlugin implements Payment {
DateTimeFormatter rfc3339NoMillis = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
String timeExpire = zonedDateTime.format(rfc3339NoMillis);
Map<String,String> attachMap=new HashMap<>();
attachMap.put("orderType",payParam.getOrderType());
attachMap.put("outOrderNo",outOrderNo);
Map<String, String> attachMap = new HashMap<>();
attachMap.put("orderType", payParam.getOrderType());
attachMap.put("outOrderNo", outOrderNo);
String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(attachMap), StandardCharsets.UTF_8);
String appid = setting.getAppId();
@ -273,26 +274,26 @@ public class WechatPlugin implements Payment {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url("http://cjh.wuzhongjie.com.cn/app/payment/callback/"+PaymentMethodEnum.WECHAT)
.setAmount(new Amount().setTotal(fen));
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url("http://cjh.wuzhongjie.com.cn/app/payment/callback/" + PaymentMethodEnum.WECHAT)
.setAmount(new Amount().setTotal(fen));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.APP_PAY.toString(),
setting.getMchId(),
setting.getMchSerialNo(),
setting.getMchSerialNo(),
setting.getPrivateKeyPath() ,
JSONUtil.toJsonStr(unifiedOrderModel)
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.APP_PAY.toString(),
setting.getMchId(),
setting.getMchSerialNo(),
setting.getMchSerialNo(),
setting.getPrivateKeyPath(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
//根据证书序列号查询对应的证书来验证签名结果
@ -304,11 +305,15 @@ public class WechatPlugin implements Payment {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.appPrepayIdCreateSign(appid,
setting.getMchId(),
prepayId,
setting.getApiclient_key(), SignType.HMACSHA256);
setting.getMchId(),
prepayId,
setting.getPrivateKeyPath(), SignType.HMACSHA256);
log.info("唤起支付参数:{}", map);
//更新订单的prepayId
return R.ok(map);
}
log.error("微信支付参数验证错误,请及时处理");
@ -499,7 +504,7 @@ public class WechatPlugin implements Payment {
//校验服务器端响应¬7
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
setting.getApiV3Key(), Objects.requireNonNull(getPlatformCert()));
setting.getApiV3Key(), Objects.requireNonNull(getPlatformCert()));
log.info("微信支付通知明文 {}", plainText);
@ -513,16 +518,81 @@ public class WechatPlugin implements Payment {
// Double totalAmount = CurrencyUtil.reversalFen(jsonObject.getJSONObject("amount").getDouble("total"));
PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
PaymentMethodEnum.WECHAT.getCode(),
tradeNo,
0d,
payParam
PaymentMethodEnum.WECHAT.getCode(),
tradeNo,
0d,
payParam
);
cashierSupport.paymentSuccess(paymentSuccessParams);
log.info("微信支付回调:支付成功{}", plainText);
}
@Override
public R searchByOutTradeNo(String outTradeNo) {
try {
String appid = setting.getAppId();
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setOut_trade_no(outTradeNo);
String uri = WechatApiEnum.ORDER_QUERY_BY_NO.toString().formatted(outTradeNo);
Map<String, String> args = new HashMap<>();
args.put("mchid", setting.getMchId());
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.GET,
WechatDomain.CHINA.toString(),
uri,
setting.getMchId(),
setting.getMchSerialNo(),
setting.getMchSerialNo(),
setting.getPrivateKeyPath(),
args
);
//根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
log.info("verifySignature: {}", verifySignature);
log.info("统一下单响应 {}", response);
if (verifySignature) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
String tradeState = jsonObject.getStr("trade_state");
if (tradeState.equals("SUCCESS")) {
String payParamStr = jsonObject.getStr("attach");
String payParamJson = URLDecoder.decode(payParamStr, StandardCharsets.UTF_8);
PayParam payParam = JSONUtil.toBean(payParamJson, PayParam.class);
String tradeNo = jsonObject.getStr("transaction_id");
PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
PaymentMethodEnum.WECHAT.getCode(),
tradeNo,
0d,
payParam
);
cashierSupport.paymentSuccess(paymentSuccessParams);
}
return R.ok();
}
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("微信退款申请失败", e);
}
return R.ok();
}
@Override
public void refund(RefundLog refundLog) {
@ -654,14 +724,14 @@ public class WechatPlugin implements Payment {
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.GET,
WechatDomain.CHINA.toString(),
WechatApiEnum.GET_CERTIFICATES.toString(),
setting.getMchId(),
setting.getMchSerialNo(),
null,
setting.getPrivateKeyPath(),
""
RequestMethodEnums.GET,
WechatDomain.CHINA.toString(),
WechatApiEnum.GET_CERTIFICATES.toString(),
setting.getMchId(),
setting.getMchSerialNo(),
null,
setting.getPrivateKeyPath(),
""
);
String body = response.getBody();
log.info("获取微信平台证书body: {}", body);
@ -708,10 +778,13 @@ public class WechatPlugin implements Payment {
//平台证书密文解密
//encrypt_certificate 中的 associated_data nonce ciphertext
return aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
}
}

View File

@ -1,4 +1,4 @@
package com.wzj.soopin.order.service;
package com.wzj.soopin.transaction.service;
import com.wzj.soopin.order.domain.query.GrabRedPacketRequest;
import com.wzj.soopin.order.domain.query.SendRedPacketRequest;

View File

@ -76,10 +76,7 @@ public class AccountBillServiceImpl extends
if (memberAccount == null) {
throw new RuntimeException("用户不存在");
}
//检查当前用于的账户余额是否充足
BigDecimal balance = memberAccount.getWallet();
BigDecimal newBalance = balance.add(money);
//锁定用户余额
memberAccountService.updateById(memberAccount.toBuilder().wallet(newBalance).build());

View File

@ -97,9 +97,13 @@ public class ChargeServiceImpl extends ServiceImpl<ChargeMapper, Charge> impleme
log.error("充值记录不存在,code:{}",code);
return false;
}
charge.setStatus(ChargeStatus.PENDING.getCode());
// charge.setPayNo(payNo);
charge.setStatus(ChargeStatus.SUCCESS.getCode());
charge.setPayNo(payNo);
charge.setMethod(method);
return updateById(charge);
boolean result= updateById(charge);
if (result){
accountBillService.addMoney(charge.getActualMoney(),charge.getMemberId(),AccountBillSourceEnum.RECHARGE,"充值");
}
return result;
}
}

View File

@ -1,9 +1,7 @@
package com.wzj.soopin.order.service.impl;
package com.wzj.soopin.transaction.service.impl;
import cn.hutool.core.date.DateTime;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.member.domain.po.AccountBill;
import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.member.mapper.MemberAccountMapper;
import com.wzj.soopin.order.domain.entity.RedPacket;
@ -12,18 +10,23 @@ import com.wzj.soopin.order.domain.query.GrabRedPacketRequest;
import com.wzj.soopin.order.domain.query.SendRedPacketRequest;
import com.wzj.soopin.order.mapper.RedPacketMapper;
import com.wzj.soopin.order.mapper.RedPacketReceiveMapper;
import com.wzj.soopin.order.service.RedPacketService;
import com.wzj.soopin.transaction.service.IAccountBillService;
import com.wzj.soopin.transaction.service.RedPacketService;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.base.BaseException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service
@ -35,6 +38,8 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
private final MemberAccountMapper umsAccountMapper;
private final RedisTemplate<String, Object> redisTemplate;
private final IAccountBillService accountBillService;
// Redis锁前缀
private static final String LOCK_PREFIX = "red_packet:lock:";
@ -68,9 +73,8 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
RedPacket redPacket = createRedPacket(request);
redPacketMapper.insert(redPacket);
Long packetId = redPacket.getId();
// 扣减发送者余额并记录变动
deductSenderBalance(request.getSenderId(), redPacket, balance);
accountBillService.reduceMoney(balance, memberId, AccountBillSourceEnum.RED_PACKAGE_SEND, "红包发放");
// 返回结果
Map<String, Object> result = new HashMap<>();
@ -108,17 +112,17 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
// 查询红包信息
RedPacket redPacket = redPacketMapper.selectById(packetId);
if (redPacket == null) {
throw new RuntimeException("红包不存在");
throw new BaseException("红包不存在");
}
// 验证红包状态
validateRedPacketStatus(redPacket, memberId);
// 检查是否已领取
Integer received = redPacketReceiveMapper.checkReceived(packetId, memberId);
if (received != null && received > 0) {
throw new RuntimeException("您已领取过该红包");
}
// Integer received = redPacketReceiveMapper.checkReceived(packetId, memberId);
// if (received != null && received > 0) {
// throw new ServiceException("您已领取过该红包");
// }
// 计算领取金额
BigDecimal receiveAmount = calculateReceiveAmount(redPacket);
@ -131,7 +135,7 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
redPacketReceiveMapper.insert(receiveRecord);
// 增加领取者余额并记录变动
addReceiverBalance(memberId, receiveAmount, packetId);
accountBillService.addMoney(receiveAmount, memberId, AccountBillSourceEnum.RED_PACKAGE_RECEIVE, "红包领取");
// 更新红包状态
updateRedPacketStatus(redPacket);
@ -179,7 +183,7 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
redPacket.setReceiverId(request.getReceiverId());
redPacket.setGroupId(request.getGroupId());
// 默认为普通红包
redPacket.setPacketType(1);
redPacket.setPacketType(redPacket.getPacketType());
redPacket.setTotalAmount(request.getTotalAmount());
redPacket.setTotalCount(request.getTotalCount());
redPacket.setRemainingAmount(request.getTotalAmount());
@ -193,28 +197,6 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
return redPacket;
}
/**
* 扣减发送者余额并记录变动
*/
private void deductSenderBalance(Long senderId, RedPacket redPacket, BigDecimal beforeBalance) {
// 计算扣减后的余额
BigDecimal afterBalance = beforeBalance.subtract(redPacket.getTotalAmount());
umsAccountMapper.updateMoneyBalance(senderId, afterBalance);
// 记录金额变动
// AccountBill record = AccountBill.builder()
// .moneyBalance(beforeBalance)
// .accountId(senderId)
// .beforeBalance(beforeBalance)
// .afterBalance(afterBalance)
// .changeAmount(redPacket.getTotalAmount())
// .changeType(AccountBillChangeTypeEnum.OUT.getCode())
// .source(AccountBillSourceEnum.RED_PACKAGE_SEND.getCode())
// .changeDesc("发送红包扣减红包ID" + redPacket.getId())
// .build();
//
// accountChangeRecordMapper.insert(record);
}
/**
* 验证红包状态
@ -227,24 +209,24 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
} else {
redPacket.setStatus(STATUS_EXPIRED);
redPacketMapper.updateById(redPacket);
throw new RuntimeException("红包已过期");
throw new BaseException("红包已过期");
}
}
// 检查是否已领完
if (redPacket.getStatus() == STATUS_ALL_RECEIVED) {
throw new RuntimeException("红包已被领完");
throw new BaseException("红包已被领完");
}
// 检查是否是自己发的红包
if (redPacket.getSenderId().equals(memberId)) {
throw new RuntimeException("不能领取自己发送的红包");
}
// if (redPacket.getSenderId().equals(memberId)) {
// throw new RuntimeException("不能领取自己发送的红包");
// }
// 单聊红包只能指定接收者领取
if (redPacket.getChatType() == CHAT_TYPE_SINGLE &&
!redPacket.getReceiverId().equals(memberId)) {
throw new RuntimeException("您无权领取该红包");
throw new BaseException("您无权领取该红包");
}
}
@ -258,25 +240,9 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
// 查询当前余额
BigDecimal beforeBalance = umsAccountMapper.getMoneyBalanceByMemberId(senderId);
if (beforeBalance == null) {
throw new RuntimeException("发送者账户不存在");
throw new BaseException("发送者账户不存在");
}
// 增加余额
BigDecimal afterBalance = beforeBalance.add(remainingAmount);
umsAccountMapper.updateMoneyBalance(senderId, afterBalance);
// // 记录金额变动
// AccountBill record = AccountBill.builder()
// .accountId(senderId)
// .beforeBalance(beforeBalance)
// .afterBalance(afterBalance)
// .changeAmount(remainingAmount)
// .changeType(AccountBillChangeTypeEnum.IN.getCode())
// .source(AccountBillSourceEnum.RED_PACKAGE_REFUND.getCode())
// .changeDesc("红包退款红包ID" + redPacket.getId())
// .build();
//
// accountChangeRecordMapper.insert(record);
accountBillService.addMoney(remainingAmount, senderId, AccountBillSourceEnum.RED_PACKAGE_REFUND, "红包退回");
// 更新红包状态为已退款
redPacket.setStatus(STATUS_REFUNDED);
@ -295,11 +261,16 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
// 最后一个红包领取全部剩余金额
return remainingAmount;
}
BigDecimal minAmount = new BigDecimal("0.01");
BigDecimal maxAmount = remainingAmount.subtract(new BigDecimal(remainingCount - 1).multiply(minAmount));
BigDecimal randomAmount = minAmount.add(new BigDecimal(Math.random() * maxAmount.doubleValue())).setScale(2, BigDecimal.ROUND_HALF_UP);
int fenAmount = remainingAmount.multiply(new BigDecimal("100")).intValue();
return randomAmount;
Random random = new Random();
// 4. 分配前 count-1 个红包
// 最大可分配金额剩余金额/剩余数量×2确保后续每人至少1分
int max = (fenAmount / remainingCount) * 2;
// 随机金额[1, max]
int money = random.nextInt(max) + 1;
// 累加结果更新剩余
return new BigDecimal(money / 100.0).setScale(2, RoundingMode.HALF_UP);
}
/**
@ -323,33 +294,6 @@ public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket
return record;
}
/**
* 增加领取者余额并记录变动
*/
private void addReceiverBalance(Long memberId, BigDecimal amount, Long packetId) {
// 查询当前余额
BigDecimal beforeBalance = umsAccountMapper.getMoneyBalanceByMemberId(memberId);
if (beforeBalance == null) {
throw new RuntimeException("领取者账户不存在");
}
// 增加余额
BigDecimal afterBalance = beforeBalance.add(amount);
umsAccountMapper.updateMoneyBalance(memberId, afterBalance);
// // 记录金额变动
// AccountBill record = AccountBill.builder()
// .accountId(memberId)
// .beforeBalance(beforeBalance)
// .afterBalance(afterBalance)
// .changeAmount(amount)
// .changeType(AccountBillChangeTypeEnum.IN.getCode())
// .source(AccountBillSourceEnum.RED_PACKAGE_RECEIVE.getCode())
// .changeDesc("红包领取红包ID" + packetId)
// .build();
//
// accountChangeRecordMapper.insert(record);
}
/**
* 更新红包状态

View File

@ -1,22 +1,28 @@
package com.wzj.soopin.transaction.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzj.soopin.member.domain.po.Member;
import com.wzj.soopin.transaction.domain.bo.WithdrawBO;
import com.wzj.soopin.member.domain.po.MemberAccount;
import com.wzj.soopin.member.domain.po.AccountBill;
import com.wzj.soopin.transaction.domain.entity.InitiateBatchTransferRequestNew;
import com.wzj.soopin.transaction.domain.entity.TransferSceneReportInfoNew;
import com.wzj.soopin.transaction.domain.po.Withdraw;
import com.wzj.soopin.member.enums.AccountBillChangeTypeEnum;
import com.wzj.soopin.member.enums.AccountBillSourceEnum;
import com.wzj.soopin.transaction.enums.WithdrawAuditStatus;
import com.wzj.soopin.transaction.enums.WithdrawStatus;
import com.wzj.soopin.transaction.enums.WithdrawType;
import com.wzj.soopin.transaction.mapper.WithdrawMapper;
import com.wzj.soopin.member.service.*;
import com.wzj.soopin.transaction.domain.vo.EasypayAccountVO;
import com.wzj.soopin.transaction.service.IAccountBillService;
import com.wzj.soopin.transaction.service.IWithdrawService;
import com.wzj.soopin.transaction.service.IEasypayService;
import com.wzj.soopin.transaction.wechat.WechatPayConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysTenantAccount;
import org.dromara.system.service.ISysTenantAccountService;
@ -24,6 +30,8 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
@ -48,6 +56,10 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
private final IAccountBillService accountBillService;
private final WxPayService wxPayService;
private final IMemberService memberService;
@Override
public boolean audit(WithdrawBO bo) {
Withdraw withdraw = getById(bo.getId());
@ -58,9 +70,12 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
throw new RuntimeException("提现申请已处理");
}
//发起提现
boolean chargeSuccess = easypayService.withdraw(withdraw.getMemberId(), withdraw.getMoney());
if (!chargeSuccess) {
throw new RuntimeException("提现失败");
try{
wxPayService.initiateBatchTransferNew(buildWechatPayParam(bo));
}catch (ServiceException e){
log.error("提现申请失败",e);
throw new RuntimeException("提现申请失败");
}
withdraw = Withdraw.builder().id(bo.getId())
.auditReason(bo.getAuditReason())
@ -72,6 +87,43 @@ public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> i
return true;
}
private InitiateBatchTransferRequestNew buildWechatPayParam(WithdrawBO withdraw) {
InitiateBatchTransferRequestNew request = new InitiateBatchTransferRequestNew();
WechatPayConfig wechatPayConfig = new WechatPayConfig();
//商户AppID
request.setAppid(wechatPayConfig.getAppId());
//商户单号
request.setOutBillNo(withdraw.getCode());
request.setTransferAmount(withdraw.getMoney().multiply(BigDecimal.valueOf(100)).intValue());
//转账场景ID
// /** 转账场景ID 说明:该批次转账使用的转账场景,如不填写则使用商家的默认场景,如无默认场景可为空,可前往“商家转账到零钱-前往功能”中申请。 如1001-现金营销 */
request.setTransferSceneId("1005");
//用户的openId
Member member=memberService.getById(withdraw.getMemberId());
request.setOpenid(member.getOpenId());
//收款用户姓名
request.setUserName(member.getNickname());
//转账备注
request.setTransferRemark("提现");
//转账场景报备信息 佣金的固定类型
List<TransferSceneReportInfoNew> transferDetailList = new ArrayList<>();
TransferSceneReportInfoNew transferSceneReportInfoNew = new TransferSceneReportInfoNew();
transferSceneReportInfoNew.setInfoType("岗位类型");
transferSceneReportInfoNew.setInfoContent("代理人");
transferDetailList.add(transferSceneReportInfoNew);
TransferSceneReportInfoNew transferSceneReportInfoNew1 = new TransferSceneReportInfoNew();
transferSceneReportInfoNew1.setInfoType("报酬说明");
transferSceneReportInfoNew1.setInfoContent("手续费");
transferDetailList.add(transferSceneReportInfoNew1);
request.setTransferSceneReportInfos(transferDetailList);
//异步接收微信支付结果通知的回调地址通知url必须为公网可访问的URL必须为HTTPS不能携带参数
// 注意此处需要填写公网可访问的服务器地址不能携带任何参数
request.setNotifyUrl(wechatPayConfig.getTransferNotifyUrl());
return request;
}
@Override
public boolean withdrawCallback(WithdrawBO withdraw) {
MemberAccount memberAccount = memberAccountService.getMemberAccount(withdraw.getMemberId());

View File

@ -42,6 +42,8 @@ public class WxPayService {
private static final Logger logger = LoggerFactory.getLogger(WxPayService.class);
@Autowired
private WechatPayConfig WechatPayConfig;
@Autowired
private RSAAutoCertificateConfig wxPayConfig;
/**
* 商家转账 - 发起转账 - 2025年1月15号之后商户转账零线必须用户确认收款
@ -51,30 +53,32 @@ public class WxPayService {
*/
public InitiateBatchTransferResponseNew initiateBatchTransferNew(InitiateBatchTransferRequestNew request) {
logger.info("WxPayService.initiateBatchTransferNew request:{}", request.toString());
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(WechatPayConfig.getMchId())
.privateKeyFromPath(WechatPayConfig.getPrivateKeyPath())
.merchantSerialNumber(WechatPayConfig.getMchSerialNo())
.apiV3Key(WechatPayConfig.getApiV3Key())
.build();
String encryptName = config.createEncryptor().encrypt(request.getUserName());
request.setUserName(encryptName);
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
HttpHeaders headers = new HttpHeaders();
headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Wechatpay-Serial", config.createEncryptor().getWechatpaySerial());
HttpRequest httpRequest =
try{
String encryptName = wxPayConfig.createEncryptor().encrypt(request.getUserName());
request.setUserName(encryptName);
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
HttpHeaders headers = new HttpHeaders();
headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Wechatpay-Serial", wxPayConfig.createEncryptor().getWechatpaySerial());
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url(requestPath)
.headers(headers)
.body(createRequestBody(request))
.build();
HttpClient httpClient = new DefaultHttpClientBuilder().config(config).build();
HttpResponse<InitiateBatchTransferResponseNew> httpResponse = httpClient.execute(httpRequest, InitiateBatchTransferResponseNew.class);
logger.info("WxPayService.initiateBatchTransferNew response:{}", httpResponse.getServiceResponse());
return httpResponse.getServiceResponse();
.httpMethod(HttpMethod.POST)
.url(requestPath)
.headers(headers)
.body(createRequestBody(request))
.build();
HttpClient httpClient = new DefaultHttpClientBuilder().config(wxPayConfig).build();
HttpResponse<InitiateBatchTransferResponseNew> httpResponse = httpClient.execute(httpRequest, InitiateBatchTransferResponseNew.class);
logger.info("WxPayService.initiateBatchTransferNew response:{}", httpResponse.getServiceResponse());
return httpResponse.getServiceResponse();
}catch (Exception e){
logger.error("WxPayService.initiateBatchTransferNew error:{}", e);
}
return null;
}
/**
@ -85,6 +89,7 @@ public class WxPayService {
*/
public TransferDetailEntityNew getTransferDetailByOutNoNew(String outBillNo) {
logger.info("WxPayService.getTransferDetailByOutNoNew request:{}", outBillNo);
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(WechatPayConfig.getMchId())
.privateKeyFromPath(WechatPayConfig.getPrivateKeyPath())

View File

@ -1,5 +1,7 @@
package com.wzj.soopin.transaction.wechat;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@ -42,4 +44,16 @@ public class WechatPayConfig {
return WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
}
}
@Bean
public RSAAutoCertificateConfig wxPayConfig() throws IOException {
return new RSAAutoCertificateConfig.Builder()
.merchantId(this.getMchId())
.privateKey(this.merchantPrivateKey())
.merchantSerialNumber(this.getMchSerialNo())
.apiV3Key(this.getApiV3Key())
.build();
}
}

View File

@ -170,7 +170,7 @@ http {
#
server {
listen 443 ssl;
server_name wuzhongjie.com.cn www.wuzhongjie.com.cn;
server_name wuzhongjie.com.cn www.wuzhongjie.com.cn ;
ssl_certificate /etc/nginx/cert/wuzhongjie.com.cn_bundle.pem;
ssl_certificate_key /etc/nginx/cert/wuzhongjie.com.cn.key;
@ -199,6 +199,7 @@ http {
}
location ^~ /busniess {
alias /data/nginx/wzj/busniess/;
index index.html;
@ -244,7 +245,27 @@ http {
proxy_redirect off;
}
}
server {
listen 443 ssl;
ssl_certificate /etc/nginx/cert/wuzhongjie.com.cn_bundle.pem;
ssl_certificate_key /etc/nginx/cert/wuzhongjie.com.cn.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# 设置 gzip 压缩
gzip on;
#gzip_static on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 256;
server_name cjh.wuzhongjie.com.cn;
location / {
proxy_pass http://43.143.227.203:7002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}