同步平台业务模块能力
This commit is contained in:
@@ -1,15 +1,36 @@
|
||||
package com.xspaceagi.bill.app.service;
|
||||
|
||||
import com.xspaceagi.bill.sdk.dto.AppNativeInvokeRequest;
|
||||
import com.xspaceagi.bill.sdk.dto.CreateOrderRequest;
|
||||
import com.xspaceagi.bill.sdk.dto.H5WebInvokeRequest;
|
||||
import com.xspaceagi.bill.sdk.dto.MiniPayInvokeRequest;
|
||||
import com.xspaceagi.bill.sdk.dto.OrderDTO;
|
||||
import com.xspaceagi.bill.sdk.dto.OrderPageDTO;
|
||||
import com.xspaceagi.bill.sdk.dto.OrderQueryRequest;
|
||||
import com.xspaceagi.bill.sdk.dto.OrderSettlementStatusResponse;
|
||||
import com.xspaceagi.bill.sdk.dto.WeChatJsapiInvokeRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderAndTransactionCreateResponse;
|
||||
|
||||
public interface BillOrderAppService {
|
||||
|
||||
OrderDTO createOrder(CreateOrderRequest request);
|
||||
|
||||
/** 对已有 Bill 订单调起小程序支付,返回 wx.requestPayment / my.tradePay 参数。 */
|
||||
OrderAndTransactionCreateResponse invokeMiniPay(long userId, MiniPayInvokeRequest request, String clientIp);
|
||||
|
||||
/** 微信内 H5 JSAPI 调起支付,返回 WeixinJSBridge 所需 wxPayParams。 */
|
||||
OrderAndTransactionCreateResponse invokeWeChatJsapi(long userId, WeChatJsapiInvokeRequest request, String clientIp);
|
||||
|
||||
/** 系统浏览器 H5 调起支付,返回 invokeType + formHtml/redirectUrl。 */
|
||||
OrderAndTransactionCreateResponse invokeH5Web(long userId, H5WebInvokeRequest request, String clientIp);
|
||||
|
||||
/** App 原生 SDK 调起支付,返回 wxPayParams / redirectUrl。 */
|
||||
OrderAndTransactionCreateResponse invokeAppNative(long userId, AppNativeInvokeRequest request, String clientIp);
|
||||
|
||||
/** 扫码/H5 收银台调起支付。 */
|
||||
CashierSessionCreateResponse invokeScanCashier(long userId, Long orderId, String returnUrl);
|
||||
|
||||
boolean paymentCallback(Long tenantId, Long orderId, String payStatus);
|
||||
|
||||
OrderPageDTO queryOrders(OrderQueryRequest query);
|
||||
|
||||
@@ -192,7 +192,7 @@ public class BillCalculateTaskServiceImpl extends AbstractTaskExecuteService {
|
||||
|
||||
if (tenantConfig.getEnableSubscription() != null && tenantConfig.getEnableSubscription() == 1 && !traceContext.isError() && !traceContext.isDevTest()) {
|
||||
PricingConfigDTO pricingConfigDTO = iPricingRpcService.queryPricingInfo(request);
|
||||
if (pricingConfigDTO != null && pricingConfigDTO.getStatus() == 1 && (pricingConfigDTO.getPricingType() == PricingTypeEnum.ONE_TIME || pricingConfigDTO.getPricingType() == PricingTypeEnum.TIERED)) {
|
||||
if (pricingConfigDTO != null && pricingConfigDTO.getStatus() == 1) {
|
||||
log.info("定价配置匹配成功 pricingType={} price={} trialCount={}",
|
||||
pricingConfigDTO.getPricingType(), pricingConfigDTO.getPrice(), pricingConfigDTO.getTrialCount());
|
||||
boolean isToolCall = false;
|
||||
@@ -202,6 +202,20 @@ public class BillCalculateTaskServiceImpl extends AbstractTaskExecuteService {
|
||||
feeAmount = pricingConfigDTO.getPrice();
|
||||
isToolCall = true;
|
||||
}
|
||||
if (pricingConfigDTO.getPricingType() == PricingTypeEnum.SECOND && traceContext.getDurationUsage() != null && traceContext.getDurationUsage().duration > 0
|
||||
&& (pricingConfigDTO.getTargetType() == TargetTypeEnum.WORKFLOW || pricingConfigDTO.getTargetType() == TargetTypeEnum.PLUGIN)) {
|
||||
//积分扣减计算
|
||||
feeAmount = pricingConfigDTO.getPrice().multiply(BigDecimal.valueOf(traceContext.getDurationUsage().duration));
|
||||
creditAmount = feeAmount.multiply(BigDecimal.valueOf(tenantConfig.getCreditExchangeRate()));
|
||||
isToolCall = true;
|
||||
}
|
||||
if (pricingConfigDTO.getPricingType() == PricingTypeEnum.MILLION_TOKEN && tokenUsage != null && tokenUsage.outputTokens > 0
|
||||
&& (pricingConfigDTO.getTargetType() == TargetTypeEnum.WORKFLOW || pricingConfigDTO.getTargetType() == TargetTypeEnum.PLUGIN)) {
|
||||
//积分扣减计算
|
||||
feeAmount = pricingConfigDTO.getPrice().multiply(BigDecimal.valueOf(tokenUsage.outputTokens)).divide(BigDecimal.valueOf(1000000), RoundingMode.HALF_UP);
|
||||
creditAmount = feeAmount.multiply(BigDecimal.valueOf(tenantConfig.getCreditExchangeRate()));
|
||||
isToolCall = true;
|
||||
}
|
||||
if (pricingConfigDTO.getPricingType() == PricingTypeEnum.TIERED && pricingConfigDTO.getTargetType() == TargetTypeEnum.MODEL) {
|
||||
//阶梯计费
|
||||
if (tokenUsage != null && CollectionUtils.isNotEmpty(pricingConfigDTO.getModelPriceTiers())) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.xspaceagi.agent.core.sdk.dto.ReqResult;
|
||||
import com.xspaceagi.agent.core.sdk.dto.SkillInfoDto;
|
||||
import com.xspaceagi.bill.app.service.BillOrderAppService;
|
||||
import com.xspaceagi.bill.app.service.BillRevenueAppService;
|
||||
import com.xspaceagi.bill.app.service.support.MiniPayChannelCredentialResolver;
|
||||
import com.xspaceagi.bill.app.service.support.WeChatJsapiChannelCredentialResolver;
|
||||
import com.xspaceagi.bill.infra.dao.entity.BillOrder;
|
||||
import com.xspaceagi.bill.infra.dao.entity.BillOrderItem;
|
||||
import com.xspaceagi.bill.infra.dao.mapper.BillOrderMapper;
|
||||
@@ -18,10 +20,22 @@ import com.xspaceagi.bill.spec.enums.*;
|
||||
import com.xspaceagi.credit.sdk.dto.CreditAddRequest;
|
||||
import com.xspaceagi.credit.sdk.rpc.ICreditRpcService;
|
||||
import com.xspaceagi.credit.spec.enums.CreditTypeEnum;
|
||||
import com.xspaceagi.pay.sdk.dto.PaymentOrderCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.AppOrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.AppTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayOrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderAndTransactionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.dto.ScanOrderCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.H5OrderRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.H5TransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import com.xspaceagi.pay.sdk.enums.PayMode;
|
||||
import com.xspaceagi.pay.sdk.enums.PayClientScene;
|
||||
import com.xspaceagi.pay.sdk.service.IPaymentRpcService;
|
||||
import com.xspaceagi.pay.sdk.support.PayOrderExtKeys;
|
||||
import com.xspaceagi.subscription.sdk.dto.CreateSubscriptionRequest;
|
||||
import com.xspaceagi.subscription.sdk.dto.PlanDTO;
|
||||
import com.xspaceagi.subscription.sdk.rpc.ISubscriptionRpcService;
|
||||
@@ -40,6 +54,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -79,6 +96,12 @@ public class BillOrderAppServiceImpl implements BillOrderAppService {
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Resource
|
||||
private MiniPayChannelCredentialResolver miniPayChannelCredentialResolver;
|
||||
|
||||
@Resource
|
||||
private WeChatJsapiChannelCredentialResolver weChatJsapiChannelCredentialResolver;
|
||||
|
||||
@Override
|
||||
public OrderDTO createOrder(CreateOrderRequest request) {
|
||||
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||
@@ -122,17 +145,39 @@ public class BillOrderAppServiceImpl implements BillOrderAppService {
|
||||
order.setAmount(totalAmount);
|
||||
|
||||
//创建支付订单
|
||||
ScanOrderCreateRequest paymentScanCreateOrderRequest = new ScanOrderCreateRequest();
|
||||
paymentScanCreateOrderRequest.setBizOrderNo(order.getId().toString());
|
||||
paymentScanCreateOrderRequest.setOrderAmount(totalAmount.multiply(new BigDecimal(100)).longValue());
|
||||
paymentScanCreateOrderRequest.setSubject(request.getDescription());
|
||||
PaymentOrderCreateResponse orderForScan = iPaymentRpcService.createOrderForScan(paymentScanCreateOrderRequest);
|
||||
PayMode payMode = request.getPayMode() != null ? request.getPayMode() : PayMode.scan;
|
||||
OrderCreateResponse payOrderResponse;
|
||||
if (payMode == PayMode.minipay) {
|
||||
MiniPayOrderRpcCreateRequest miniPayOrderRequest = new MiniPayOrderRpcCreateRequest();
|
||||
miniPayOrderRequest.setBizOrderNo(order.getId().toString());
|
||||
miniPayOrderRequest.setOrderAmount(totalAmount.multiply(new BigDecimal(100)).longValue());
|
||||
miniPayOrderRequest.setSubject(request.getDescription());
|
||||
payOrderResponse = iPaymentRpcService.createOrderForMiniPay(miniPayOrderRequest);
|
||||
} else if (payMode == PayMode.h5) {
|
||||
H5OrderRpcCreateRequest h5OrderRequest = new H5OrderRpcCreateRequest();
|
||||
h5OrderRequest.setBizOrderNo(order.getId().toString());
|
||||
h5OrderRequest.setOrderAmount(totalAmount.multiply(new BigDecimal(100)).longValue());
|
||||
h5OrderRequest.setSubject(request.getDescription());
|
||||
payOrderResponse = iPaymentRpcService.createOrderForH5(h5OrderRequest);
|
||||
} else if (payMode == PayMode.app) {
|
||||
AppOrderRpcCreateRequest appOrderRequest = new AppOrderRpcCreateRequest();
|
||||
appOrderRequest.setBizOrderNo(order.getId().toString());
|
||||
appOrderRequest.setOrderAmount(totalAmount.multiply(new BigDecimal(100)).longValue());
|
||||
appOrderRequest.setSubject(request.getDescription());
|
||||
payOrderResponse = iPaymentRpcService.createOrderForApp(appOrderRequest);
|
||||
} else {
|
||||
ScanOrderCreateRequest paymentScanCreateOrderRequest = new ScanOrderCreateRequest();
|
||||
paymentScanCreateOrderRequest.setBizOrderNo(order.getId().toString());
|
||||
paymentScanCreateOrderRequest.setOrderAmount(totalAmount.multiply(new BigDecimal(100)).longValue());
|
||||
paymentScanCreateOrderRequest.setSubject(request.getDescription());
|
||||
payOrderResponse = iPaymentRpcService.createOrderForScan(paymentScanCreateOrderRequest);
|
||||
}
|
||||
Map<String, Object> extra = request.getExtra();
|
||||
if (extra == null) {
|
||||
extra = new HashMap<>();
|
||||
}
|
||||
extra.put("gatewayPaymentOrderNo", orderForScan.getGatewayPaymentOrderNo());
|
||||
extra.put("payMode", PayMode.scan.name());
|
||||
extra.put("gatewayPaymentOrderNo", payOrderResponse.getGatewayPaymentOrderNo());
|
||||
extra.put("payMode", payMode.name());
|
||||
order.setExtra(JSON.toJSONString(extra));
|
||||
billOrderService.updateById(order);
|
||||
} catch (Exception e) {
|
||||
@@ -146,6 +191,221 @@ public class BillOrderAppServiceImpl implements BillOrderAppService {
|
||||
return convertToDTO(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse invokeMiniPay(long userId, MiniPayInvokeRequest request, String clientIp) {
|
||||
if (request.getOrderId() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), I18nUtil.systemMessage("Backend.Bill.Order.Validate.OrderIdRequired"));
|
||||
}
|
||||
if (request.getPayChannel() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "payChannel is required");
|
||||
}
|
||||
BillOrder order = requirePendingBillOrder(userId, request.getOrderId());
|
||||
ensurePayOrderForMode(order, PayMode.minipay);
|
||||
|
||||
MiniPayTransactionRpcCreateRequest txRequest = new MiniPayTransactionRpcCreateRequest();
|
||||
txRequest.setBizOrderNo(String.valueOf(order.getId()));
|
||||
txRequest.setPayChannel(request.getPayChannel());
|
||||
txRequest.setClientIp(clientIp);
|
||||
txRequest.setPayClientScene(PayClientScene.MINI_PROGRAM);
|
||||
miniPayChannelCredentialResolver.applyChannelParams(order.getTenantId(), request, txRequest);
|
||||
OrderAndTransactionCreateResponse response = iPaymentRpcService.createMiniPayTransaction(txRequest);
|
||||
recordPayClientSelectionOnBillOrder(order, PayClientScene.MINI_PROGRAM, response.getPayChannel());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse invokeWeChatJsapi(
|
||||
long userId, WeChatJsapiInvokeRequest request, String clientIp) {
|
||||
if (request.getOrderId() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), I18nUtil.systemMessage("Backend.Bill.Order.Validate.OrderIdRequired"));
|
||||
}
|
||||
BillOrder order = requirePendingBillOrder(userId, request.getOrderId());
|
||||
ensurePayOrderForMode(order, PayMode.minipay);
|
||||
|
||||
MiniPayTransactionRpcCreateRequest txRequest = new MiniPayTransactionRpcCreateRequest();
|
||||
txRequest.setBizOrderNo(String.valueOf(order.getId()));
|
||||
txRequest.setClientIp(clientIp);
|
||||
txRequest.setPayClientScene(PayClientScene.WECHAT_JSAPI);
|
||||
weChatJsapiChannelCredentialResolver.applyChannelParams(order.getTenantId(), request, txRequest);
|
||||
OrderAndTransactionCreateResponse response = iPaymentRpcService.createMiniPayTransaction(txRequest);
|
||||
recordPayClientSelectionOnBillOrder(order, PayClientScene.WECHAT_JSAPI, response.getPayChannel());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse invokeH5Web(long userId, H5WebInvokeRequest request, String clientIp) {
|
||||
if (request.getOrderId() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), I18nUtil.systemMessage("Backend.Bill.Order.Validate.OrderIdRequired"));
|
||||
}
|
||||
if (request.getPayChannel() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "payChannel is required");
|
||||
}
|
||||
if (request.getFrontNotifyUrl() == null || request.getFrontNotifyUrl().isBlank()) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "frontNotifyUrl is required");
|
||||
}
|
||||
BillOrder order = requirePendingBillOrder(userId, request.getOrderId());
|
||||
ensurePayOrderForMode(order, PayMode.h5);
|
||||
H5TransactionRpcCreateRequest txRequest = new H5TransactionRpcCreateRequest();
|
||||
txRequest.setBizOrderNo(String.valueOf(order.getId()));
|
||||
txRequest.setPayChannel(request.getPayChannel());
|
||||
txRequest.setClientIp(clientIp);
|
||||
String frontNotifyUrl = validateFrontNotifyUrl(request.getFrontNotifyUrl().trim());
|
||||
txRequest.setFrontNotifyUrl(buildChannelFrontNotifyUrl(frontNotifyUrl, request.getOrderId()));
|
||||
OrderAndTransactionCreateResponse response = iPaymentRpcService.createH5Transaction(txRequest);
|
||||
recordPayClientSelectionOnBillOrder(order, PayClientScene.H5_WEB, response.getPayChannel());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderAndTransactionCreateResponse invokeAppNative(
|
||||
long userId, AppNativeInvokeRequest request, String clientIp) {
|
||||
if (request.getOrderId() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), I18nUtil.systemMessage("Backend.Bill.Order.Validate.OrderIdRequired"));
|
||||
}
|
||||
if (request.getPayChannel() == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "payChannel is required");
|
||||
}
|
||||
BillOrder order = requirePendingBillOrder(userId, request.getOrderId());
|
||||
ensurePayOrderForMode(order, PayMode.app);
|
||||
|
||||
AppTransactionRpcCreateRequest txRequest = new AppTransactionRpcCreateRequest();
|
||||
txRequest.setBizOrderNo(String.valueOf(order.getId()));
|
||||
txRequest.setPayChannel(request.getPayChannel());
|
||||
txRequest.setClientIp(clientIp);
|
||||
txRequest.setPayClientScene(PayClientScene.NATIVE_APP);
|
||||
OrderAndTransactionCreateResponse response = iPaymentRpcService.createAppTransaction(txRequest);
|
||||
recordPayClientSelectionOnBillOrder(order, PayClientScene.NATIVE_APP, response.getPayChannel());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CashierSessionCreateResponse invokeScanCashier(long userId, Long orderId, String returnUrl) {
|
||||
if (orderId == null) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), I18nUtil.systemMessage("Backend.Bill.Order.Validate.OrderIdRequired"));
|
||||
}
|
||||
BillOrder order = requirePendingBillOrder(userId, orderId);
|
||||
OrderCreateResponse payOrderResponse = ensurePayOrderForMode(order, PayMode.scan);
|
||||
CashierSessionCreateRequest paymentCreateCashierSessionRequest = new CashierSessionCreateRequest();
|
||||
paymentCreateCashierSessionRequest.setGatewayPaymentOrderNo(payOrderResponse.getGatewayPaymentOrderNo());
|
||||
paymentCreateCashierSessionRequest.setBizRedirectUrl(returnUrl);
|
||||
return iPaymentRpcService.createCashierSession(paymentCreateCashierSessionRequest);
|
||||
}
|
||||
|
||||
private static String buildChannelFrontNotifyUrl(String frontNotifyUrl, Long orderId) {
|
||||
URI frontendUri = URI.create(frontNotifyUrl);
|
||||
String origin = frontendUri.getScheme() + "://" + frontendUri.getAuthority();
|
||||
String encodedReturnUrl = URLEncoder.encode(frontNotifyUrl, StandardCharsets.UTF_8);
|
||||
return origin + "/api/bill/order/pay/front-notify?orderId=" + orderId + "&returnUrl=" + encodedReturnUrl;
|
||||
}
|
||||
|
||||
private static String validateFrontNotifyUrl(String url) {
|
||||
try {
|
||||
URI uri = URI.create(url);
|
||||
String scheme = uri.getScheme();
|
||||
if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "frontNotifyUrl must start with http or https");
|
||||
}
|
||||
String host = uri.getHost();
|
||||
if (host == null || host.isBlank()) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "Invalid frontNotifyUrl");
|
||||
}
|
||||
return url;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "Invalid frontNotifyUrl");
|
||||
}
|
||||
}
|
||||
|
||||
private void recordPayClientSelectionOnBillOrder(BillOrder order, PayClientScene scene, PayChannel payChannel) {
|
||||
Map<String, Object> extra = parseExtra(order.getExtra());
|
||||
extra.put(PayOrderExtKeys.PAY_CLIENT_SCENE, scene.name());
|
||||
if (payChannel != null) {
|
||||
extra.put("payChannel", payChannel.name());
|
||||
}
|
||||
order.setExtra(JSON.toJSONString(extra));
|
||||
billOrderService.updateById(order);
|
||||
}
|
||||
|
||||
private BillOrder requirePendingBillOrder(long userId, Long orderId) {
|
||||
BillOrder order = billOrderService.getById(orderId);
|
||||
if (order == null) {
|
||||
throw new BizException("ORDER_NOT_FOUND", I18nUtil.systemMessage("Backend.Bill.Order.Error.OrderNotFound"));
|
||||
}
|
||||
if (!Long.valueOf(userId).equals(order.getUserId())) {
|
||||
throw new BizException("USER_NOT_MATCH", I18nUtil.systemMessage("Backend.Bill.Order.Error.UserNotMatch"));
|
||||
}
|
||||
Long ctxTenantId = RequestContext.get() != null ? RequestContext.get().getTenantId() : null;
|
||||
if (ctxTenantId == null || !ctxTenantId.equals(order.getTenantId())) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "订单租户与当前上下文不一致");
|
||||
}
|
||||
if (!PayStatusEnum.PENDING.getCode().equals(order.getPayStatus())) {
|
||||
throw new BizException("ORDER_NOT_PENDING", "The order status is incorrect.");
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private OrderCreateResponse ensurePayOrderForMode(BillOrder order, PayMode targetPayMode) {
|
||||
OrderCreateResponse response;
|
||||
if (targetPayMode == PayMode.minipay) {
|
||||
MiniPayOrderRpcCreateRequest req = new MiniPayOrderRpcCreateRequest();
|
||||
req.setBizOrderNo(String.valueOf(order.getId()));
|
||||
req.setOrderAmount(orderAmountFen(order));
|
||||
req.setSubject(resolvePaySubject(order));
|
||||
response = iPaymentRpcService.createOrderForMiniPay(req);
|
||||
} else if (targetPayMode == PayMode.h5) {
|
||||
H5OrderRpcCreateRequest req = new H5OrderRpcCreateRequest();
|
||||
req.setBizOrderNo(String.valueOf(order.getId()));
|
||||
req.setOrderAmount(orderAmountFen(order));
|
||||
req.setSubject(resolvePaySubject(order));
|
||||
response = iPaymentRpcService.createOrderForH5(req);
|
||||
} else if (targetPayMode == PayMode.app) {
|
||||
AppOrderRpcCreateRequest req = new AppOrderRpcCreateRequest();
|
||||
req.setBizOrderNo(String.valueOf(order.getId()));
|
||||
req.setOrderAmount(orderAmountFen(order));
|
||||
req.setSubject(resolvePaySubject(order));
|
||||
response = iPaymentRpcService.createOrderForApp(req);
|
||||
} else {
|
||||
ScanOrderCreateRequest req = new ScanOrderCreateRequest();
|
||||
req.setBizOrderNo(String.valueOf(order.getId()));
|
||||
req.setOrderAmount(orderAmountFen(order));
|
||||
req.setSubject(resolvePaySubject(order));
|
||||
response = iPaymentRpcService.createOrderForScan(req);
|
||||
}
|
||||
updateBillPayPointer(order, targetPayMode, response.getGatewayPaymentOrderNo());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void updateBillPayPointer(BillOrder order, PayMode payMode, String gatewayPaymentOrderNo) {
|
||||
Map<String, Object> extra = parseExtra(order.getExtra());
|
||||
extra.put("payMode", payMode.name());
|
||||
extra.put("gatewayPaymentOrderNo", gatewayPaymentOrderNo);
|
||||
order.setExtra(JSON.toJSONString(extra));
|
||||
billOrderService.updateById(order);
|
||||
}
|
||||
|
||||
private static long orderAmountFen(BillOrder order) {
|
||||
BigDecimal amount = order.getAmount() == null ? BigDecimal.ZERO : order.getAmount();
|
||||
return amount.multiply(new BigDecimal(100)).longValue();
|
||||
}
|
||||
|
||||
private static String resolvePaySubject(BillOrder order) {
|
||||
if (order.getDescription() != null && !order.getDescription().isBlank()) {
|
||||
return order.getDescription();
|
||||
}
|
||||
return "BillOrder-" + order.getId();
|
||||
}
|
||||
|
||||
private static Map<String, Object> parseExtra(String extraJson) {
|
||||
if (extraJson == null || extraJson.isBlank()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
try {
|
||||
Map<String, Object> parsed = JSON.parseObject(extraJson);
|
||||
return parsed != null ? parsed : new HashMap<>();
|
||||
} catch (Exception e) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean paymentCallback(Long tenantId, Long orderId, String payStatus) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.xspaceagi.bill.app.service.support;
|
||||
|
||||
import com.xspaceagi.bill.sdk.dto.MiniPayInvokeRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
|
||||
import com.xspaceagi.system.infra.rpc.WeChatMpService;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/** 将小程序支付渠道凭证解析为网关下单参数 */
|
||||
@Component
|
||||
public class MiniPayChannelCredentialResolver {
|
||||
|
||||
@Resource
|
||||
private TenantConfigApplicationService tenantConfigApplicationService;
|
||||
|
||||
@Resource
|
||||
private WeChatMpService weChatMpService;
|
||||
|
||||
public void applyChannelParams(long tenantId, MiniPayInvokeRequest request, MiniPayTransactionRpcCreateRequest tx) {
|
||||
PayChannel channel = request.getPayChannel();
|
||||
if (channel == PayChannel.WxPay) {
|
||||
applyWxPay(tenantId, request, tx);
|
||||
return;
|
||||
}
|
||||
if (channel == PayChannel.AliPay) {
|
||||
applyAliPay(request, tx);
|
||||
return;
|
||||
}
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "小程序支付仅支持 WxPay 与 AliPay");
|
||||
}
|
||||
|
||||
private void applyWxPay(long tenantId, MiniPayInvokeRequest request, MiniPayTransactionRpcCreateRequest tx) {
|
||||
if (!StringUtils.hasText(request.getWxLoginCode())) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "wxLoginCode 不能为空");
|
||||
}
|
||||
TenantConfigDto config = tenantConfigApplicationService.getTenantConfig(tenantId);
|
||||
if (config == null || !StringUtils.hasText(config.getMpAppId())) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemWeChatMpNotConfigured);
|
||||
}
|
||||
String openId = weChatMpService.getOpenIdByLoginCode(request.getWxLoginCode().trim());
|
||||
tx.setSubAppid(config.getMpAppId().trim());
|
||||
tx.setOpenId(openId);
|
||||
}
|
||||
|
||||
private void applyAliPay(MiniPayInvokeRequest request, MiniPayTransactionRpcCreateRequest tx) {
|
||||
if (!StringUtils.hasText(request.getAlipayAuthCode())) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "alipayAuthCode 不能为空");
|
||||
}
|
||||
// 支付宝 oauth 换 buyer_id 依赖租户密钥配置,待接入 AliPayMpService 后实现
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemAlipayMiniPayNotConfigured);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.xspaceagi.bill.app.service.support;
|
||||
|
||||
import com.xspaceagi.bill.sdk.dto.WeChatJsapiInvokeRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.MiniPayTransactionRpcCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
|
||||
import com.xspaceagi.system.infra.rpc.WeChatOaService;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/** 将公众号 OAuth 凭证解析为 minipay(JSAPI)网关下单参数。 */
|
||||
@Component
|
||||
public class WeChatJsapiChannelCredentialResolver {
|
||||
|
||||
@Resource
|
||||
private TenantConfigApplicationService tenantConfigApplicationService;
|
||||
|
||||
@Resource
|
||||
private WeChatOaService weChatOaService;
|
||||
|
||||
public void applyChannelParams(long tenantId, WeChatJsapiInvokeRequest request, MiniPayTransactionRpcCreateRequest tx) {
|
||||
if (!StringUtils.hasText(request.getWxOAuthCode())) {
|
||||
throw new BizException(ErrorCodeEnum.INVALID_PARAM.getCode(), "wxOAuthCode 不能为空");
|
||||
}
|
||||
TenantConfigDto config = tenantConfigApplicationService.getTenantConfig(tenantId);
|
||||
if (config == null || !StringUtils.hasText(config.getOaAppId())) {
|
||||
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemWeChatOaNotConfigured);
|
||||
}
|
||||
String openId = weChatOaService.getOpenIdByOAuthCode(request.getWxOAuthCode().trim(), tenantId);
|
||||
tx.setPayChannel(PayChannel.WxPay);
|
||||
tx.setSubAppid(config.getOaAppId().trim());
|
||||
tx.setOpenId(openId);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,10 @@
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-bill-spec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-pay-sdk</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.bill.sdk.dto;
|
||||
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "App 原生 SDK 调起支付请求")
|
||||
public class AppNativeInvokeRequest implements Serializable {
|
||||
|
||||
@Schema(description = "Bill 订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "支付渠道:WxPay / AliPay", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private PayChannel payChannel;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.xspaceagi.bill.sdk.dto;
|
||||
|
||||
import com.xspaceagi.bill.spec.enums.BizTypeEnum;
|
||||
import com.xspaceagi.pay.sdk.enums.PayMode;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -30,6 +31,9 @@ public class CreateOrderRequest implements Serializable {
|
||||
@Schema(description = "扩展字段")
|
||||
private Map<String, Object> extra;
|
||||
|
||||
@Schema(description = "支付方式:scan(默认,扫码/H5 收银台)/ minipay(小程序/微信JSAPI)/ h5(H5支付)/ app(App 原生 SDK)")
|
||||
private PayMode payMode;
|
||||
|
||||
@Data
|
||||
@Schema(description = "订单项")
|
||||
public static class CreateOrderItem implements Serializable {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.xspaceagi.bill.sdk.dto;
|
||||
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统浏览器 H5 调起支付请求(h5)")
|
||||
public class H5WebInvokeRequest implements Serializable {
|
||||
|
||||
@Schema(description = "Bill 订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "支付渠道:WxPay / AliPay", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private PayChannel payChannel;
|
||||
|
||||
@Schema(description = "前端结算页地址(后端将封装为渠道可回调的中转地址)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String frontNotifyUrl;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.xspaceagi.bill.sdk.dto;
|
||||
|
||||
import com.xspaceagi.pay.sdk.enums.PayChannel;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "小程序调起支付请求(微信内 H5 JSAPI 请使用 WeChatJsapiInvokeRequest)")
|
||||
public class MiniPayInvokeRequest implements Serializable {
|
||||
|
||||
@Schema(description = "Bill 订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "支付渠道:WxPay / AliPay", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private PayChannel payChannel;
|
||||
|
||||
@Schema(description = "WxPay:wx.login() 返回的临时 code(5 分钟有效,一次性)")
|
||||
private String wxLoginCode;
|
||||
|
||||
@Schema(description = "AliPay:my.getAuthCode() 返回的授权码")
|
||||
private String alipayAuthCode;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.xspaceagi.bill.sdk.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@Schema(description = "微信内 H5 JSAPI 调起支付请求")
|
||||
public class WeChatJsapiInvokeRequest implements Serializable {
|
||||
|
||||
@Schema(description = "Bill 订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "公众号 OAuth 回调 code(snsapi_base,一次性)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String wxOAuthCode;
|
||||
}
|
||||
@@ -11,7 +11,8 @@ public enum RevenueTargetTypeEnum {
|
||||
MODEL("Model", "模型"),
|
||||
PLUGIN("Plugin", "插件"),
|
||||
MCP("Mcp", "MCP"),
|
||||
WORKFLOW("Workflow", "工作流");
|
||||
WORKFLOW("Workflow", "工作流"),
|
||||
PROJECT("Project", "项目");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
@@ -8,7 +8,8 @@ import lombok.Getter;
|
||||
public enum RevenueTypeEnum {
|
||||
PLAN("Plan", "计划购买"),
|
||||
MODEL_CALL("ModelCall", "模型调用"),
|
||||
TOOL_CALL("ToolCall", "工具调用");
|
||||
TOOL_CALL("ToolCall", "工具调用"),
|
||||
PROJECT_PAY("ProjectPay", "项目支付");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
@@ -22,5 +22,9 @@
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-bill-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-pay-spec</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -5,20 +5,24 @@ import com.xspaceagi.bill.app.service.BillRevenueAppService;
|
||||
import com.xspaceagi.bill.app.service.BillWithdrawAppService;
|
||||
import com.xspaceagi.bill.app.service.ResourceStatAppService;
|
||||
import com.xspaceagi.bill.sdk.dto.*;
|
||||
import com.xspaceagi.bill.spec.enums.PayStatusEnum;
|
||||
import com.xspaceagi.bill.spec.enums.ResourceStatTypeEnum;
|
||||
import com.xspaceagi.bill.spec.enums.RevenueTargetTypeEnum;
|
||||
import com.xspaceagi.bill.spec.enums.RevenueTypeEnum;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateRequest;
|
||||
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.service.IPaymentRpcService;
|
||||
import com.xspaceagi.pay.sdk.dto.OrderAndTransactionCreateResponse;
|
||||
import com.xspaceagi.pay.sdk.support.PayAppWebViewDetector;
|
||||
import com.xspaceagi.pay.spec.support.PayAppNativeInAppGuard;
|
||||
import com.xspaceagi.pay.spec.support.PayH5InAppGuard;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.utils.IPUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -42,9 +46,6 @@ public class BillController {
|
||||
@Resource
|
||||
private BillWithdrawAppService billWithdrawAppService;
|
||||
|
||||
@Resource
|
||||
private IPaymentRpcService iPaymentRpcService;
|
||||
|
||||
@Resource
|
||||
private ResourceStatAppService resourceStatAppService;
|
||||
|
||||
@@ -143,26 +144,88 @@ public class BillController {
|
||||
@RequestParam Long orderId, @RequestParam String returnUrl) {
|
||||
Assert.notNull(orderId, "orderId can not be null");
|
||||
Assert.hasText(returnUrl, "returnUrl can not be blank");
|
||||
OrderDTO orderDTO = billOrderAppService.queryOrder(orderId);
|
||||
if (orderDTO == null) {
|
||||
throw new IllegalArgumentException("order not found");
|
||||
}
|
||||
if (!RequestContext.get().getUserId().equals(orderDTO.getUserId())) {
|
||||
throw new BizException("USER_NOT_MATCH", "The order data does not match the current user.");
|
||||
}
|
||||
if (orderDTO.getPayStatus() != PayStatusEnum.PENDING) {
|
||||
throw new BizException("ORDER_NOT_PENDING", "The order status is incorrect.");
|
||||
}
|
||||
if (orderDTO.getExtra().get("gatewayPaymentOrderNo") == null) {
|
||||
throw new BizException("The order data is incomplete.");
|
||||
}
|
||||
CashierSessionCreateRequest paymentCreateCashierSessionRequest = new CashierSessionCreateRequest();
|
||||
paymentCreateCashierSessionRequest.setGatewayPaymentOrderNo(String.valueOf(orderDTO.getExtra().get("gatewayPaymentOrderNo")));
|
||||
paymentCreateCashierSessionRequest.setBizRedirectUrl(validateSettlementPageUrl(returnUrl.trim()));
|
||||
CashierSessionCreateResponse cashierSession = iPaymentRpcService.createCashierSession(paymentCreateCashierSessionRequest);
|
||||
CashierSessionCreateResponse cashierSession = billOrderAppService.invokeScanCashier(
|
||||
RequestContext.get().getUserId(),
|
||||
orderId,
|
||||
validateSettlementPageUrl(returnUrl.trim()));
|
||||
return ReqResult.success(cashierSession);
|
||||
}
|
||||
|
||||
@PostMapping("/order/pay/minipay")
|
||||
@Operation(
|
||||
summary = "小程序调起支付",
|
||||
description = "调起微信/支付宝小程序支付,返回 wx.requestPayment / my.tradePay 所需参数")
|
||||
public ReqResult<OrderAndTransactionCreateResponse> invokeMiniPay(
|
||||
@RequestBody MiniPayInvokeRequest request, HttpServletRequest httpServletRequest) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
Assert.notNull(request.getOrderId(), "orderId can not be null");
|
||||
String clientIp = IPUtil.getIpAddr(httpServletRequest);
|
||||
return ReqResult.success(
|
||||
billOrderAppService.invokeMiniPay(RequestContext.get().getUserId(), request, clientIp));
|
||||
}
|
||||
|
||||
@PostMapping("/order/pay/wechat-jsapi")
|
||||
@Operation(
|
||||
summary = "微信内 H5 JSAPI 调起支付",
|
||||
description = "仅微信内置浏览器:先 GET /api/system/wechat/oa/oauth-url 完成 snsapi_base 授权,"
|
||||
+ "再传 wxOAuthCode;返回 wxPayParams 供 WeixinJSBridge.getBrandWCPayRequest;"
|
||||
+ "付后须轮询 GET /api/bill/order/settlement-status")
|
||||
public ReqResult<OrderAndTransactionCreateResponse> invokeWeChatJsapi(
|
||||
@RequestBody WeChatJsapiInvokeRequest request, HttpServletRequest httpServletRequest) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
Assert.notNull(request.getOrderId(), "orderId can not be null");
|
||||
String clientIp = IPUtil.getIpAddr(httpServletRequest);
|
||||
return ReqResult.success(
|
||||
billOrderAppService.invokeWeChatJsapi(RequestContext.get().getUserId(), request, clientIp));
|
||||
}
|
||||
|
||||
@PostMapping("/order/pay/h5-web")
|
||||
@Operation(
|
||||
summary = "系统浏览器 H5 调起支付",
|
||||
description = "调起 h5 支付,返回 invokeType + formHtml/redirectUrl;支付后轮询 settlement-status。"
|
||||
+ "App WebView 内禁止调用")
|
||||
public ReqResult<OrderAndTransactionCreateResponse> invokeH5Web(
|
||||
@RequestBody H5WebInvokeRequest request, HttpServletRequest httpServletRequest) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
Assert.notNull(request.getOrderId(), "orderId can not be null");
|
||||
PayH5InAppGuard.assertH5PayAllowed(
|
||||
httpServletRequest.getHeader(PayAppWebViewDetector.HEADER_CLIENT_TYPE),
|
||||
httpServletRequest.getHeader("User-Agent"));
|
||||
String clientIp = IPUtil.getIpAddr(httpServletRequest);
|
||||
return ReqResult.success(
|
||||
billOrderAppService.invokeH5Web(RequestContext.get().getUserId(), request, clientIp));
|
||||
}
|
||||
|
||||
@PostMapping("/order/pay/app-native")
|
||||
@Operation(
|
||||
summary = "App 原生 SDK 调起支付",
|
||||
description = "WebView/App 壳内调起支付。支付后轮询 settlement-status。"
|
||||
+ "仅限 App WebView 内调用")
|
||||
public ReqResult<OrderAndTransactionCreateResponse> invokeAppNative(
|
||||
@RequestBody AppNativeInvokeRequest request, HttpServletRequest httpServletRequest) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
Assert.notNull(request.getOrderId(), "orderId can not be null");
|
||||
PayAppNativeInAppGuard.assertAppNativePayRequired(
|
||||
httpServletRequest.getHeader(PayAppWebViewDetector.HEADER_CLIENT_TYPE),
|
||||
httpServletRequest.getHeader("User-Agent"));
|
||||
String clientIp = IPUtil.getIpAddr(httpServletRequest);
|
||||
return ReqResult.success(
|
||||
billOrderAppService.invokeAppNative(RequestContext.get().getUserId(), request, clientIp));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/order/pay/front-notify", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
@Operation(
|
||||
summary = "H5 渠道前台回跳中转",
|
||||
description = "接收渠道前台回调(GET/POST),统一 302 到前端结算页")
|
||||
public void handleH5FrontNotify(
|
||||
@RequestParam(required = false) Long orderId,
|
||||
@RequestParam String returnUrl,
|
||||
HttpServletResponse response) {
|
||||
String target = validateSettlementPageUrl(returnUrl.trim());
|
||||
response.setStatus(HttpServletResponse.SC_FOUND);
|
||||
response.setHeader("Location", target);
|
||||
}
|
||||
|
||||
/** 校验前端传入的结算页 URL(http/https)。 */
|
||||
private static String validateSettlementPageUrl(String url) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user