支付,金币权限

This commit is contained in:
hy001 2026-04-30 18:59:20 +08:00
parent 0fc752832e
commit 7df5a8e04d
51 changed files with 19960 additions and 443 deletions

View File

@ -46,13 +46,8 @@ red-circle:
gateway-base-url: ${LIKEI_PAYERMAX_GATEWAY_BASE_URL}
enable-real-time-exchange-rate: ${LIKEI_PAYERMAX_ENABLE_REAL_TIME_EXCHANGE_RATE}
mifa-pay:
mer-account: ${LIKEI_MIFA_PAY_MER_ACCOUNT}
mer-no: ${LIKEI_MIFA_PAY_MER_NO}
gateway-base-url: ${LIKEI_MIFA_PAY_GATEWAY_BASE_URL}
rsa-private-key: ${LIKEI_MIFA_PAY_RSA_PRIVATE_KEY}
platform-rsa-public-key: ${LIKEI_MIFA_PAY_PLATFORM_RSA_PUBLIC_KEY}
notify-url: ${LIKEI_MIFA_PAY_NOTIFY_URL:https://jvapi.haiyihy.com/play-server-notice/mifa_pay/receive_payment}
factory-config:
cache-ttl-seconds: ${LIKEI_PAY_FACTORY_CONFIG_CACHE_TTL_SECONDS:300}
airwallex:
client-id: ${LIKEI_AIRWALLEX_CLIENT_ID}

View File

@ -2,8 +2,10 @@ package com.red.circle.order.inner.endpoint.api;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import java.util.List;
import java.util.Map;
@ -36,4 +38,11 @@ public interface PayFactoryClientApi {
@PostMapping("/updateSysPayChannel")
ResultResponse<Void> updateSysPayChannel(@RequestBody SysPayFactoryCmd param);
@PostMapping("/getFactoryConfig")
ResultResponse<SysPayFactoryConfigDTO> getFactoryConfig(
@RequestBody SysPayFactoryConfigCmd param);
@PostMapping("/saveOrUpdateFactoryConfig")
ResultResponse<Void> saveOrUpdateFactoryConfig(@RequestBody SysPayFactoryConfigCmd param);
}

View File

@ -0,0 +1,52 @@
package com.red.circle.order.inner.model.cmd;
import com.red.circle.framework.core.dto.CommonCommand;
import jakarta.validation.constraints.NotBlank;
import java.io.Serial;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付厂商运行配置.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class SysPayFactoryConfigCmd extends CommonCommand {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
@NotBlank
private String factoryCode;
private String env;
private String sysOrigin;
private String merAccount;
private String merNo;
private String gatewayBaseUrl;
private String rsaPrivateKey;
private String platformRsaPublicKey;
private String notifyUrl;
private Boolean enabled;
private Timestamp createTime;
private Timestamp updateTime;
private Long createUser;
private Long updateUser;
}

View File

@ -0,0 +1,51 @@
package com.red.circle.order.inner.model.dto;
import cn.hutool.core.convert.Convert;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付厂商运行配置.
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class SysPayFactoryConfigDTO extends Convert {
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String factoryCode;
private String env;
private String sysOrigin;
private String merAccount;
private String merNo;
private String gatewayBaseUrl;
private String rsaPrivateKey;
private String platformRsaPublicKey;
private String notifyUrl;
private Boolean enabled;
private Timestamp createTime;
private Timestamp updateTime;
private Long createUser;
private Long updateUser;
}

View File

@ -36,9 +36,14 @@ public class DeductGoldCmd extends CommonCommand {
*/
private String remarks;
/**
* 后台操作用户id.
*/
private Long operationUserId;
}
/**
* 后台操作用户id.
*/
private Long operationUserId;
/**
* 验证跟踪id.
*/
private String trackId;
}

View File

@ -39,15 +39,21 @@ public class UserGoldRunningWaterBackQryCmd extends Command {
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
/**
* 后台操作用户.
*/
private Boolean queryBackOperationUser;
/**
* 跟踪id.
*/
private String trackId;
/**
* 后台操作用户.
*/
private Boolean queryBackOperationUser;
/**
* 后台操作用户ID.
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long backOperationUser;
/**
* 跟踪id.
*/
private String trackId;
/**
* 类型0.收入 1.消耗.

View File

@ -5,8 +5,10 @@ import com.red.circle.console.app.service.app.sys.pay.SysPayFactoryService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import com.red.circle.tool.core.date.TimestampUtils;
import java.util.List;
@ -70,4 +72,23 @@ public class SysPayFactoryController extends BaseController {
sysPayFactoryService.updateSysPayChannel(param);
}
/**
* 厂商运行配置.
*/
@GetMapping("/config")
public SysPayFactoryConfigDTO getFactoryConfig(SysPayFactoryConfigCmd param) {
return sysPayFactoryService.getFactoryConfig(param);
}
/**
* 保存厂商运行配置.
*/
@OpsOperationReqLog("保存-支付厂商运行配置")
@PostMapping("/config/save")
public void saveOrUpdateFactoryConfig(@RequestBody @Validated SysPayFactoryConfigCmd param) {
param.setUpdateUser(param.getReqUserId());
param.setUpdateTime(TimestampUtils.now());
sysPayFactoryService.saveOrUpdateFactoryConfig(param);
}
}

View File

