Merge remote-tracking branch 'origin/master'

This commit is contained in:
2025-12-14 08:40:31 +08:00
5 changed files with 364 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
package com.gxwebsoft.common.system.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import cn.hutool.core.util.IdUtil;
import com.gxwebsoft.common.core.Constants;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.Order;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.param.SubscriptionOrderParam;
import com.gxwebsoft.common.system.result.SubscriptionOrderCreateResult;
import com.gxwebsoft.common.system.result.SubscriptionOrderPayResult;
import com.gxwebsoft.common.system.result.SubscriptionPriceResult;
import com.gxwebsoft.common.system.service.SettingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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 javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
/**
* 订阅订单接口
*/
@Tag(name = "订阅订单")
@RestController
@RequestMapping("/api/system/subscription-order")
public class SubscriptionOrderController extends BaseController {
@Resource
private SettingService settingService;
@Resource
private WxNativePayController wxNativePayController;
@Operation(summary = "计算订阅订单价格")
@PostMapping("/calculate-price")
public ApiResult<SubscriptionPriceResult> calculatePrice(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult result = buildPriceResult(param, config);
return success(result);
}
@Operation(summary = "创建订阅订单")
@PostMapping("/create")
public ApiResult<SubscriptionOrderCreateResult> create(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult price = buildPriceResult(param, config);
SubscriptionOrderCreateResult result = new SubscriptionOrderCreateResult();
result.setOrderNo(IdUtil.getSnowflakeNextIdStr());
result.setPrice(price);
return success(result);
}
@Operation(summary = "订阅订单支付生成微信Native二维码")
@PostMapping("/pay")
public ApiResult<SubscriptionOrderPayResult> pay(@RequestBody SubscriptionOrderParam param) {
final User loginUser = getLoginUser();
if (loginUser == null) {
return fail("请先登录", null);
}
if (param.getPackageId() == null) {
return fail("套餐ID不能为空", null);
}
JSONObject config = loadSubscriptionConfig();
final SubscriptionPriceResult price = buildPriceResult(param, config);
if (price.getPayPrice() == null || price.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) {
return fail("支付金额必须大于0", null);
}
// 构造订单用于生成支付二维码
Order order = new Order();
order.setPayPrice(price.getPayPrice());
order.setTotalPrice(price.getTotalPrice());
order.setComments("订阅套餐-" + param.getPackageId());
order.setPayType(param.getPayType());
order.setUserId(loginUser.getUserId());
order.setTenantId(loginUser.getTenantId());
ApiResult<?> payResp = wxNativePayController.getCodeUrl(order);
if (payResp.getCode() == null || !payResp.getCode().equals(Constants.RESULT_OK_CODE)) {
return fail(payResp.getMessage(), null);
}
SubscriptionOrderPayResult result = new SubscriptionOrderPayResult();
result.setOrderNo(order.getOrderNo());
result.setCodeUrl(String.valueOf(payResp.getData()));
result.setPrice(price);
return success(result);
}
/**
* 从配置表读取订阅套餐配置尝试多种key以兼容历史数据
*/
private JSONObject loadSubscriptionConfig() {
final List<String> keys = Arrays.asList("subscription", "subscription-package", "subscriptionOrder");
for (String key : keys) {
try {
JSONObject cfg = settingService.getBySettingKey(key);
if (cfg != null) {
return cfg;
}
} catch (Exception ignored) {
}
}
return new JSONObject();
}
/**
* 计算价格结果
*/
private SubscriptionPriceResult buildPriceResult(SubscriptionOrderParam param, JSONObject config) {
BigDecimal originalPrice = resolveBasePrice(param.getPackageId(), config);
BigDecimal factor = BigDecimal.ONE;
StringBuilder remark = new StringBuilder();
if (config == null || config.isEmpty()) {
remark.append("未查询到订阅价格配置已按0元试算");
}
if (originalPrice.compareTo(BigDecimal.ZERO) == 0 && config != null && !config.isEmpty()) {
remark.append("未找到套餐价格已按0元试算");
}
if (isTrue(param.getIsRenewal())) {
BigDecimal renewalFactor = resolveFactor(config, "renewalDiscount");
factor = factor.multiply(renewalFactor);
remark.append("续费系数:").append(renewalFactor).append(";");
}
if (isTrue(param.getIsUpgrade())) {
BigDecimal upgradeFactor = resolveFactor(config, "upgradeDiscount");
factor = factor.multiply(upgradeFactor);
remark.append("升级系数:").append(upgradeFactor).append(";");
}
BigDecimal payTypeFactor = resolvePayTypeFactor(config, param.getPayType());
factor = factor.multiply(payTypeFactor);
if (param.getPayType() != null) {
remark.append("支付系数:").append(payTypeFactor).append(";");
}
BigDecimal payPrice = originalPrice.multiply(factor).setScale(2, RoundingMode.HALF_UP);
BigDecimal discount = originalPrice.subtract(payPrice);
if (discount.compareTo(BigDecimal.ZERO) < 0) {
discount = BigDecimal.ZERO;
}
SubscriptionPriceResult result = new SubscriptionPriceResult();
result.setPackageId(param.getPackageId());
result.setIsRenewal(param.getIsRenewal());
result.setIsUpgrade(param.getIsUpgrade());
result.setPayType(param.getPayType());
result.setOriginalPrice(originalPrice.setScale(2, RoundingMode.HALF_UP));
result.setTotalPrice(originalPrice.setScale(2, RoundingMode.HALF_UP));
result.setPayPrice(payPrice);
result.setDiscountAmount(discount.setScale(2, RoundingMode.HALF_UP));
result.setRemark(remark.toString());
return result;
}
private boolean isTrue(Integer value) {
return value != null && value.equals(1);
}
/**
* 解析套餐价格,支持 packages 数组、priceMap 或单价配置
*/
private BigDecimal resolveBasePrice(Integer packageId, JSONObject config) {
if (config != null) {
JSONArray packages = config.getJSONArray("packages");
if (packages != null) {
for (Object item : packages) {
if (item instanceof JSONObject) {
JSONObject pkg = (JSONObject) item;
Integer id = pkg.getInteger("id");
if (id == null) {
id = pkg.getInteger("packageId");
}
if (packageId.equals(id)) {
BigDecimal price = pkg.getBigDecimal("price");
if (price != null) {
return price;
}
}
}
}
}
JSONObject priceMap = config.getJSONObject("priceMap");
if (priceMap != null) {
BigDecimal price = priceMap.getBigDecimal(packageId.toString());
if (price != null) {
return price;
}
}
BigDecimal fallbackPrice = config.getBigDecimal("price");
if (fallbackPrice != null) {
return fallbackPrice;
}
}
return BigDecimal.ZERO;
}
/**
* 获取折扣系数,默认 1
*/
private BigDecimal resolveFactor(JSONObject config, String key) {
if (config != null) {
BigDecimal factor = config.getBigDecimal(key);
if (factor != null) {
return factor;
}
}
return BigDecimal.ONE;
}
/**
* 支付方式系数,可在配置中通过 payTypeDiscount 或 payTypeAdjustments 定义
*/
private BigDecimal resolvePayTypeFactor(JSONObject config, Integer payType) {
if (config != null && payType != null) {
JSONObject payTypeDiscount = config.getJSONObject("payTypeDiscount");
if (payTypeDiscount == null) {
payTypeDiscount = config.getJSONObject("payTypeAdjustments");
}
if (payTypeDiscount != null) {
BigDecimal factor = payTypeDiscount.getBigDecimal(payType.toString());
if (factor != null) {
return factor;
}
}
}
return BigDecimal.ONE;
}
}

