同步平台业务模块能力

This commit is contained in:
baiyanyun
2026-07-13 09:13:30 +08:00
parent e612f5e434
commit a1a38f62d8
224 changed files with 22331 additions and 1224 deletions

View File

@@ -0,0 +1,20 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayClientScene;
import lombok.Data;
@Data
public class AppOrderAndTransactionCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String bizOrderNo;
private PayChannel payChannel;
private Long orderAmount;
private String subject;
private String ext;
private String clientIp;
private PayClientScene payClientScene;
}

View File

@@ -0,0 +1,15 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
@Data
public class AppOrderCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String bizOrderNo;
private Long orderAmount;
private String subject;
private String ext;
}

View File

@@ -0,0 +1,12 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
/** 创建 App 原生支付单(仅落网关支付单,不调渠道)。 */
@Data
public class AppOrderRpcCreateRequest {
private String bizOrderNo;
private Long orderAmount;
private String subject;
private String ext;
}

View File

@@ -0,0 +1,17 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayClientScene;
import lombok.Data;
@Data
public class AppTransactionCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String gatewayPaymentOrderNo;
private PayChannel payChannel;
private String clientIp;
private PayClientScene payClientScene;
}

View File

@@ -0,0 +1,14 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayClientScene;
import lombok.Data;
/** 对已有 App 支付单调渠道下单,返回原生 SDK 调起参数。 */
@Data
public class AppTransactionRpcCreateRequest {
private String bizOrderNo;
private PayChannel payChannel;
private String clientIp;
private PayClientScene payClientScene;
}

View File

@@ -0,0 +1,14 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
/** Open API 文件上传元数据(不含文件二进制;与 multipart 表单字段一致)。 */
@Data
public class FileEntryOpenApiUploadRequest {
private String clientId;
private String bizType;
private String replaceFileKey;
private Long timestamp;
private String nonce;
private String signature;
}

View File

@@ -0,0 +1,19 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileEntryUploadResponse {
/** 32 位小写 hex无中划线 */
private String fileKey;
/** 对外可访问 URL含 site.base-url */
private String publicUrl;
private String contentType;
private int sizeBytes;
}

View File

@@ -0,0 +1,15 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
@Data
public class H5OrderCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String bizOrderNo;
private Long orderAmount;
private String subject;
private String ext;
}

View File

@@ -0,0 +1,12 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
/** 创建 H5支付单仅落网关支付单不调渠道。 */
@Data
public class H5OrderRpcCreateRequest {
private String bizOrderNo;
private Long orderAmount;
private String subject;
private String ext;
}

View File

@@ -0,0 +1,16 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import lombok.Data;
@Data
public class H5TransactionCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String gatewayPaymentOrderNo;
private PayChannel payChannel;
private String clientIp;
private String frontNotifyUrl;
}

View File

@@ -0,0 +1,13 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import lombok.Data;
/** 对已有 h5 支付单调渠道下单,返回前端调起参数。 */
@Data
public class H5TransactionRpcCreateRequest {
private String bizOrderNo;
private PayChannel payChannel;
private String clientIp;
private String frontNotifyUrl;
}

View File

@@ -54,18 +54,25 @@ public class MerchantOnboardingResponse {
/** 营业执照 */
private String orgCertificateUrl;
private String orgCertificateFileKey;
/** 法人身份证正面 */
private String legalPersonIdCardFrontUrl;
private String legalPersonIdCardFrontFileKey;
/** 法人身份证背面 */
private String legalPersonIdCardBackUrl;
private String legalPersonIdCardBackFileKey;
/** 财务室照片 */
private String photoFinanceRoomUrl;
private String photoFinanceRoomFileKey;
/** 门头照片 */
private String photoGateUrl;
private String photoGateFileKey;
/** 地标照片 */
private String photoLandmarkUrl;
private String photoLandmarkFileKey;
/** 银行开户证明 */
private String bankAccountProofUrl;
private String bankAccountProofFileKey;
private LocalDateTime created;
private LocalDateTime modified;

View File

@@ -5,7 +5,8 @@ import com.xspaceagi.pay.sdk.enums.MerchantOnboardingType;
import lombok.Data;
/**
* 创建/全量更新进件资料(图片字段均为 URL
* 创建/全量更新进件资料。
* 影像优先传 {@code *FileKey}(本系统上传);{@code *Url} 保留兼容外部链接。
*/
@Data
public class MerchantOnboardingUpsertRequest {
@@ -56,4 +57,16 @@ public class MerchantOnboardingUpsertRequest {
private String photoGateUrl;
private String photoLandmarkUrl;
private String bankAccountProofUrl;
/** 营业执照;本系统上传时为 file_entry.file_key */
private String orgCertificateFileKey;
/** 法人身份证人像面/正面 file_key */
private String legalPersonIdCardFrontFileKey;
/** 法人身份证国徽面/反面 file_key */
private String legalPersonIdCardBackFileKey;
private String photoFinanceRoomFileKey;
private String photoGateFileKey;
private String photoLandmarkFileKey;
private String bankAccountProofFileKey;
}

View File

@@ -0,0 +1,23 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import lombok.Data;
@Data
public class MiniPayOrderAndTransactionCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String bizOrderNo;
private PayChannel payChannel;
private Long orderAmount;
private String subject;
/** 微信小程序 AppID安心付 channel_params.sub_appidWxPay 必填) */
private String subAppid;
/** 微信:用户 OpenID安心付 channel_params.open_id */
private String openId;
/** 支付宝小程序:用户 buyer_id安心付 channel_params.buyer_id */
private String buyerId;
private String ext;
}

View File

@@ -0,0 +1,14 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
@Data
public class MiniPayOrderCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String bizOrderNo;
private Long orderAmount;
private String subject;
}