@ -1,11 +1,12 @@
package com.red.circle.console.adapter.app.user;
import com.red.circle.console.app.dto.clienobject.user.UserGoldRunningWaterCO;
import com.red.circle.console.app.dto.clienobject.user.UserGoldRunningWaterClsTmpCO;
import com.red.circle.console.app.service.app.user.UserWalletService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.console.app.dto.clienobject.user.UserGoldRunningWaterCO;
import com.red.circle.console.app.dto.clienobject.user.UserGoldRunningWaterClsTmpCO;
import com.red.circle.console.app.service.gm.GmGoldOperationService;
import com.red.circle.console.app.service.app.user.UserWalletService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.wallet.inner.model.cmd.DeductGoldCmd;
import com.red.circle.wallet.inner.model.cmd.DiamondCmd;
import com.red.circle.wallet.inner.model.cmd.SendGoldCmd;
@ -29,9 +30,10 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController
@RequestMapping("/user-wallet")
public class UserWalletRestController extends BaseController {
private final UserWalletService userWalletService;
public class UserWalletRestController extends BaseController {
private final UserWalletService userWalletService;
private final GmGoldOperationService gmGoldOperationService;
/**
* 流水列表(使用cls代替).
@ -50,19 +52,19 @@ public class UserWalletRestController extends BaseController {
return userWalletService.getGoldRunningWaterCls(query);
}
@OpsOperationReqLog("发送金币")
@PostMapping("/send-gold")
public void sendGold(@RequestBody @Validated SendGoldCmd param) {
param.setOperationUserId(param.getReqUserId());
userWalletService.sendGold(param);
}
@OpsOperationReqLog("扣除金币")
@PostMapping("/deduct-gold")
public void deductCandy(@RequestBody @Validated DeductGoldCmd param) {
param.setOperationUserId(param.getReqUserId());
userWalletService.deductGold(param);
}
@OpsOperationReqLog("发送金币")
@PostMapping("/send-gold")
public void sendGold(@RequestBody @Validated SendGoldCmd param) {
param.setOperationUserId(param.getReqUserId());
gmGoldOperationService.submitSendGold(param);
}
@OpsOperationReqLog("扣除金币")
@PostMapping("/deduct-gold")
public void deductCandy(@RequestBody @Validated DeductGoldCmd param) {
param.setOperationUserId(param.getReqUserId());
gmGoldOperationService.submitDeductGold(param);
}
@OpsOperationReqLog("发送钻石")
@PostMapping("/send-diamond")

View File

@ -0,0 +1,45 @@
package com.red.circle.console.adapter.gm;
import com.red.circle.console.app.dto.clienobject.gm.GmGoldOperationCO;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationPageQryCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationReviewCmd;
import com.red.circle.console.app.service.gm.GmGoldOperationService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.web.controller.BaseController;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
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;
/**
* GM金币操作.
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/gm/gold-operation")
public class GmGoldOperationRestController extends BaseController {
private final GmGoldOperationService gmGoldOperationService;
@OpsOperationReqLog("GM金币操作申请")
@PostMapping
public GmGoldOperationCO submit(@RequestBody @Validated GmGoldOperationCmd cmd) {
return gmGoldOperationService.submit(cmd);
}
@GetMapping("/page")
public PageResult<GmGoldOperationCO> page(GmGoldOperationPageQryCmd cmd) {
return gmGoldOperationService.page(cmd);
}
@OpsOperationReqLog("GM金币操作审核")
@PostMapping("/review")
public GmGoldOperationCO review(@RequestBody @Validated GmGoldOperationReviewCmd cmd) {
return gmGoldOperationService.review(cmd);
}
}

View File

@ -1,12 +1,16 @@
package com.red.circle.console.app.command.admin.query;
import com.red.circle.console.app.convertor.MenuAppConvertor;
import com.red.circle.console.app.dto.clienobject.admin.MenuTreeCO;
import com.red.circle.console.infra.database.rds.service.admin.MenuService;
import com.red.circle.framework.core.dto.CommonCommand;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import com.red.circle.console.app.convertor.MenuAppConvertor;
import com.red.circle.console.app.dto.clienobject.admin.MenuTreeCO;
import com.red.circle.console.infra.database.rds.entity.admin.User;
import com.red.circle.console.infra.database.rds.service.admin.MenuService;
import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.framework.core.dto.CommonCommand;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 账号菜单查询.
@ -15,14 +19,43 @@ import org.springframework.stereotype.Component;
*/
@Component
@RequiredArgsConstructor
public class AccountMenuQryExe {
private final MenuService menuService;
private final MenuAppConvertor menuAppConvertor;
public List<MenuTreeCO> execute(CommonCommand cmd) {
return menuAppConvertor.toListMenuTreeCO(
menuService.listUserPermMenu(cmd.requiredReqUserId().intValue()));
}
}
public class AccountMenuQryExe {
private static final String YUMI_ADMIN = "yumiadmin";
private static final String GM_OPERATION = "GmOperation";
private static final String GM_GOLD_OPERATION_REVIEW = "GmGoldOperationReview";
private final MenuService menuService;
private final UserService userService;
private final MenuAppConvertor menuAppConvertor;
public List<MenuTreeCO> execute(CommonCommand cmd) {
Integer reqUserId = cmd.requiredReqUserId().intValue();
List<MenuTreeCO> menus = menuAppConvertor.toListMenuTreeCO(menuService.listUserPermMenu(reqUserId));
User user = userService.getById(reqUserId);
if (Objects.nonNull(user) && YUMI_ADMIN.equalsIgnoreCase(user.getLoginName())) {
return menus;
}
removeReviewMenu(menus);
return menus;
}
private void removeReviewMenu(List<MenuTreeCO> menus) {
if (menus == null) {
return;
}
Iterator<MenuTreeCO> iterator = menus.iterator();
while (iterator.hasNext()) {
MenuTreeCO menu = iterator.next();
removeReviewMenu(menu.getChildrens());
if (GM_GOLD_OPERATION_REVIEW.equals(menu.getAlias())
|| (GM_OPERATION.equals(menu.getAlias()) && isEmpty(menu.getChildrens()))) {
iterator.remove();
}
}
}
private boolean isEmpty(List<?> values) {
return values == null || values.isEmpty();
}
}

View File

@ -3,8 +3,10 @@ package com.red.circle.console.app.service.app.sys.pay;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.inner.endpoint.PayFactoryClient;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import java.util.List;
import java.util.Map;
@ -50,4 +52,14 @@ public class SysPayFactoryServiceImpl implements SysPayFactoryService {
public Map<String, SysPayFactoryDTO> mapByCodes(Set<String> codes) {
return ResponseAssert.requiredSuccess(payFactoryClient.mapByCodes(codes));
}
@Override
public SysPayFactoryConfigDTO getFactoryConfig(SysPayFactoryConfigCmd param) {
return ResponseAssert.requiredSuccess(payFactoryClient.getFactoryConfig(param));
}
@Override
public void saveOrUpdateFactoryConfig(SysPayFactoryConfigCmd param) {
ResponseAssert.requiredSuccess(payFactoryClient.saveOrUpdateFactoryConfig(param));
}
}

View File

@ -95,11 +95,10 @@ public class UserWalletServiceImpl implements UserWalletService {
userProfileClient.mapByUserIds(
runningWaters.stream().map(UserGoldRunningWaterHistoryDTO::getUserId)
.collect(Collectors.toSet())));
/*
Map<Long, User> userMap = userService.mapBackUser(
runningWaters.stream().map(UserGoldRunningWaterTmpDTO::getBackOperationUser)
.filter(Objects::nonNull)
.collect(Collectors.toSet()));*/
Map<Long, String> opsUserNicknameMap = userService.mapBackUserNickname(
runningWaters.stream().map(UserGoldRunningWaterHistoryDTO::getBackOperationUser)
.filter(Objects::nonNull)
.collect(Collectors.toSet()));
return runningWaters.stream()
.map(runningWater -> {
@ -111,15 +110,12 @@ public class UserWalletServiceImpl implements UserWalletService {
return null;
}
return new UserGoldRunningWaterCO()
.setUserProfile(userProfileMap.get(runningWater.getUserId()))
.setRunningWater(walletAppConvertor.toUserGoldRunningWaterDTO(runningWater));
/*.backOperationName(Optional.ofNullable(runningWater.getBackOperationUser())
.map(backUid -> Optional.of(userMap.get(backUid))
.map(User::getNickname)
.orElse(""))
.orElse(""))*/
})
return new UserGoldRunningWaterCO()
.setUserProfile(userProfileMap.get(runningWater.getUserId()))
.setRunningWater(walletAppConvertor.toUserGoldRunningWaterDTO(runningWater))
.setBackOperationName(Optional.ofNullable(runningWater.getBackOperationUser())
.map(opsUserNicknameMap::get).orElse(""));
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@ -266,12 +262,12 @@ public class UserWalletServiceImpl implements UserWalletService {
ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, userProfile);
walletGoldClient.changeBalance(GoldReceiptCmd.builder()
.opsExpenditure()
.userId(userProfile.getId())
.sysOrigin(userProfile.getOriginSys())
.eventId("BACK-" + param.getOperationUserId())
.amount(param.getQuantity())
.origin(GoldOrigin.DEDUCT_COINS)
.opsExpenditure()
.userId(userProfile.getId())
.sysOrigin(userProfile.getOriginSys())
.eventId(StringUtils.isBlankOrElse(param.getTrackId(), "BACK-" + param.getOperationUserId()))
.amount(param.getQuantity())
.origin(GoldOrigin.DEDUCT_COINS)
.remark(param.getRemarks())
.closeDelayAsset()
.opUserId(param.getOperationUserId())

View File

@ -0,0 +1,316 @@
package com.red.circle.console.app.service.gm;
import com.red.circle.console.app.dto.clienobject.gm.GmGoldOperationCO;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationPageQryCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationReviewCmd;
import com.red.circle.console.app.service.app.user.UserWalletService;
import com.red.circle.console.infra.database.rds.entity.admin.User;
import com.red.circle.console.infra.database.rds.entity.app.gm.GmGoldOperationRecord;
import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.console.infra.database.rds.service.app.gm.GmGoldOperationRecordService;
import com.red.circle.console.inner.error.ConsoleErrorCode;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.DeductGoldCmd;
import com.red.circle.wallet.inner.model.cmd.SendGoldCmd;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* GM金币操作.
*/
@Service
@RequiredArgsConstructor
public class GmGoldOperationServiceImpl implements GmGoldOperationService {
private static final int OPERATION_INCOME = 0;
private static final int OPERATION_EXPENDITURE = 1;
private static final String STATUS_PENDING = "PENDING";
private static final String STATUS_APPROVED = "APPROVED";
private static final String STATUS_REJECTED = "REJECTED";
private static final String YUMI_ADMIN = "yumiadmin";
private final GmGoldOperationRecordService recordService;
private final UserWalletService userWalletService;
private final UserProfileClient userProfileClient;
private final UserService userService;
@Override
@Transactional(rollbackFor = Exception.class)
public GmGoldOperationCO submit(GmGoldOperationCmd cmd) {
validateSubmit(cmd);
Long applicantId = cmd.getReqUserId();
User applicant = getAdminUser(applicantId);
UserProfileDTO userProfile = getUserProfile(cmd.getUserId());
GmGoldOperationRecord record = buildRecord(cmd, applicant, userProfile);
recordService.save(record);
if (isYumiAdmin(applicant)) {
approveAndExecute(record, applicant, "yumiadmin direct execution");
}
return toCO(record, userProfile);
}
@Override
public GmGoldOperationCO submitSendGold(SendGoldCmd cmd) {
GmGoldOperationCmd operationCmd = new GmGoldOperationCmd();
operationCmd.setReqUserId(cmd.getReqUserId());
operationCmd.setUserId(cmd.getUserId());
operationCmd.setOperationType(OPERATION_INCOME);
operationCmd.setQuantity(cmd.getQuantity());
operationCmd.setReasonType(cmd.getType());
operationCmd.setUsdQuantity(cmd.getUsdQuantity());
operationCmd.setRemarks(cmd.getRemarks());
return submit(operationCmd);
}
@Override
public GmGoldOperationCO submitDeductGold(DeductGoldCmd cmd) {
GmGoldOperationCmd operationCmd = new GmGoldOperationCmd();
operationCmd.setReqUserId(cmd.getReqUserId());
operationCmd.setUserId(cmd.getUserId());
operationCmd.setOperationType(OPERATION_EXPENDITURE);
operationCmd.setQuantity(cmd.getQuantity());
operationCmd.setReasonType(4);
operationCmd.setRemarks(cmd.getRemarks());
return submit(operationCmd);
}
@Override
public PageResult<GmGoldOperationCO> page(GmGoldOperationPageQryCmd cmd) {
PageResult<GmGoldOperationRecord> page = recordService.query()
.eq(Objects.nonNull(cmd.getUserId()), GmGoldOperationRecord::getUserId, cmd.getUserId())
.eq(Objects.nonNull(cmd.getApplicantId()), GmGoldOperationRecord::getApplicantId,
cmd.getApplicantId())
.eq(Objects.nonNull(cmd.getOperationType()), GmGoldOperationRecord::getOperationType,
cmd.getOperationType())
.eq(Objects.nonNull(cmd.getStatus()), GmGoldOperationRecord::getStatus, cmd.getStatus())
.ge(Objects.nonNull(cmd.getStartTime()), GmGoldOperationRecord::getCreateTime,
toTimestamp(cmd.getStartTime()))
.le(Objects.nonNull(cmd.getEndTime()), GmGoldOperationRecord::getCreateTime,
toTimestamp(cmd.getEndTime()))
.orderByDesc(GmGoldOperationRecord::getId)
.page(cmd.getPageQuery());
Map<Long, UserProfileDTO> userProfileMap = mapUserProfiles(page.getRecords());
return page.convert(record -> toCO(record, userProfileMap.get(record.getUserId())));
}
@Override
@Transactional(rollbackFor = Exception.class)
public GmGoldOperationCO review(GmGoldOperationReviewCmd cmd) {
Long reviewerId = cmd.getReqUserId();
User reviewer = getAdminUser(reviewerId);
ResponseAssert.isTrue(ConsoleErrorCode.ACCOUNT_NOT_PERMISSIONS, isYumiAdmin(reviewer));
GmGoldOperationRecord record = recordService.getById(cmd.getId());
ResponseAssert.notNull(ConsoleErrorCode.NOT_FOUND_RECORD, record);
ResponseAssert.isTrue(ConsoleErrorCode.PROCESSED, STATUS_PENDING.equals(record.getStatus()));
if (Boolean.TRUE.equals(cmd.getApproved())) {
approveAndExecute(record, reviewer, cmd.getReviewRemark());
} else {
record.setStatus(STATUS_REJECTED);
record.setReviewerId(reviewer.getId().longValue());
record.setReviewerName(resolveAdminName(reviewer));
record.setReviewRemark(cmd.getReviewRemark());
record.setReviewTime(now());
recordService.updateSelectiveById(record);
}
return toCO(record, getUserProfile(record.getUserId()));
}
private void validateSubmit(GmGoldOperationCmd cmd) {
ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getReqUserId());
ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getUserId(),
cmd.getOperationType(), cmd.getQuantity(), cmd.getReasonType());
ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
Objects.equals(cmd.getOperationType(), OPERATION_INCOME)
|| Objects.equals(cmd.getOperationType(), OPERATION_EXPENDITURE));
ResponseAssert.isTrue(WalletErrorCode.REQUIRED_GT_ZERO,
cmd.getQuantity().compareTo(BigDecimal.ZERO) > 0);
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getRemarks());
}
private GmGoldOperationRecord buildRecord(GmGoldOperationCmd cmd, User applicant,
UserProfileDTO userProfile) {
Long id = IdWorkerUtils.getId();
return new GmGoldOperationRecord()
.setId(id)
.setUserId(userProfile.getId())
.setSysOrigin(userProfile.getOriginSys())
.setOperationType(cmd.getOperationType())
.setQuantity(cmd.getQuantity())
.setReasonType(cmd.getReasonType())
.setUsdQuantity(cmd.getUsdQuantity())
.setRemarks(cmd.getRemarks())
.setStatus(STATUS_PENDING)
.setApplicantId(applicant.getId().longValue())
.setApplicantName(resolveAdminName(applicant))
.setTrackId("GM-GOLD-" + id);
}
private void approveAndExecute(GmGoldOperationRecord record, User reviewer, String reviewRemark) {
executeWallet(record);
record.setStatus(STATUS_APPROVED);
record.setReviewerId(reviewer.getId().longValue());
record.setReviewerName(resolveAdminName(reviewer));
record.setReviewRemark(reviewRemark);
record.setReviewTime(now());
record.setExecutedTime(now());
recordService.updateSelectiveById(record);
}
private void executeWallet(GmGoldOperationRecord record) {
if (Objects.equals(record.getOperationType(), OPERATION_INCOME)) {
SendGoldCmd cmd = new SendGoldCmd();
cmd.setUserId(record.getUserId());
cmd.setQuantity(record.getQuantity());
cmd.setType(record.getReasonType());
cmd.setUsdQuantity(record.getUsdQuantity());
cmd.setRemarks(record.getRemarks());
cmd.setTrackId(record.getTrackId());
cmd.setReqUserId(record.getApplicantId());
cmd.setOperationUserId(record.getApplicantId());
userWalletService.sendGold(cmd);
return;
}
DeductGoldCmd cmd = new DeductGoldCmd();
cmd.setUserId(record.getUserId());
cmd.setQuantity(record.getQuantity());
cmd.setRemarks(record.getRemarks());
cmd.setTrackId(record.getTrackId());
cmd.setReqUserId(record.getApplicantId());
cmd.setOperationUserId(record.getApplicantId());
userWalletService.deductGold(cmd);
}
private User getAdminUser(Long userId) {
User user = userService.getById(Math.toIntExact(userId));
ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, user);
return user;
}
private UserProfileDTO getUserProfile(Long userId) {
UserProfileDTO userProfile = ResponseAssert.requiredSuccess(userProfileClient.getByUserId(userId));
ResponseAssert.notNull(ConsoleErrorCode.USER_NOT_FOUND, userProfile);
return userProfile;
}
private boolean isYumiAdmin(User user) {
return Objects.nonNull(user) && YUMI_ADMIN.equalsIgnoreCase(user.getLoginName());
}
private String resolveAdminName(User user) {
if (Objects.isNull(user)) {
return null;
}
if (Objects.nonNull(user.getNickname()) && !user.getNickname().isBlank()) {
return user.getNickname();
}
return user.getLoginName();
}
private Map<Long, UserProfileDTO> mapUserProfiles(Collection<GmGoldOperationRecord> records) {
if (CollectionUtils.isEmpty(records)) {
return Map.of();
}
Set<Long> userIds = records.stream()
.map(GmGoldOperationRecord::getUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (CollectionUtils.isEmpty(userIds)) {
return Map.of();
}
return ResponseAssert.requiredSuccess(userProfileClient.mapByUserIds(userIds));
}
private GmGoldOperationCO toCO(GmGoldOperationRecord record, UserProfileDTO userProfile) {
GmGoldOperationCO co = new GmGoldOperationCO();
co.setId(record.getId());
co.setUserId(record.getUserId());
co.setSysOrigin(record.getSysOrigin());
co.setOperationType(record.getOperationType());
co.setOperationName(operationName(record.getOperationType()));
co.setQuantity(record.getQuantity());
co.setReasonType(record.getReasonType());
co.setReasonName(reasonName(record.getOperationType(), record.getReasonType()));
co.setUsdQuantity(record.getUsdQuantity());
co.setRemarks(record.getRemarks());
co.setStatus(record.getStatus());
co.setStatusName(statusName(record.getStatus()));
co.setApplicantId(record.getApplicantId());
co.setApplicantName(record.getApplicantName());
co.setReviewerId(record.getReviewerId());
co.setReviewerName(record.getReviewerName());
co.setReviewRemark(record.getReviewRemark());
co.setTrackId(record.getTrackId());
co.setReviewTime(record.getReviewTime());
co.setExecutedTime(record.getExecutedTime());
co.setCreateTime(record.getCreateTime());
co.setUpdateTime(record.getUpdateTime());
co.setUserProfile(userProfile);
return co;
}
private String operationName(Integer operationType) {
return Objects.equals(operationType, OPERATION_INCOME) ? "增加金币" : "扣除金币";
}
private String statusName(String status) {
return switch (status) {
case STATUS_APPROVED -> "已通过";
case STATUS_REJECTED -> "已拒绝";
default -> "待审核";
};
}
private String reasonName(Integer operationType, Integer reasonType) {
if (Objects.equals(operationType, OPERATION_INCOME)) {
return switch (reasonType) {
case 1 -> "奖励";
case 2 -> "内部";
case 3 -> "工资";
case 4 -> "充值";
case 5 -> "其他";
default -> "未知";
};
}
return switch (reasonType) {
case 1 -> "违规";
case 2 -> "多发";
case 3 -> "操作错误";
case 4 -> "其他";
default -> "未知";
};
}
private Timestamp toTimestamp(Long value) {
return Objects.isNull(value) ? null : new Timestamp(value);
}
private Timestamp now() {
return new Timestamp(System.currentTimeMillis());
}
}