View File

@@ -0,0 +1,24 @@
package com.gxwebsoft.common.system.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单价格试算入参
*/
@Data
@Schema(description = "订阅订单价格试算参数")
public class SubscriptionOrderParam {
@Schema(description = "是否续费1=续费 0=新购")
private Integer isRenewal;
@Schema(description = "是否升级1=升级 0=非升级")
private Integer isUpgrade;
@Schema(description = "套餐ID")
private Integer packageId;
@Schema(description = "支付方式")
private Integer payType;
}

View File

@@ -0,0 +1,18 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单创建结果
*/
@Data
@Schema(description = "订阅订单创建结果")
public class SubscriptionOrderCreateResult {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "价格试算结果")
private SubscriptionPriceResult price;
}

View File

@@ -0,0 +1,21 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 订阅订单支付结果
*/
@Data
@Schema(description = "订阅订单支付结果")
public class SubscriptionOrderPayResult {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "支付二维码链接")
private String codeUrl;
@Schema(description = "价格信息")
private SubscriptionPriceResult price;
}

View File

@@ -0,0 +1,41 @@
package com.gxwebsoft.common.system.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 订阅订单价格试算结果
*/
@Data
@Schema(description = "订阅订单价格试算结果")
public class SubscriptionPriceResult {
@Schema(description = "套餐ID")
private Integer packageId;
@Schema(description = "是否续费1=续费 0=新购")
private Integer isRenewal;
@Schema(description = "是否升级1=升级 0=非升级")
private Integer isUpgrade;
@Schema(description = "支付方式")
private Integer payType;
@Schema(description = "原价")
private BigDecimal originalPrice;
@Schema(description = "优惠金额")
private BigDecimal discountAmount;
@Schema(description = "应付金额")
private BigDecimal payPrice;
@Schema(description = "总价(同原价,用于兼容前端字段)")
private BigDecimal totalPrice;
@Schema(description = "价格说明")
private String remark;
}