View File

@@ -0,0 +1,12 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Data;
/** 创建小程序支付单(仅落网关支付单,不调渠道)。 */
@Data
public class MiniPayOrderRpcCreateRequest {
private String bizOrderNo;
private Long orderAmount;
private String subject;
private String ext;
}

View File

@@ -0,0 +1,24 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayClientScene;
import lombok.Data;
@Data
public class MiniPayTransactionCreateRequest {
private String clientId;
private Long timestamp;
private String nonce;
private String signature;
private String gatewayPaymentOrderNo;
private PayChannel payChannel;
/** WxPay minipaychannel_params.sub_appid */
private String subAppid;
/** WxPay minipaychannel_params.open_id */
private String openId;
/** AliPay minipaychannel_params.buyer_id */
private String buyerId;
private String clientIp;
/** 调起场景 */
private PayClientScene payClientScene;
}

View File

@@ -0,0 +1,21 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayClientScene;
import lombok.Data;
/** 对已有 minipay 支付单调渠道下单,返回调起支付参数。 */
@Data
public class MiniPayTransactionRpcCreateRequest {
private String bizOrderNo;
private PayChannel payChannel;
/** WxPay minipay小程序或公众号 AppIDsub_appid */
private String subAppid;
/** WxPay minipay用户 OpenID */
private String openId;
/** AliPay minipay用户 buyer_id */
private String buyerId;
private String clientIp;
/** minipay 调起场景:小程序 / 微信JSAPI写入 pay_order.ext */
private PayClientScene payClientScene;
}

View File