View File

@ -0,0 +1,72 @@
package com.red.circle.console.app.dto.clienobject.gm;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.red.circle.framework.dto.ClientObject;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import java.io.Serial;
import java.math.BigDecimal;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* GM金币操作申请.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GmGoldOperationCO extends ClientObject {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
private String sysOrigin;
private Integer operationType;
private String operationName;
private BigDecimal quantity;
private Integer reasonType;
private String reasonName;
private BigDecimal usdQuantity;
private String remarks;
private String status;
private String statusName;
@JsonSerialize(using = ToStringSerializer.class)
private Long applicantId;
private String applicantName;
@JsonSerialize(using = ToStringSerializer.class)
private Long reviewerId;
private String reviewerName;
private String reviewRemark;
private String trackId;
private Timestamp reviewTime;
private Timestamp executedTime;
private Timestamp createTime;
private Timestamp updateTime;
private UserProfileDTO userProfile;
}

View File

@ -0,0 +1,53 @@
package com.red.circle.console.app.dto.cmd.gm;
import com.red.circle.framework.core.dto.CommonCommand;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
import java.math.BigDecimal;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* GM金币操作申请.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GmGoldOperationCmd extends CommonCommand {
@Serial
private static final long serialVersionUID = 1L;
/**
* APP用户ID.
*/
@NotNull
private Long userId;
/**
* 操作类型: 0 增加, 1 扣除.
*/
@NotNull
private Integer operationType;
/**
* 金币数量.
*/
@NotNull
private BigDecimal quantity;
/**
* 原因类型.
*/
@NotNull
private Integer reasonType;
/**
* 工资/充值折算USD金额.
*/
private BigDecimal usdQuantity;
/**
* 备注.
*/
private String remarks;
}

View File

@ -0,0 +1,29 @@
package com.red.circle.console.app.dto.cmd.gm;
import com.red.circle.framework.core.dto.PageCommand;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* GM金币操作申请分页查询.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GmGoldOperationPageQryCmd extends PageCommand {
@Serial
private static final long serialVersionUID = 1L;
private Long userId;
private Long applicantId;
private Integer operationType;
private String status;
private Long startTime;
private Long endTime;
}

View File

@ -0,0 +1,26 @@
package com.red.circle.console.app.dto.cmd.gm;
import com.red.circle.framework.core.dto.CommonCommand;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* GM金币操作审核.
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GmGoldOperationReviewCmd extends CommonCommand {
@Serial
private static final long serialVersionUID = 1L;
@NotNull
private Long id;
@NotNull
private Boolean approved;
private String reviewRemark;
}

View File

@ -1,8 +1,10 @@
package com.red.circle.console.app.service.app.sys.pay;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import java.util.List;
import java.util.Map;
@ -42,4 +44,8 @@ public interface SysPayFactoryService {
* 获取渠道映射信息.
*/
Map<String, SysPayFactoryDTO> mapByCodes(Set<String> codes);
SysPayFactoryConfigDTO getFactoryConfig(SysPayFactoryConfigCmd param);
void saveOrUpdateFactoryConfig(SysPayFactoryConfigCmd param);
}

View File

@ -0,0 +1,25 @@
package com.red.circle.console.app.service.gm;
import com.red.circle.console.app.dto.clienobject.gm.GmGoldOperationCO;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationPageQryCmd;
import com.red.circle.console.app.dto.cmd.gm.GmGoldOperationReviewCmd;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.wallet.inner.model.cmd.DeductGoldCmd;
import com.red.circle.wallet.inner.model.cmd.SendGoldCmd;
/**
* GM金币操作.
*/
public interface GmGoldOperationService {
GmGoldOperationCO submit(GmGoldOperationCmd cmd);
GmGoldOperationCO submitSendGold(SendGoldCmd cmd);
GmGoldOperationCO submitDeductGold(DeductGoldCmd cmd);
PageResult<GmGoldOperationCO> page(GmGoldOperationPageQryCmd cmd);
GmGoldOperationCO review(GmGoldOperationReviewCmd cmd);
}

View File

@ -0,0 +1,10 @@
package com.red.circle.console.infra.database.rds.dao.app.gm;
import com.red.circle.console.infra.database.rds.entity.app.gm.GmGoldOperationRecord;
import com.red.circle.framework.mybatis.dao.BaseDAO;
/**
* GM金币操作申请记录 DAO.
*/
public interface GmGoldOperationRecordDAO extends BaseDAO<GmGoldOperationRecord> {
}

