同步平台业务模块能力
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_appid,WxPay 必填) */
|
||||
private String subAppid;
|
||||
/** 微信:用户 OpenID(安心付 channel_params.open_id) */
|
||||
private String openId;
|
||||
/** 支付宝小程序:用户 buyer_id(安心付 channel_params.buyer_id) */
|
||||
private String buyerId;
|
||||
private String ext;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 minipay:channel_params.sub_appid */
|
||||
private String subAppid;
|
||||
/** WxPay minipay:channel_params.open_id */
|
||||
private String openId;
|
||||
/** AliPay minipay:channel_params.buyer_id */
|
||||
private String buyerId;
|
||||
private String clientIp;
|
||||
/** 调起场景 */
|
||||
private PayClientScene payClientScene;
|
||||
}
|
||||
@@ -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:小程序或公众号 AppID(sub_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;
|
||||
}
|
||||
@@ -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 + WxPay:wx.requestPayment 参数 */
|
||||
private WxPayInvokeParams wxPayParams;
|
||||
/** minipay + AliPay:tradeNO,用于 my.tradePay */
|
||||
private String alipayTradeNo;
|
||||
private String expiredAt;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class PaymentOrderCreateResponse {
|
||||
public class OrderCreateResponse {
|
||||
private String gatewayPaymentOrderNo;
|
||||
private String bizOrderNo;
|
||||
private PaymentStatus status;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.xspaceagi.pay.sdk.enums;
|
||||
|
||||
public enum PayInvokeType {
|
||||
FORM_HTML,
|
||||
REDIRECT_URL,
|
||||
QRCODE_FALLBACK
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.xspaceagi.pay.sdk.enums;
|
||||
|
||||
public enum PayMode {
|
||||
scan("主扫");
|
||||
scan("主扫"),
|
||||
minipay("小程序支付"),
|
||||
h5("H5支付"),
|
||||
app("App原生支付");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
@@ -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 一致,幂等)。
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/** 微信/支付宝小程序支付(minipay)Open 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
Reference in New Issue
Block a user