@@ -1,14 +1,16 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayInvokeType;
import com.xspaceagi.pay.sdk.enums.PayMode;
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
import lombok.Builder;
import lombok.Data;
/** 创建支付单/渠道下单后的统一响应 */
@Data
@Builder
public class ScanOrderCreateResponse {
public class OrderAndTransactionCreateResponse {
private String gatewayPaymentOrderNo;
private String gatewayPaymentTxNo;
/** 渠道服务费,单位分 */
@@ -20,6 +22,14 @@ public class ScanOrderCreateResponse {
private PaymentStatus status;
private PayChannel payChannel;
private PayMode payMode;
private PayInvokeType invokeType;
/** 主扫 scan渠道返回的二维码/链接原文payparam */
private String qrCodeContent;
private String formHtml;
private String redirectUrl;
/** minipay + WxPaywx.requestPayment 参数 */
private WxPayInvokeParams wxPayParams;
/** minipay + AliPaytradeNO用于 my.tradePay */
private String alipayTradeNo;
private String expiredAt;
}

View File

@@ -7,7 +7,7 @@ import lombok.Data;
@Data
@Builder
public class PaymentOrderCreateResponse {
public class OrderCreateResponse {
private String gatewayPaymentOrderNo;
private String bizOrderNo;
private PaymentStatus status;

View File

@@ -1,6 +1,7 @@
package com.xspaceagi.pay.sdk.dto;
import com.xspaceagi.pay.sdk.enums.PayChannel;
import com.xspaceagi.pay.sdk.enums.PayInvokeType;
import com.xspaceagi.pay.sdk.enums.PayMode;
import com.xspaceagi.pay.sdk.enums.PaymentStatus;
import lombok.Builder;
@@ -8,7 +9,7 @@ import lombok.Data;
@Data
@Builder
public class ScanOrderStatusQueryResponse {
public class PaymentStatusQueryResponse {
private String gatewayPaymentOrderNo;
private String gatewayPaymentTxNo;
private PaymentStatus status;
@@ -19,6 +20,9 @@ public class ScanOrderStatusQueryResponse {
private Long providerFee;
private Long platformFee;
private Long netAmount;
private PayInvokeType invokeType;
private String qrCodeContent;
private String formHtml;
private String redirectUrl;
private String paidAt;
}

View File

@@ -0,0 +1,17 @@
package com.xspaceagi.pay.sdk.dto;
import lombok.Builder;
import lombok.Data;
/** 微信小程序 {@code wx.requestPayment} 所需字段(来自渠道 payparam JSON。 */
@Data
@Builder
public class WxPayInvokeParams {
private String appId;
private String timeStamp;
private String nonceStr;
/** 对应微信字段 package值为 prepay_id=... */
private String packageValue;
private String signType;
private String paySign;
}

View File

@@ -0,0 +1,19 @@
package com.xspaceagi.pay.sdk.enums;
/** 调起场景 */
public enum PayClientScene {
MINI_PROGRAM("微信小程序"), //渠道 pay_mode 均为 minipay
WECHAT_JSAPI("微信JSAPI"), //渠道 pay_mode 均为 minipay
H5_WEB("系统浏览器H5"),
NATIVE_APP("App原生SDK"); // gateway_back微信 common / 支付宝 scan
private final String name;
PayClientScene(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,7 @@
package com.xspaceagi.pay.sdk.enums;
public enum PayInvokeType {
FORM_HTML,
REDIRECT_URL,
QRCODE_FALLBACK
}

View File

@@ -1,7 +1,10 @@
package com.xspaceagi.pay.sdk.enums;
public enum PayMode {
scan("主扫");
scan("主扫"),
minipay("小程序支付"),
h5("H5支付"),
app("App原生支付");
private final String name;

View File

@@ -1,17 +1,42 @@
package com.xspaceagi.pay.sdk.service;
import com.xspaceagi.pay.sdk.dto.AppOrderRpcCreateRequest;
import com.xspaceagi.pay.sdk.dto.AppTransactionRpcCreateRequest;
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateRequest;
import com.xspaceagi.pay.sdk.dto.CashierSessionCreateResponse;
import com.xspaceagi.pay.sdk.dto.PaymentOrderCreateResponse;
import com.xspaceagi.pay.sdk.dto.H5OrderRpcCreateRequest;
import com.xspaceagi.pay.sdk.dto.H5TransactionRpcCreateRequest;
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.PaymentStatusQueryRequest;
import com.xspaceagi.pay.sdk.dto.ScanOrderCreateRequest;
import com.xspaceagi.pay.sdk.dto.ScanOrderStatusQueryResponse;
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryResponse;
/** 平台内支付 RPC 契约(由 pay-application 实现,供 bill 等外部模块依赖调用)。 */
public interface IPaymentRpcService {
/** 创建扫码支付订单 */
PaymentOrderCreateResponse createOrderForScan(ScanOrderCreateRequest request);
OrderCreateResponse createOrderForScan(ScanOrderCreateRequest request);
/** 创建小程序支付单(仅落网关支付单,不调渠道;调起支付见 {@link #createMiniPayTransaction} */
OrderCreateResponse createOrderForMiniPay(MiniPayOrderRpcCreateRequest request);
/** 创建 H5支付单仅落网关支付单不调渠道调起支付见 {@link #createH5Transaction} */
OrderCreateResponse createOrderForH5(H5OrderRpcCreateRequest request);
/** 创建 App 原生支付单(仅落网关支付单,不调渠道;调起支付见 {@link #createAppTransaction} */
OrderCreateResponse createOrderForApp(AppOrderRpcCreateRequest request);
/** 对已有小程序支付单调渠道下单,返回 wx.requestPayment / my.tradePay 参数 */
OrderAndTransactionCreateResponse createMiniPayTransaction(MiniPayTransactionRpcCreateRequest request);
/** 对已有 h5 支付单调渠道下单,返回 formHtml / redirectUrl / qrcode 兜底 */
OrderAndTransactionCreateResponse createH5Transaction(H5TransactionRpcCreateRequest request);
/** 对已有 App 支付单调渠道下单,返回 wxPayParams / redirectUrl */
OrderAndTransactionCreateResponse createAppTransaction(AppTransactionRpcCreateRequest request);
/**
* 获取收银台地址;会话有过期时间,过期后需重新调用。
@@ -19,7 +44,7 @@ public interface IPaymentRpcService {
CashierSessionCreateResponse createCashierSession(CashierSessionCreateRequest request);
/** 查询支付状态(仅更新 pay_order不通知 Bill */
ScanOrderStatusQueryResponse queryStatus(PaymentStatusQueryRequest request);
PaymentStatusQueryResponse queryStatus(PaymentStatusQueryRequest request);
/**
* 结算页主动同步:按业务单号查网关终态并通知 Bill与支付轮询任务单 tick 一致,幂等)。

View File

@@ -0,0 +1,65 @@
package com.xspaceagi.pay.sdk.sign;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Builder;
import lombok.Value;
/** 将已签名的 Open API JSON 元数据解析为 multipart 表单字段。 */
public final class OpenApiFileEntryMultipartSupport {
private OpenApiFileEntryMultipartSupport() {}
@Value
@Builder
public static class SignedUploadFields {
String clientId;
String bizType;
String replaceFileKey;
long timestamp;
String nonce;
String signature;
}
public static SignedUploadFields parseSignedMetadata(byte[] signedJson) {
if (signedJson == null || signedJson.length == 0) {
throw new IllegalArgumentException("signed metadata must not be empty");
}
try {
JsonNode root = OpenApiSignJson.mapper().readTree(signedJson);
JsonNode clientId = root.get("clientId");
JsonNode bizType = root.get("bizType");
JsonNode timestamp = root.get("timestamp");
JsonNode nonce = root.get("nonce");
JsonNode signature = root.get("signature");
if (clientId == null
|| !clientId.isTextual()
|| bizType == null
|| !bizType.isTextual()
|| timestamp == null
|| !timestamp.isNumber()
|| nonce == null
|| !nonce.isTextual()
|| signature == null
|| !signature.isTextual()) {
throw new IllegalArgumentException("invalid signed file upload metadata");
}
String replaceFileKey = null;
JsonNode replaceNode = root.get("replaceFileKey");
if (replaceNode != null && replaceNode.isTextual() && !replaceNode.asText().isBlank()) {
replaceFileKey = replaceNode.asText().trim();
}
return SignedUploadFields.builder()
.clientId(clientId.asText().trim())
.bizType(bizType.asText().trim())
.replaceFileKey(replaceFileKey)
.timestamp(timestamp.longValue())
.nonce(nonce.asText().trim())
.signature(signature.asText().trim())
.build();
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException("invalid signed file upload metadata", e);
}
}
}

View File

@@ -0,0 +1,15 @@
package com.xspaceagi.pay.sdk.sign;
import com.xspaceagi.pay.sdk.dto.FileEntryOpenApiUploadRequest;
/** 文件 Open API 路径与签名辅助 */
public final class OpenApiFileEntrySign {
public static final String PATH_UPLOAD = "/open-api/file/upload";
private OpenApiFileEntrySign() {}
public static byte[] signUpload(FileEntryOpenApiUploadRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_UPLOAD, request, clientSecret);
}
}

View File

@@ -0,0 +1,35 @@
package com.xspaceagi.pay.sdk.sign;
import com.xspaceagi.pay.sdk.dto.AppOrderAndTransactionCreateRequest;
import com.xspaceagi.pay.sdk.dto.AppOrderCreateRequest;
import com.xspaceagi.pay.sdk.dto.AppTransactionCreateRequest;
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryRequest;
/** App 原生支付 Open API 路径与签名辅助。 */
public final class OpenApiPaymentAppSign {
public static final String BASE = "/open-api/payment/app";
public static final String PATH_CREATE_ORDER_AND_TRANSACTION = BASE + "/create-order-and-transaction";
public static final String PATH_CREATE_ORDER = BASE + "/create-order";
public static final String PATH_CREATE_TRANSACTION = BASE + "/create-transaction";
public static final String PATH_STATUS = BASE + "/status";
private OpenApiPaymentAppSign() {}
public static byte[] signCreateOrderAndTransaction(
AppOrderAndTransactionCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_ORDER_AND_TRANSACTION, request, clientSecret);
}
public static byte[] signCreateOrder(AppOrderCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_ORDER, request, clientSecret);
}
public static byte[] signCreateTransaction(AppTransactionCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_TRANSACTION, request, clientSecret);
}
public static byte[] signQueryStatus(PaymentStatusQueryRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_STATUS, request, clientSecret);
}
}

View File

@@ -0,0 +1,27 @@
package com.xspaceagi.pay.sdk.sign;
import com.xspaceagi.pay.sdk.dto.H5OrderCreateRequest;
import com.xspaceagi.pay.sdk.dto.H5TransactionCreateRequest;
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryRequest;
public final class OpenApiPaymentH5Sign {
public static final String BASE = "/open-api/payment/h5";
public static final String PATH_CREATE_ORDER = BASE + "/create-order";
public static final String PATH_CREATE_TRANSACTION = BASE + "/create-transaction";
public static final String PATH_STATUS = BASE + "/status";
private OpenApiPaymentH5Sign() {}
public static byte[] signCreateOrder(H5OrderCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_ORDER, request, clientSecret);
}
public static byte[] signCreateTransaction(H5TransactionCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_TRANSACTION, request, clientSecret);
}
public static byte[] signQueryStatus(PaymentStatusQueryRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_STATUS, request, clientSecret);
}
}

View File

@@ -0,0 +1,35 @@
package com.xspaceagi.pay.sdk.sign;
import com.xspaceagi.pay.sdk.dto.MiniPayOrderAndTransactionCreateRequest;
import com.xspaceagi.pay.sdk.dto.MiniPayOrderCreateRequest;
import com.xspaceagi.pay.sdk.dto.MiniPayTransactionCreateRequest;
import com.xspaceagi.pay.sdk.dto.PaymentStatusQueryRequest;
/** 微信/支付宝小程序支付minipayOpen API 路径与签名辅助 */
public final class OpenApiPaymentMiniPaySign {
public static final String BASE = "/open-api/payment/minipay";
public static final String PATH_CREATE_ORDER_AND_TRANSACTION = BASE + "/create-order-and-transaction";
public static final String PATH_CREATE_ORDER = BASE + "/create-order";
public static final String PATH_CREATE_TRANSACTION = BASE + "/create-transaction";
public static final String PATH_STATUS = BASE + "/status";
private OpenApiPaymentMiniPaySign() {}
public static byte[] signCreateOrderAndTransaction(
MiniPayOrderAndTransactionCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_ORDER_AND_TRANSACTION, request, clientSecret);
}
public static byte[] signCreateOrder(MiniPayOrderCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_ORDER, request, clientSecret);
}
public static byte[] signCreateTransaction(MiniPayTransactionCreateRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_CREATE_TRANSACTION, request, clientSecret);
}
public static byte[] signQueryStatus(PaymentStatusQueryRequest request, String clientSecret) {
return OpenApiSignSupport.sign(PATH_STATUS, request, clientSecret);
}
}