View File

@ -1,6 +1,8 @@
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.red.circle.console.infra.database.rds.enums.MenuTypeEnum;
import com.red.circle.console.infra.database.rds.enums.StatusEnum;
import com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
@ -26,10 +28,11 @@ public class Menu extends TimestampBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
protected Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
protected Integer id;
/**
* 父菜单ID一级菜单为0

View File

@ -1,6 +1,8 @@
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
@ -23,10 +25,11 @@ public class MenuResource implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
protected Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
protected Integer id;
/**
* 菜单ID

View File

@ -1,6 +1,8 @@
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -21,10 +23,11 @@ public class Role extends TimestampBaseEntity {
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
private Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 角色名称

View File

@ -20,7 +20,9 @@
*/
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -41,10 +43,11 @@ public class RoleMenu implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
protected Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
protected Integer id;
/**
* 角色ID

View File

@ -1,6 +1,8 @@
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -21,10 +23,11 @@ public class RoleResource implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
protected Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
protected Integer id;
/**
* 角色ID

View File

@ -20,7 +20,9 @@
*/
package com.red.circle.console.infra.database.rds.entity.admin;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -41,10 +43,11 @@ public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录id.
*/
private Integer id;
/**
* 记录id.
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 用户ID

View File

@ -0,0 +1,77 @@
package com.red.circle.console.infra.database.rds.entity.app.gm;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.sql.Timestamp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* GM金币操作申请记录.
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@TableName("gm_gold_operation_record")
public class GmGoldOperationRecord extends TimestampBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.INPUT)
private Long id;
@TableField("user_id")
private Long userId;
@TableField("sys_origin")
private String sysOrigin;
@TableField("operation_type")
private Integer operationType;
@TableField("quantity")
private BigDecimal quantity;
@TableField("reason_type")
private Integer reasonType;
@TableField("usd_quantity")
private BigDecimal usdQuantity;
@TableField("remarks")
private String remarks;
@TableField("status")
private String status;
@TableField("applicant_id")
private Long applicantId;
@TableField("applicant_name")
private String applicantName;
@TableField("reviewer_id")
private Long reviewerId;
@TableField("reviewer_name")
private String reviewerName;
@TableField("review_remark")
private String reviewRemark;
@TableField("track_id")
private String trackId;
@TableField("review_time")
private Timestamp reviewTime;
@TableField("executed_time")
private Timestamp executedTime;
}

View File

@ -0,0 +1,10 @@
package com.red.circle.console.infra.database.rds.service.app.gm;
import com.red.circle.console.infra.database.rds.entity.app.gm.GmGoldOperationRecord;
import com.red.circle.framework.mybatis.service.BaseService;
/**
* GM金币操作申请记录服务.
*/
public interface GmGoldOperationRecordService extends BaseService<GmGoldOperationRecord> {
}

View File

@ -0,0 +1,16 @@
package com.red.circle.console.infra.database.rds.service.app.gm.impl;
import com.red.circle.console.infra.database.rds.dao.app.gm.GmGoldOperationRecordDAO;
import com.red.circle.console.infra.database.rds.entity.app.gm.GmGoldOperationRecord;
import com.red.circle.console.infra.database.rds.service.app.gm.GmGoldOperationRecordService;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import org.springframework.stereotype.Service;
/**
* GM金币操作申请记录服务实现.
*/
@Service
public class GmGoldOperationRecordServiceImpl extends
BaseServiceImpl<GmGoldOperationRecordDAO, GmGoldOperationRecord>
implements GmGoldOperationRecordService {
}

View File

@ -1,19 +1,20 @@
package com.red.circle.order.app.command.pay.web;
import com.red.circle.component.pay.paymax.PayMaxUtils;
import com.red.circle.order.app.common.InAppPurchaseCommon;
import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport;
import com.red.circle.order.domain.order.MiFaPayReceivePaymentNotice;
import com.red.circle.order.infra.config.MiFaPayProperties;
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseCollectionReceiptService;
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayNoticeService;
import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay;
import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService;
import com.red.circle.tool.core.text.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import com.red.circle.component.pay.paymax.PayMaxUtils;
import com.red.circle.order.app.common.InAppPurchaseCommon;
import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport;
import com.red.circle.order.app.service.MiFaPayConfig;
import com.red.circle.order.app.service.MiFaPayConfigService;
import com.red.circle.order.domain.order.MiFaPayReceivePaymentNotice;
import com.red.circle.order.infra.database.mongo.order.InAppPurchaseCollectionReceiptService;
import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails;
import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay;
import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayNoticeService;
import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService;
import com.red.circle.tool.core.text.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
@ -30,12 +31,12 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe {
private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL";
private final MiFaPayProperties miFaPayProperties;
private final InAppPurchaseCommon inAppPurchaseCommon;
private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService;
private final OrderUserPurchasePayNoticeService orderUserPurchasePayNoticeService;
private final OrderUserPurchasePayService orderUserPurchasePayService;
private final MiFaPayConfigService miFaPayConfigService;
private final InAppPurchaseCommon inAppPurchaseCommon;
private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService;
private final OrderUserPurchasePayNoticeService orderUserPurchasePayNoticeService;
private final OrderUserPurchasePayService orderUserPurchasePayService;
/**
* 处理MiFaPay支付回调
@ -60,16 +61,26 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe {
return FAIL;
}
MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData = notice.getNoticeData();
OrderUserPurchasePay mysqlOrder = getMysqlOrder(noticeData.getOrderId());
if (Objects.isNull(mysqlOrder)) {
log.error("MiFaPay回调未找到MySQL订单: orderId={}", noticeData.getOrderId());
return FAIL;
}
// 验证签名对data字段验签
if (StringUtils.isBlank(notice.getSign()) || StringUtils.isBlank(notice.getData())) {
log.error("MiFaPay回调签名或data为空");
return FAIL;
}
MiFaPayConfig config = miFaPayConfigService.getConfig(
mysqlOrder.getFactoryCode(), mysqlOrder.getEvn(), mysqlOrder.getSysOrigin());
boolean validSign = PayMaxUtils.validSignRsa(
notice.getData(),
notice.getSign(),
miFaPayProperties.getPlatformRsaPublicKey()
config.getPlatformRsaPublicKey()
);
if (!validSign) {
@ -77,74 +88,67 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe {
return FAIL;
}
MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData = notice.getNoticeData();
OrderUserPurchasePay mysqlOrder = getMysqlOrder(noticeData.getOrderId());
if (Objects.isNull(mysqlOrder)) {
log.error("MiFaPay回调未找到MySQL订单: orderId={}", noticeData.getOrderId());
return FAIL;
}
return handleMysqlOrder(notice, noticeData, mysqlOrder);
}
private String handleMysqlOrder(MiFaPayReceivePaymentNotice notice,
MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData,
OrderUserPurchasePay mysqlOrder) {
orderUserPurchasePayNoticeService.addNotice(
mysqlOrder.getId(),
notice.getSign(),
"mifapay-" + noticeData.getOrderStatus(),
notice,
mysqlOrder.getUpdateUser()
);
if (!Objects.equals(mysqlOrder.getPayStatus(), MiFaPayMysqlOrderSupport.PAY_STATUS_PREPAYMENT)) {
log.warn("MiFaPay订单已处理(MySQL): orderId={}, status={}",
mysqlOrder.getId(), mysqlOrder.getPayStatus());
return SUCCESS;
}
InAppPurchaseDetails order = MiFaPayMysqlOrderSupport.toInAppPurchaseDetails(mysqlOrder);
if (noticeData.isSuccess()) {
if (order.receiptTypeEqReceipt()) {
inAppPurchaseCommon.inAppPurchaseReceipt(order,
() -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId()));
log.info("MiFaPay收款单处理成功(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
if (order.receiptTypeEqPayment()) {
inAppPurchaseCommon.inAppPurchasePayment(order,
() -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId()));
log.info("MiFaPay付款单处理成功(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
log.error("MiFaPay订单类型错误(MySQL): orderId={}", mysqlOrder.getId());
return FAIL;
}
if (noticeData.isFailed()) {
if (order.receiptTypeEqReceipt() && StringUtils.isNotBlank(mysqlOrder.getReferenceId())) {
inAppPurchaseCollectionReceiptService.updateStatusSuccessFail(
mysqlOrder.getReferenceId());
}
orderUserPurchasePayService.updateFail(
mysqlOrder.getId(),
String.format("MiFaPay支付失败: %s", noticeData.getOrderStatus())
);
log.info("MiFaPay支付失败处理完成(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
log.error("MiFaPay未知订单状态(MySQL): orderId={}, status={}",
mysqlOrder.getId(), noticeData.getOrderStatus());
return SUCCESS;
}
private OrderUserPurchasePay getMysqlOrder(String orderId) {
Long mysqlOrderId = MiFaPayMysqlOrderSupport.parseOrderId(orderId);
return mysqlOrderId == null ? null : orderUserPurchasePayService.getById(mysqlOrderId);
}
}
return handleMysqlOrder(notice, noticeData, mysqlOrder);
}
private String handleMysqlOrder(MiFaPayReceivePaymentNotice notice,
MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData,
OrderUserPurchasePay mysqlOrder) {
orderUserPurchasePayNoticeService.addNotice(
mysqlOrder.getId(),
notice.getSign(),
"mifapay-" + noticeData.getOrderStatus(),
notice,
mysqlOrder.getUpdateUser()
);
if (!Objects.equals(mysqlOrder.getPayStatus(), MiFaPayMysqlOrderSupport.PAY_STATUS_PREPAYMENT)) {
log.warn("MiFaPay订单已处理(MySQL): orderId={}, status={}",
mysqlOrder.getId(), mysqlOrder.getPayStatus());
return SUCCESS;
}
InAppPurchaseDetails order = MiFaPayMysqlOrderSupport.toInAppPurchaseDetails(mysqlOrder);
if (noticeData.isSuccess()) {
if (order.receiptTypeEqReceipt()) {
inAppPurchaseCommon.inAppPurchaseReceipt(order,
() -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId()));
log.info("MiFaPay收款单处理成功(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
if (order.receiptTypeEqPayment()) {
inAppPurchaseCommon.inAppPurchasePayment(order,
() -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId()));
log.info("MiFaPay付款单处理成功(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
log.error("MiFaPay订单类型错误(MySQL): orderId={}", mysqlOrder.getId());
return FAIL;
}
if (noticeData.isFailed()) {
if (order.receiptTypeEqReceipt() && StringUtils.isNotBlank(mysqlOrder.getReferenceId())) {
inAppPurchaseCollectionReceiptService.updateStatusSuccessFail(
mysqlOrder.getReferenceId());
}
orderUserPurchasePayService.updateFail(
mysqlOrder.getId(),
String.format("MiFaPay支付失败: %s", noticeData.getOrderStatus())
);
log.info("MiFaPay支付失败处理完成(MySQL): orderId={}", mysqlOrder.getId());
return SUCCESS;
}
log.error("MiFaPay未知订单状态(MySQL): orderId={}, status={}",
mysqlOrder.getId(), noticeData.getOrderStatus());
return SUCCESS;
}
private OrderUserPurchasePay getMysqlOrder(String orderId) {
Long mysqlOrderId = MiFaPayMysqlOrderSupport.parseOrderId(orderId);
return mysqlOrderId == null ? null : orderUserPurchasePayService.getById(mysqlOrderId);
}
}

View File

@ -117,7 +117,8 @@ public class PayWebApplicationCommodityV2QueryExe {
private List<PayApplicationCommodity> getPayApplicationCommodities(
PayWebApplicationCommodityCmd cmd) {
if (Objects.equals(cmd.requireReqSysOriginChildEnum(), SysOriginPlatformEnum.YOLO)) {
if (Objects.equals(cmd.requireReqSysOriginChildEnum(), SysOriginPlatformEnum.YOLO)
|| StringUtils.isNotBlank(cmd.getRegionId())) {
ResponseAssert
.isTrue(CommonErrorCode.NOT_SUPPORTED_REGION, StringUtils.isNotBlank(cmd.getRegionId()));

View File

@ -1,8 +1,8 @@
package com.red.circle.order.app.command.pay.web.strategy;
import com.red.circle.component.pay.paymax.service.PlaceAnOrderParam;
import com.red.circle.common.business.core.util.CountryCodeAliasUtils;
import com.red.circle.framework.core.asserts.ResponseAssert;
package com.red.circle.order.app.command.pay.web.strategy;
import com.red.circle.component.pay.paymax.service.PlaceAnOrderParam;
import com.red.circle.common.business.core.util.CountryCodeAliasUtils;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam;
import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO;
@ -66,10 +66,10 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra
private MiFaPayResponseCO requestApi(PayPlaceAnOrderDetailsV2 details, MiFaPayPlaceAnOrderParam param) {
try {
return miFaPayService.placeAnOrder(param);
return miFaPayService.placeAnOrder(param, details.getSysOrigin());
} catch (Exception ex) {
ex.printStackTrace();
log.error("MiFaPay订单创建请求失败:details={}、requestParam={}",
log.error("MiFaPay订单创建请求失败:details={}、requestParam={}",
JacksonUtils.toJson(details), JacksonUtils.toJson(param));
}
return null;
@ -102,13 +102,13 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra
.amount(amountInCents)
.currency(details.getCurrency())
.payWay(extractPayWay(details.getChannelCode()))
.payType(details.getFactoryChannel())
.userIp(details.getRequestIp())
.returnUrl(details.getSuccessRedirectUrl())
.notifyUrl(miFaPayService.getNotifyUrl())
.language("en")
.email(memberInfo.getEmail())
.countryCode(replaceCountryCode(details.getCountryCode()))
.payType(details.getFactoryChannel())
.userIp(details.getRequestIp())
.returnUrl(details.getSuccessRedirectUrl())
.notifyUrl(miFaPayService.getNotifyUrl(details.getSysOrigin()))
.language("en")
.email(memberInfo.getEmail())
.countryCode(replaceCountryCode(details.getCountryCode()))
.firstName(memberInfo.getFirstName())
.lastName(memberInfo.getLastName())
.phone(memberInfo.getPhone())
@ -126,9 +126,9 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra
return underscoreIndex > 0 ? channelCode.substring(0, underscoreIndex) : channelCode;
}
private static String replaceCountryCode(String countryCode) {
return CountryCodeAliasUtils.normalizeSaudiCode(countryCode);
}
private static String replaceCountryCode(String countryCode) {
return CountryCodeAliasUtils.normalizeSaudiCode(countryCode);
}
private String getPlaceAnOrderPaymentDetailParam(PayPlaceAnOrderDetailsV2 details) {
@ -139,7 +139,7 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra
if (StringUtils.isBlank(details.getFactoryChannel())) {
return null;
}
PlaceAnOrderPaymentDetail paymentDetail = new PlaceAnOrderPaymentDetail();
paymentDetail.setPaymentType(details.getFactoryChannel());
if (paymentDetail.getPaymentType().equals("PROMPTPAY")) {

View File

@ -0,0 +1,30 @@
package com.red.circle.order.app.service;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* MiFaPay 运行配置.
*/
@Data
@Accessors(chain = true)
public class MiFaPayConfig {
private String factoryCode;
private String env;
private String sysOrigin;
private String merAccount;
private String merNo;
private String gatewayBaseUrl;
private String rsaPrivateKey;
private String platformRsaPublicKey;
private String notifyUrl;
}

View File

@ -0,0 +1,107 @@
package com.red.circle.order.app.service;
import com.red.circle.framework.web.props.EnvProperties;
import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport;
import com.red.circle.order.app.command.pay.web.strategy.PayEnv;
import com.red.circle.order.infra.config.PayFactoryConfigProperties;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryConfigService;
import com.red.circle.tool.core.text.StringUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* MiFaPay DB 配置读取服务.
*/
@Service
@RequiredArgsConstructor
public class MiFaPayConfigService {
private final EnvProperties envProperties;
private final PayFactoryConfigProperties properties;
private final PayFactoryConfigService payFactoryConfigService;
private final ConcurrentMap<String, CacheItem> cache = new ConcurrentHashMap<>();
public MiFaPayConfig getConfig(String sysOrigin) {
return getConfig(MiFaPayMysqlOrderSupport.FACTORY_CODE, currentEnv(), sysOrigin);
}
public MiFaPayConfig getConfig(String env, String sysOrigin) {
return getConfig(MiFaPayMysqlOrderSupport.FACTORY_CODE, env, sysOrigin);
}
public MiFaPayConfig getConfig(String factoryCode, String env, String sysOrigin) {
String normalizedFactoryCode = StringUtils.isBlank(factoryCode)
? MiFaPayMysqlOrderSupport.FACTORY_CODE : factoryCode.trim();
String normalizedEnv = StringUtils.isBlank(env) ? currentEnv() : env.trim();
String normalizedOrigin = normalize(sysOrigin);
String cacheKey = normalizedFactoryCode + "|" + normalizedEnv + "|" + normalizedOrigin;
long now = System.currentTimeMillis();
CacheItem cached = cache.get(cacheKey);
if (cached != null && cached.expireAt > now) {
return cached.config;
}
MiFaPayConfig config = loadConfig(normalizedFactoryCode, normalizedEnv, normalizedOrigin);
cache.put(cacheKey, new CacheItem(config, now + cacheTtlMillis()));
return config;
}
public String currentEnv() {
return envProperties.isProd() ? PayEnv.PROD.name() : PayEnv.TEST.name();
}
private MiFaPayConfig loadConfig(String factoryCode, String env, String sysOrigin) {
PayFactoryConfig config = payFactoryConfigService.getEnabledByScope(
factoryCode, env, sysOrigin);
if (config == null) {
throw new IllegalStateException(
"MiFaPay config missing: factoryCode=" + factoryCode
+ ", env=" + env + ", sysOrigin=" + sysOrigin);
}
MiFaPayConfig runtimeConfig = new MiFaPayConfig()
.setFactoryCode(config.getFactoryCode())
.setEnv(config.getEnv())
.setSysOrigin(config.getSysOrigin())
.setMerAccount(config.getMerAccount())
.setMerNo(config.getMerNo())
.setGatewayBaseUrl(config.getGatewayBaseUrl())
.setRsaPrivateKey(config.getRsaPrivateKey())
.setPlatformRsaPublicKey(config.getPlatformRsaPublicKey())
.setNotifyUrl(config.getNotifyUrl());
validate(runtimeConfig);
return runtimeConfig;
}
private long cacheTtlMillis() {
Long seconds = properties.getCacheTtlSeconds();
if (seconds == null || seconds <= 0) {
return TimeUnit.MINUTES.toMillis(5);
}
return TimeUnit.SECONDS.toMillis(seconds);
}
private void validate(MiFaPayConfig config) {
validateRequiredValue("merAccount", config.getMerAccount());
validateRequiredValue("merNo", config.getMerNo());
validateRequiredValue("gatewayBaseUrl", config.getGatewayBaseUrl());
validateRequiredValue("rsaPrivateKey", config.getRsaPrivateKey());
validateRequiredValue("platformRsaPublicKey", config.getPlatformRsaPublicKey());
validateRequiredValue("notifyUrl", config.getNotifyUrl());
}
private void validateRequiredValue(String fieldName, String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalStateException("MiFaPay config missing: " + fieldName);
}
}
private String normalize(String value) {
return StringUtils.isBlank(value) ? "" : value.trim();
}
private record CacheItem(MiFaPayConfig config, long expireAt) {
}
}

View File