View File

@@ -4,8 +4,7 @@ import java.util.List;
import java.util.Map;
/**
* 各 Open API 路径参与 body 哈希的字段(不含 {@code timestamp}/{@code nonce}/{@code signature})。
* <p>path 与各 {@code OpenApi*Sign#PATH_*} 保持一致,为单一来源。</p>
* 各 Open API 路径参与 body 哈希的字段
*/
public final class OpenApiSignPolicies {
@@ -24,6 +23,39 @@ public final class OpenApiSignPolicies {
Map.entry(
OpenApiPaymentScanSign.PATH_STATUS,
List.of("clientId", "gatewayPaymentOrderNo", "syncFromChannel")),
Map.entry(
OpenApiPaymentMiniPaySign.PATH_CREATE_ORDER_AND_TRANSACTION,
List.of("bizOrderNo", "buyerId", "clientId", "openId", "orderAmount", "payChannel", "subAppid")),
Map.entry(
OpenApiPaymentMiniPaySign.PATH_CREATE_ORDER,
List.of("bizOrderNo", "clientId", "orderAmount")),
Map.entry(
OpenApiPaymentMiniPaySign.PATH_CREATE_TRANSACTION,
List.of("buyerId", "clientId", "gatewayPaymentOrderNo", "openId", "payChannel", "subAppid")),
Map.entry(
OpenApiPaymentMiniPaySign.PATH_STATUS,
List.of("clientId", "gatewayPaymentOrderNo", "syncFromChannel")),
Map.entry(
OpenApiPaymentH5Sign.PATH_CREATE_ORDER,
List.of("bizOrderNo", "clientId", "orderAmount")),
Map.entry(
OpenApiPaymentH5Sign.PATH_CREATE_TRANSACTION,
List.of("clientId", "frontNotifyUrl", "gatewayPaymentOrderNo", "payChannel")),
Map.entry(
OpenApiPaymentH5Sign.PATH_STATUS,
List.of("clientId", "gatewayPaymentOrderNo", "syncFromChannel")),
Map.entry(
OpenApiPaymentAppSign.PATH_CREATE_ORDER_AND_TRANSACTION,
List.of("bizOrderNo", "clientId", "orderAmount", "payChannel")),
Map.entry(
OpenApiPaymentAppSign.PATH_CREATE_ORDER,
List.of("bizOrderNo", "clientId", "orderAmount")),
Map.entry(
OpenApiPaymentAppSign.PATH_CREATE_TRANSACTION,
List.of("clientId", "gatewayPaymentOrderNo", "payChannel")),
Map.entry(
OpenApiPaymentAppSign.PATH_STATUS,
List.of("clientId", "gatewayPaymentOrderNo", "syncFromChannel")),
Map.entry(OpenApiPaymentConfigSign.PATH_QUERY, List.of("clientId")),
Map.entry(
OpenApiMerchantOnboardingSign.PATH_ADD,
@@ -38,7 +70,10 @@ public final class OpenApiSignPolicies {
List.of("clientId", "onboardingType", "page", "pageSize", "status")),
Map.entry(
OpenApiCashierSign.PATH_SESSION,
List.of("clientId", "gatewayPaymentOrderNo", "orderAmount")));
List.of("clientId", "gatewayPaymentOrderNo", "orderAmount")),
Map.entry(
OpenApiFileEntrySign.PATH_UPLOAD,
List.of("clientId", "bizType", "replaceFileKey")));
public static List<String> signFieldsForPath(String path) {
String normalized = normalizePath(path);

View File

@@ -0,0 +1,63 @@
package com.xspaceagi.pay.sdk.support;
import java.util.Locale;
import java.util.Set;
/**
* 识别 App 壳 / WebView 内发起的 HTTP 请求(非系统浏览器、非微信内置浏览器)。
* 平台 App 请求会携带 {@value #HEADER_CLIENT_TYPE};兜底解析 User-Agent 中的 App 标记。
*/
public final class PayAppWebViewDetector {
/** App 客户端标识,与 AuthInterceptor 续期逻辑一致:非空即视为 App 请求。 */
public static final String HEADER_CLIENT_TYPE = "X-Client-Type";
private static final Set<String> APP_CLIENT_TYPE_VALUES = Set.of(
"app",
"native",
"ios",
"android",
"mobile",
"mobile-app",
"qiming-app",
"nuwax-app");
private static final Set<String> WEB_CLIENT_TYPE_VALUES = Set.of("web", "h5", "browser", "wap");
private PayAppWebViewDetector() {}
/**
* 是否为 App WebView 内请求。H5 支付在此环境下会被渠道判定违规,应改用 App 原生 SDK 支付。
*/
public static boolean isAppWebView(String clientTypeHeader, String userAgent) {
if (clientTypeHeader != null && !clientTypeHeader.isBlank()) {
String normalized = clientTypeHeader.trim().toLowerCase(Locale.ROOT);
if (WEB_CLIENT_TYPE_VALUES.contains(normalized)) {
return false;
}
if (APP_CLIENT_TYPE_VALUES.contains(normalized) || normalized.startsWith("app")) {
return true;
}
// 平台 App 统一注入 X-Client-Type非 web 类取值视为 App 壳(与鉴权续期策略一致)
return true;
}
return matchesAppWebViewUserAgent(userAgent);
}
private static boolean matchesAppWebViewUserAgent(String userAgent) {
if (userAgent == null || userAgent.isBlank()) {
return false;
}
String ua = userAgent;
if (ua.contains("QimingApp") || ua.contains("QIMING_APP") || ua.contains("qiming-app")
|| ua.contains("NuwaxApp") || ua.contains("NUWAX_APP") || ua.contains("nuwax-app")) {
return true;
}
String lower = ua.toLowerCase(Locale.ROOT);
if ((lower.contains("qiming") || lower.contains("nuwax"))
&& (lower.contains("webview") || lower.contains("; wv)"))) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,12 @@
package com.xspaceagi.pay.sdk.support;
/** pay_order.ext / Bill order extra 中与支付相关的扩展键。 */
public final class PayOrderExtKeys {
public static final String PAY_CLIENT_SCENE = "payClientScene";
public static final String GENERAL_PROJECT_ID = "generalProjectId";
public static final String GENERAL_PROJECT_USER_ID = "generalProjectUserId";
private PayOrderExtKeys() {}
}