@ -1,18 +1,16 @@
package com.red.circle.order.app.service;
import com.red.circle.component.pay.paymax.PayMaxUtils;
import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam;
import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO;
import com.red.circle.order.infra.config.MiFaPayProperties;
import com.red.circle.tool.core.http.RcHttpClient;
import com.red.circle.tool.core.json.JacksonUtils;
import com.red.circle.tool.core.text.StringUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.red.circle.component.pay.paymax.PayMaxUtils;
import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam;
import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO;
import com.red.circle.tool.core.http.RcHttpClient;
import com.red.circle.tool.core.json.JacksonUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.util.Objects;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* MiFaPay支付服务
@ -21,135 +19,126 @@ import java.util.Objects;
*/
@Slf4j
@Service
public class MiFaPayService {
/**
* 下单接口路径
*/
private static final String PLACE_ORDER_PATH = "/paygateway/mbpay/order/en_v2";
public class MiFaPayService {
private final String active;
private final MiFaPayProperties miFaPayProperties;
private final RcHttpClient rcHttpClient = RcHttpClient.builder().build();
/**
* 下单接口路径
*/
private static final String PLACE_ORDER_PATH = "/paygateway/mbpay/order/en_v2";
public MiFaPayService(@Value("${spring.profiles.active:dev}") String active,
MiFaPayProperties miFaPayProperties) {
this.active = active;
this.miFaPayProperties = miFaPayProperties;
private final String active;
private final MiFaPayConfigService miFaPayConfigService;
private final RcHttpClient rcHttpClient = RcHttpClient.builder().build();
public MiFaPayService(@Value("${spring.profiles.active:dev}") String active,
MiFaPayConfigService miFaPayConfigService) {
this.active = active;
this.miFaPayConfigService = miFaPayConfigService;
}
/**
* 发起支付下单
*
* @param param 下单参数
* @return 支付响应
*/
public MiFaPayResponseCO placeAnOrder(MiFaPayPlaceAnOrderParam param, String sysOrigin) {
MiFaPayConfig config = miFaPayConfigService.getConfig(sysOrigin);
// 设置商户编号
param.setMerNo(config.getMerNo());
String url = config.getGatewayBaseUrl() + PLACE_ORDER_PATH;
// 将param转为JSON字符串作为data
String dataJson = JacksonUtils.toJson(param);
// 对data进行签名
String sign = PayMaxUtils.signRsa(dataJson, config.getRsaPrivateKey());
// 构建请求体merAccount + sign + data
MiFaPayRequest request = new MiFaPayRequest();
request.setMerAccount(config.getMerAccount());
request.setSign(sign);
request.setData(dataJson);
String body = JacksonUtils.toJson(request);
// 非生产环境打印日志
if (!Objects.equals(active, "prod")) {
log.warn("MiFaPay Request URL: {}\nBody:{}", url, body);
}
MiFaPayResponseCO response = rcHttpClient.post()
.uri(url)
.contentType("application/json")
.bodyValue(body)
.retrieve()
.bodyToEntity(MiFaPayResponseCO.class)
.block();
// 非生产环境打印响应日志
if (!Objects.equals(active, "prod")) {
log.warn("MiFaPay Response: {}", JacksonUtils.toJson(response));
}
verifySuccessfulResponse(response, config);
return response;
}
public String getNotifyUrl(String sysOrigin) {
return miFaPayConfigService.getConfig(sysOrigin).getNotifyUrl();
}
private void verifySuccessfulResponse(MiFaPayResponseCO response, MiFaPayConfig config) {
if (response == null || !response.isSuccess()) {
return;
}
validateRequiredValue("response.data", response.getData());
validateRequiredValue("response.sign", response.getSign());
if (StringUtils.isNotBlank(response.getMerAccount())
&& !Objects.equals(response.getMerAccount(), config.getMerAccount())) {
throw new IllegalStateException("MiFaPay response merAccount mismatch");
}
boolean validSign = PayMaxUtils.validSignRsa(
response.getData(),
response.getSign(),
config.getPlatformRsaPublicKey()
);
if (!validSign) {
throw new IllegalStateException("MiFaPay response signature verification failed");
}
}
private void validateRequiredValue(String fieldName, String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalStateException("MiFaPay config missing: " + fieldName);
}
}
/**
* MiFaPay请求体结构
*/
@Data
public static class MiFaPayRequest {
/**
* 发起支付下单
*
* @param param 下单参数
* @return 支付响应
* 商户标识
*/
public MiFaPayResponseCO placeAnOrder(MiFaPayPlaceAnOrderParam param) {
validateRequiredConfig();
// 设置商户编号
param.setMerNo(miFaPayProperties.getMerNo());
private String merAccount;
String url = miFaPayProperties.getGatewayBaseUrl() + PLACE_ORDER_PATH;
// 将param转为JSON字符串作为data
String dataJson = JacksonUtils.toJson(param);
// 对data进行签名
String sign = PayMaxUtils.signRsa(dataJson, miFaPayProperties.getRsaPrivateKey());
/**
* 签名
*/
private String sign;
// 构建请求体merAccount + sign + data
MiFaPayRequest request = new MiFaPayRequest();
request.setMerAccount(miFaPayProperties.getMerAccount());
request.setSign(sign);
request.setData(dataJson);
String body = JacksonUtils.toJson(request);
// 非生产环境打印日志
if (!Objects.equals(active, "prod")) {
log.warn("MiFaPay Request URL: {}\nBody:{}", url, body);
}
MiFaPayResponseCO response = rcHttpClient.post()
.uri(url)
.contentType("application/json")
.bodyValue(body)
.retrieve()
.bodyToEntity(MiFaPayResponseCO.class)
.block();
// 非生产环境打印响应日志
if (!Objects.equals(active, "prod")) {
log.warn("MiFaPay Response: {}", JacksonUtils.toJson(response));
}
verifySuccessfulResponse(response);
return response;
}
public String getNotifyUrl() {
validateRequiredValue("notifyUrl", miFaPayProperties.getNotifyUrl());
return miFaPayProperties.getNotifyUrl();
}
private void verifySuccessfulResponse(MiFaPayResponseCO response) {
if (response == null || !response.isSuccess()) {
return;
}
validateRequiredValue("response.data", response.getData());
validateRequiredValue("response.sign", response.getSign());
if (StringUtils.isNotBlank(response.getMerAccount())
&& !Objects.equals(response.getMerAccount(), miFaPayProperties.getMerAccount())) {
throw new IllegalStateException("MiFaPay response merAccount mismatch");
}
boolean validSign = PayMaxUtils.validSignRsa(
response.getData(),
response.getSign(),
miFaPayProperties.getPlatformRsaPublicKey()
);
if (!validSign) {
throw new IllegalStateException("MiFaPay response signature verification failed");
}
}
private void validateRequiredConfig() {
validateRequiredValue("merAccount", miFaPayProperties.getMerAccount());
validateRequiredValue("merNo", miFaPayProperties.getMerNo());
validateRequiredValue("gatewayBaseUrl", miFaPayProperties.getGatewayBaseUrl());
validateRequiredValue("rsaPrivateKey", miFaPayProperties.getRsaPrivateKey());
validateRequiredValue("platformRsaPublicKey", miFaPayProperties.getPlatformRsaPublicKey());
validateRequiredValue("notifyUrl", miFaPayProperties.getNotifyUrl());
}
private void validateRequiredValue(String fieldName, String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalStateException("MiFaPay config missing: " + fieldName);
}
}
/**
* MiFaPay请求体结构
*/
@Data
public static class MiFaPayRequest {
/**
* 商户标识
*/
private String merAccount;
/**
* 签名
*/
private String sign;
/**
* 主请求参数JSON字符串
*/
private String data;
}
/**
* 主请求参数JSON字符串
*/
private String data;
}
}

View File

@ -1,48 +0,0 @@
package com.red.circle.order.infra.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* MiFaPay配置属性
*
* @author system
*/
@Data
@Component
@ConfigurationProperties(prefix = "red-circle.pay.mifa-pay")
@RefreshScope
public class MiFaPayProperties {
/**
* 商户标识平台分配
*/
private String merAccount;
/**
* 商户号
*/
private String merNo;
/**
* 网关基础URL
*/
private String gatewayBaseUrl;
/**
* 商户私钥用于签名
*/
private String rsaPrivateKey;
/**
* 平台公钥用于验签
*/
private String platformRsaPublicKey;
/**
* 支付结果异步通知地址
*/
private String notifyUrl;
}

View File

@ -0,0 +1,19 @@
package com.red.circle.order.infra.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 支付厂商运行配置.
*/
@Data
@Component
@ConfigurationProperties(prefix = "red-circle.pay.factory-config")
public class PayFactoryConfigProperties {
/**
* order 运行时读取配置的本地缓存 TTL.
*/
private Long cacheTtlSeconds = 300L;
}

View File

@ -0,0 +1,11 @@
package com.red.circle.order.infra.database.rds.dao.pay;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
/**
* 支付厂商运行配置 Mapper.
*/
public interface PayFactoryConfigDAO extends BaseDAO<PayFactoryConfig> {
}

View File

@ -0,0 +1,56 @@
package com.red.circle.order.infra.database.rds.entity.pay;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.red.circle.framework.mybatis.entity.TimestampBaseEntity;
import java.io.Serial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付厂商运行配置.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_pay_factory_config")
public class PayFactoryConfig extends TimestampBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@TableId("id")
private Long id;
@TableField("factory_code")
private String factoryCode;
@TableField("env")
private String env;
@TableField("sys_origin")
private String sysOrigin;
@TableField("mer_account")
private String merAccount;
@TableField("mer_no")
private String merNo;
@TableField("gateway_base_url")
private String gatewayBaseUrl;
@TableField("rsa_private_key")
private String rsaPrivateKey;
@TableField("platform_rsa_public_key")
private String platformRsaPublicKey;
@TableField("notify_url")
private String notifyUrl;
@TableField("enabled")
private Boolean enabled;
}

View File

@ -0,0 +1,14 @@
package com.red.circle.order.infra.database.rds.service.pay;
import com.red.circle.framework.mybatis.service.BaseService;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
/**
* 支付厂商运行配置.
*/
public interface PayFactoryConfigService extends BaseService<PayFactoryConfig> {
PayFactoryConfig getByScope(String factoryCode, String env, String sysOrigin);
PayFactoryConfig getEnabledByScope(String factoryCode, String env, String sysOrigin);
}

View File

@ -0,0 +1,54 @@
package com.red.circle.order.infra.database.rds.service.pay.impl;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.order.infra.database.rds.dao.pay.PayFactoryConfigDAO;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryConfigService;
import com.red.circle.tool.core.text.StringUtils;
import org.springframework.stereotype.Service;
/**
* 支付厂商运行配置.
*/
@Service
public class PayFactoryConfigServiceImpl extends
BaseServiceImpl<PayFactoryConfigDAO, PayFactoryConfig> implements PayFactoryConfigService {
@Override
public PayFactoryConfig getByScope(String factoryCode, String env, String sysOrigin) {
return getByScope(factoryCode, env, sysOrigin, null);
}
@Override
public PayFactoryConfig getEnabledByScope(String factoryCode, String env, String sysOrigin) {
PayFactoryConfig exact = getByScope(factoryCode, env, sysOrigin, Boolean.TRUE);
if (exact != null) {
return exact;
}
PayFactoryConfig envDefault = getByScope(factoryCode, env, "", Boolean.TRUE);
if (envDefault != null) {
return envDefault;
}
PayFactoryConfig originDefault = getByScope(factoryCode, "", sysOrigin, Boolean.TRUE);
if (originDefault != null) {
return originDefault;
}
return getByScope(factoryCode, "", "", Boolean.TRUE);
}
private PayFactoryConfig getByScope(String factoryCode, String env, String sysOrigin,
Boolean enabled) {
return query()
.eq(PayFactoryConfig::getFactoryCode, normalize(factoryCode))
.eq(PayFactoryConfig::getEnv, normalize(env))
.eq(PayFactoryConfig::getSysOrigin, normalize(sysOrigin))
.eq(enabled != null, PayFactoryConfig::getEnabled, enabled)
.last(PageConstant.LIMIT_ONE)
.getOne();
}
private String normalize(String value) {
return StringUtils.isBlank(value) ? "" : value.trim();
}
}

View File

@ -10,6 +10,7 @@ import com.red.circle.order.infra.database.rds.entity.pay.PayCountry;
import com.red.circle.order.infra.database.rds.entity.pay.PayCountryChannel;
import com.red.circle.order.infra.database.rds.entity.pay.PayCountryChannelDetails;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactory;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
import com.red.circle.order.infra.database.rds.query.SysPayChannelQry;
import com.red.circle.order.inner.model.cmd.SysPayApplicationCommodityCmd;
import com.red.circle.order.inner.model.cmd.SysPayChannelCmd;
@ -17,6 +18,7 @@ import com.red.circle.order.inner.model.cmd.SysPayChannelFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayChannelQryCmd;
import com.red.circle.order.inner.model.cmd.SysPayCountryAddCmd;
import com.red.circle.order.inner.model.cmd.SysPayCountryChannelDetailsCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.dto.CountryCodeDTO;
import com.red.circle.order.inner.model.dto.PayCountryDTO;
@ -26,6 +28,7 @@ import com.red.circle.order.inner.model.dto.SysPayChannelDTO;
import com.red.circle.order.inner.model.dto.SysPayCountryChannelDTO;
import com.red.circle.order.inner.model.dto.SysPayCountryChannelDetailsDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryAssociatedChannelsDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import com.red.circle.other.inner.model.dto.sys.SysCountryCodeDTO;
import java.util.List;
@ -58,6 +61,10 @@ public interface WebPayInnerConvertor {
PayFactory toPayFactory(SysPayFactoryCmd cmd);
PayFactoryConfig toPayFactoryConfig(SysPayFactoryConfigCmd cmd);
SysPayFactoryConfigDTO toSysPayFactoryConfigDTO(PayFactoryConfig config);
Map<String, SysPayChannelDTO> toMapSysPayChannelDTO(Map<String, PayChannel> map);
SysPayFactoryAssociatedChannelsDTO toSysPayFactoryAssociatedChannelsDTO(

View File

@ -3,8 +3,10 @@ package com.red.circle.order.inner.endpoint;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.order.inner.endpoint.api.PayFactoryClientApi;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import com.red.circle.order.inner.service.PayFactoryClientService;
import java.util.List;
@ -56,4 +58,15 @@ public class PayFactoryClientEndpoint implements PayFactoryClientApi {
payFactoryClientService.updateSysPayChannel(param);
return ResultResponse.success();
}
@Override
public ResultResponse<SysPayFactoryConfigDTO> getFactoryConfig(SysPayFactoryConfigCmd param) {
return ResultResponse.success(payFactoryClientService.getFactoryConfig(param));
}
@Override
public ResultResponse<Void> saveOrUpdateFactoryConfig(SysPayFactoryConfigCmd param) {
payFactoryClientService.saveOrUpdateFactoryConfig(param);
return ResultResponse.success();
}
}

View File

@ -2,8 +2,10 @@ package com.red.circle.order.inner.service;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import java.util.List;
import java.util.Map;
@ -25,4 +27,8 @@ public interface PayFactoryClientService {
void addSysPayChannel(SysPayFactoryCmd param);
void updateSysPayChannel(SysPayFactoryCmd param);
SysPayFactoryConfigDTO getFactoryConfig(SysPayFactoryConfigCmd param);
void saveOrUpdateFactoryConfig(SysPayFactoryConfigCmd param);
}

View File

@ -1,14 +1,24 @@
package com.red.circle.order.inner.service.impl;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryService;
import com.red.circle.order.inner.convertor.WebPayInnerConvertor;
import com.red.circle.order.infra.database.rds.entity.pay.PayFactoryConfig;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryConfigService;
import com.red.circle.order.infra.database.rds.service.pay.PayFactoryService;
import com.red.circle.order.inner.convertor.WebPayInnerConvertor;
import com.red.circle.order.inner.model.cmd.SysPayFactoryConfigCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryCmd;
import com.red.circle.order.inner.model.cmd.SysPayFactoryQryCmd;
import com.red.circle.order.inner.model.dto.SysPayFactoryConfigDTO;
import com.red.circle.order.inner.model.dto.SysPayFactoryDTO;
import com.red.circle.order.inner.service.PayFactoryClientService;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -22,8 +32,9 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class PayFactoryClientServiceImpl implements PayFactoryClientService {
private final PayFactoryService payFactoryService;
private final WebPayInnerConvertor webPayInnerConvertor;
private final PayFactoryService payFactoryService;
private final PayFactoryConfigService payFactoryConfigService;
private final WebPayInnerConvertor webPayInnerConvertor;
@Override
public Map<String, SysPayFactoryDTO> mapByCodes(Set<String> codes) {
@ -57,4 +68,69 @@ public class PayFactoryClientServiceImpl implements PayFactoryClientService {
public void updateSysPayChannel(SysPayFactoryCmd param) {
payFactoryService.updateSysPayChannel(webPayInnerConvertor.toPayFactory(param));
}
}
@Override
public SysPayFactoryConfigDTO getFactoryConfig(SysPayFactoryConfigCmd param) {
normalize(param);
PayFactoryConfig config = payFactoryConfigService.getByScope(
param.getFactoryCode(), param.getEnv(), param.getSysOrigin());
return webPayInnerConvertor.toSysPayFactoryConfigDTO(config);
}
@Override
public void saveOrUpdateFactoryConfig(SysPayFactoryConfigCmd param) {
normalize(param);
validate(param);
PayFactoryConfig existing = findExisting(param);
PayFactoryConfig entity = webPayInnerConvertor.toPayFactoryConfig(param);
if (existing != null) {
entity.setId(existing.getId());
entity.setCreateTime(existing.getCreateTime());
entity.setCreateUser(existing.getCreateUser());
entity.setUpdateTime(Objects.requireNonNullElse(param.getUpdateTime(), TimestampUtils.now()));
payFactoryConfigService.updateSelectiveById(entity);
return;
}
entity.setId(Objects.requireNonNullElseGet(param.getId(), IdWorkerUtils::getId));
entity.setCreateTime(Objects.requireNonNullElse(param.getCreateTime(), TimestampUtils.now()));
entity.setUpdateTime(param.getUpdateTime());
payFactoryConfigService.save(entity);
}
private PayFactoryConfig findExisting(SysPayFactoryConfigCmd param) {
if (param.getId() != null) {
PayFactoryConfig byId = payFactoryConfigService.getById(param.getId());
if (byId != null) {
return byId;
}
}
return payFactoryConfigService.getByScope(
param.getFactoryCode(), param.getEnv(), param.getSysOrigin());
}
private void normalize(SysPayFactoryConfigCmd param) {
param.setFactoryCode(normalize(param.getFactoryCode()));
param.setEnv(normalize(param.getEnv()));
param.setSysOrigin(normalize(param.getSysOrigin()));
if (param.getEnabled() == null) {
param.setEnabled(Boolean.TRUE);
}
}
private String normalize(String value) {
return StringUtils.isBlank(value) ? "" : value.trim();
}
private void validate(SysPayFactoryConfigCmd param) {
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getFactoryCode());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getMerAccount());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getMerNo());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getGatewayBaseUrl());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
param.getPlatformRsaPublicKey());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getNotifyUrl());
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, param.getRsaPrivateKey());
}
}

View File

@ -146,12 +146,14 @@ public class TmpGoldWalletServiceImpl implements TmpGoldWalletService {
List<WalletGoldAssetRecord> assetRecords = queryChain
.ge(WalletGoldAssetRecord::getCreateTime, cmd.getStartTime())
.le(WalletGoldAssetRecord::getCreateTime, cmd.getEndTime())
.eq(Objects.nonNull(cmd.getId()), WalletGoldAssetRecord::getId, cmd.getId())
.eq(Objects.nonNull(cmd.getQueryBackOperationUser()), WalletGoldAssetRecord::getOpUserType,
cmd.getQueryBackOperationUser())
.eq(Objects.nonNull(cmd.getType()), WalletGoldAssetRecord::getType, cmd.getType())
.eq(Objects.nonNull(cmd.getUserId()), WalletGoldAssetRecord::getUserId, cmd.getUserId())
.le(WalletGoldAssetRecord::getCreateTime, cmd.getEndTime())
.eq(Objects.nonNull(cmd.getId()), WalletGoldAssetRecord::getId, cmd.getId())
.eq(Objects.nonNull(cmd.getQueryBackOperationUser()), WalletGoldAssetRecord::getOpUserType,
cmd.getQueryBackOperationUser())
.eq(Objects.nonNull(cmd.getBackOperationUser()), WalletGoldAssetRecord::getCreateUser,
cmd.getBackOperationUser())
.eq(Objects.nonNull(cmd.getType()), WalletGoldAssetRecord::getType, cmd.getType())
.eq(Objects.nonNull(cmd.getUserId()), WalletGoldAssetRecord::getUserId, cmd.getUserId())
.eq(StringUtils.isNotBlank(cmd.getTrackId()), WalletGoldAssetRecord::getEventId,
cmd.getTrackId())
.eq(StringUtils.isNotBlank(cmd.getOrigin()), WalletGoldAssetRecord::getEventType,
@ -185,13 +187,15 @@ public class TmpGoldWalletServiceImpl implements TmpGoldWalletService {
.setType(assetRecord.getType())
.setOrigin(assetRecord.getEventType())
.setOriginName(assetRecord.getEventDesc())
.setStatus(Optional.ofNullable(statusMap.get(assetRecord.getId()))
.map(Objects::toString)
.orElse(""))
.setRemark(remarkValue.get(Objects.toString(assetRecord.getId())))
.setCreateTime(assetRecord.getCreateTime().getTime())
.setUpdateTime(assetRecord.getUpdateTime().getTime())
).toList();
.setStatus(Optional.ofNullable(statusMap.get(assetRecord.getId()))
.map(Objects::toString)
.orElse(""))
.setRemark(remarkValue.get(Objects.toString(assetRecord.getId())))
.setBackOperationUser(Objects.equals(assetRecord.getOpUserType(), 1)
? assetRecord.getCreateUser() : null)
.setCreateTime(assetRecord.getCreateTime().getTime())
.setUpdateTime(assetRecord.getUpdateTime().getTime())
).toList();
}
@ -388,10 +392,11 @@ public class TmpGoldWalletServiceImpl implements TmpGoldWalletService {
private String getQueryString(UserGoldRunningWaterBackQryCmd cmd) {
List<String> query = Stream.of(
toQuery("sysOrigin", getQuerySysOrigin(cmd)),
toQuery("id", cmd.getId()),
toQuery("opUserType", DataTypeUtils.toInteger(cmd.getQueryBackOperationUser())),
toQuery("type", cmd.getType()),
toQuery("userId", cmd.getUserId()),
toQuery("id", cmd.getId()),
toQuery("opUserType", DataTypeUtils.toInteger(cmd.getQueryBackOperationUser())),
toQuery("createUser", cmd.getBackOperationUser()),
toQuery("type", cmd.getType()),
toQuery("userId", cmd.getUserId()),
toQuery("eventId", cmd.getTrackId()),
toQuery("eventType", getOrigin(cmd.getOrigin())))
.filter(StringUtils::isNotBlank).toList();

View File

@ -161,12 +161,14 @@ public class WalletGoldClientServiceImpl implements WalletGoldClientService {
List<WalletGoldAssetRecord> assetRecords = queryChain
.ge(WalletGoldAssetRecord::getCreateTime, cmd.getStartTime())
.le(WalletGoldAssetRecord::getCreateTime, cmd.getEndTime())
.eq(Objects.nonNull(cmd.getId()), WalletGoldAssetRecord::getId, cmd.getId())
.eq(Objects.nonNull(cmd.getQueryBackOperationUser()), WalletGoldAssetRecord::getOpUserType,
cmd.getQueryBackOperationUser())
.eq(Objects.nonNull(cmd.getType()), WalletGoldAssetRecord::getType, cmd.getType())
.eq(Objects.nonNull(cmd.getUserId()), WalletGoldAssetRecord::getUserId, cmd.getUserId())
.le(WalletGoldAssetRecord::getCreateTime, cmd.getEndTime())
.eq(Objects.nonNull(cmd.getId()), WalletGoldAssetRecord::getId, cmd.getId())
.eq(Objects.nonNull(cmd.getQueryBackOperationUser()), WalletGoldAssetRecord::getOpUserType,
cmd.getQueryBackOperationUser())
.eq(Objects.nonNull(cmd.getBackOperationUser()), WalletGoldAssetRecord::getCreateUser,
cmd.getBackOperationUser())
.eq(Objects.nonNull(cmd.getType()), WalletGoldAssetRecord::getType, cmd.getType())
.eq(Objects.nonNull(cmd.getUserId()), WalletGoldAssetRecord::getUserId, cmd.getUserId())
.eq(StringUtils.isNotBlank(cmd.getTrackId()), WalletGoldAssetRecord::getEventId,
cmd.getTrackId())
.eq(StringUtils.isNotBlank(cmd.getOrigin()), WalletGoldAssetRecord::getEventType,
@ -201,12 +203,14 @@ public class WalletGoldClientServiceImpl implements WalletGoldClientService {
.setType(assetRecord.getType())
.setOrigin(assetRecord.getEventType())
.setOriginName(assetRecord.getEventDesc())
.setStatus(Optional.ofNullable(statusMap.get(assetRecord.getId()))
.map(Objects::toString).orElse(""))
.setRemark(remarkValue.get(Objects.toString(assetRecord.getId())))
.setCreateTime(assetRecord.getCreateTime().getTime())
.setUpdateTime(assetRecord.getUpdateTime().getTime())
).toList();
.setStatus(Optional.ofNullable(statusMap.get(assetRecord.getId()))
.map(Objects::toString).orElse(""))
.setRemark(remarkValue.get(Objects.toString(assetRecord.getId())))
.setBackOperationUser(Objects.equals(assetRecord.getOpUserType(), 1)
? assetRecord.getCreateUser() : null)
.setCreateTime(assetRecord.getCreateTime().getTime())
.setUpdateTime(assetRecord.getUpdateTime().getTime())
).toList();
return walletInnerConvertor.toListUserGoldRunningWaterHistoryDTO(co);
}
@ -298,10 +302,11 @@ public class WalletGoldClientServiceImpl implements WalletGoldClientService {
private String getQueryString(UserGoldRunningWaterBackQryCmd cmd) {
List<String> query = Stream.of(
toQuery("sysOrigin", querySysOrigin(cmd)),
toQuery("id", cmd.getId()),
toQuery("opUserType", DataTypeUtils.toInteger(cmd.getQueryBackOperationUser())),
toQuery("type", cmd.getType()),
toQuery("userId", cmd.getUserId()),
toQuery("id", cmd.getId()),
toQuery("opUserType", DataTypeUtils.toInteger(cmd.getQueryBackOperationUser())),
toQuery("createUser", cmd.getBackOperationUser()),
toQuery("type", cmd.getType()),
toQuery("userId", cmd.getUserId()),
toQuery("eventId", cmd.getTrackId()),
toQuery("eventType", getOrigin(cmd.getOrigin()))
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,108 @@
-- MiFaPay V2 region commodity tiers.
-- Region: LIKEI Middle East, sys_region_config._id = 2046145518364221441.
START TRANSACTION;
SET @application_id := 2048200000000000701;
SET @region_id := '2046145518364221441';
SET @type := 'GOLD';
SET @unexpected_existing_ids := (
SELECT COUNT(*)
FROM `sys_pay_application_commodity`
WHERE `id` IN (
2048200000000000801,
2048200000000000802,
2048200000000000803,
2048200000000000804,
2048200000000000805,
2048200000000000806
)
AND (`application_id` <> @application_id OR `type` <> @type)
);
SET @abort_sql := IF(
@unexpected_existing_ids = 0,
'DO 0',
'SIGNAL SQLSTATE ''45000'' SET MESSAGE_TEXT = ''MiFaPay commodity id collision'''
);
PREPARE stmt FROM @abort_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
INSERT INTO `sys_pay_application` (
`id`,
`app_name`,
`app_code`,
`android_link`,
`ios_link`,
`create_time`,
`update_time`,
`create_user`,
`update_user`
) VALUES (
@application_id,
'LIKEI',
'LIKEI',
'',
'',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
NULL,
NULL
) ON DUPLICATE KEY UPDATE
`app_name` = VALUES(`app_name`),
`app_code` = VALUES(`app_code`),
`android_link` = VALUES(`android_link`),
`ios_link` = VALUES(`ios_link`),
`update_time` = CURRENT_TIMESTAMP,
`update_user` = VALUES(`update_user`);
DELETE FROM `sys_pay_application_commodity`
WHERE `application_id` = @application_id
AND `region_id` = @region_id
AND `type` = @type
AND `amount_usd` IN (1.00, 5.00, 10.00, 25.00, 50.00, 100.00)
AND `id` NOT IN (
2048200000000000801,
2048200000000000802,
2048200000000000803,
2048200000000000804,
2048200000000000805,
2048200000000000806
);
INSERT INTO `sys_pay_application_commodity` (
`id`,
`application_id`,
`pay_country_id`,
`region_id`,
`type`,
`content`,
`award_content`,
`amount_usd`,
`is_shelf`,
`create_time`,
`update_time`,
`create_user`,
`update_user`
) VALUES
(2048200000000000801, @application_id, NULL, @region_id, @type, '80000', '0', 1.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL),
(2048200000000000802, @application_id, NULL, @region_id, @type, '400000', '0', 5.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL),
(2048200000000000803, @application_id, NULL, @region_id, @type, '800000', '0', 10.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL),
(2048200000000000804, @application_id, NULL, @region_id, @type, '2000000', '0', 25.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL),
(2048200000000000805, @application_id, NULL, @region_id, @type, '4000000', '0', 50.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL),
(2048200000000000806, @application_id, NULL, @region_id, @type, '8000000', '0', 100.00, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, NULL)
ON DUPLICATE KEY UPDATE
`application_id` = VALUES(`application_id`),
`pay_country_id` = VALUES(`pay_country_id`),
`region_id` = VALUES(`region_id`),
`type` = VALUES(`type`),
`content` = VALUES(`content`),
`award_content` = VALUES(`award_content`),
`amount_usd` = VALUES(`amount_usd`),
`is_shelf` = VALUES(`is_shelf`),
`update_time` = CURRENT_TIMESTAMP,
`update_user` = VALUES(`update_user`);
COMMIT;

View File

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS `sys_pay_factory_config`
(
`id` BIGINT NOT NULL COMMENT '主键',
`factory_code` VARCHAR(64) NOT NULL COMMENT '支付厂商code',
`env` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '运行环境PROD/TEST空表示通用',
`sys_origin` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '来源系统,空表示通用',
`mer_account` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '商户标识',
`mer_no` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '商户编号',
`gateway_base_url` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '网关基础地址',
`rsa_private_key` TEXT COMMENT '商户RSA私钥',
`platform_rsa_public_key` TEXT COMMENT '平台RSA公钥',
`notify_url` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '回调地址',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`create_user` BIGINT DEFAULT NULL COMMENT '创建人',
`update_user` BIGINT DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_factory_env_origin` (`factory_code`, `env`, `sys_origin`),
KEY `idx_factory_enabled` (`factory_code`, `enabled`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci
COMMENT ='支付厂商运行配置';