@tokenbuddy/contracts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/crypto.d.ts +45 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +129 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/errors.d.ts +47 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +162 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +17 -0
- package/src/crypto.ts +120 -0
- package/src/errors.ts +173 -0
- package/src/index.ts +2 -0
- package/tests/crypto.test.ts +91 -0
- package/tests/errors.test.ts +34 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypt a plaintext string using SM4-ECB with PKCS7 padding.
|
|
3
|
+
* Returns the ciphertext encoded as a base64 string.
|
|
4
|
+
*/
|
|
5
|
+
export declare function encryptSm4EcbPkcs7Base64(plaintext: string, sm4KeyBase64: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Decrypt a base64 ciphertext string using SM4-ECB with PKCS7 padding.
|
|
8
|
+
* Returns the decrypted plaintext string.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decryptSm4EcbPkcs7Base64(encryptedBase64: string, sm4KeyBase64: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Compute md5 hex digest of a string (indicator).
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeIndicator(slug: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a 32-character lowercase hex string as orderNo (UUID v4 without dashes).
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateOrderNo(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Encrypt Clawtip order data: {"orderNo":..., "amount":..., "payTo":...}
|
|
21
|
+
* Returns the base64 encrypted data.
|
|
22
|
+
*/
|
|
23
|
+
export declare function encryptClawtipOrderData(orderNo: string, amount: number, payTo: string, sm4KeyBase64: string): string;
|
|
24
|
+
export interface ClawtipPaymentParams {
|
|
25
|
+
orderNo: string;
|
|
26
|
+
amountFen: number;
|
|
27
|
+
payTo: string;
|
|
28
|
+
encryptedData: string;
|
|
29
|
+
indicator: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
skillId: string;
|
|
32
|
+
description: string;
|
|
33
|
+
resourceUrl: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ClawtipPaymentParamsInput {
|
|
36
|
+
payTo: string;
|
|
37
|
+
sm4KeyBase64: string;
|
|
38
|
+
skillSlug: string;
|
|
39
|
+
skillId: string;
|
|
40
|
+
description: string;
|
|
41
|
+
resourceUrl: string;
|
|
42
|
+
amountFen: number;
|
|
43
|
+
}
|
|
44
|
+
export declare function buildClawtipPaymentParams(input: ClawtipPaymentParamsInput): ClawtipPaymentParams;
|
|
45
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWxF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAU9F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GACnB,MAAM,CAOR;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,yBAAyB,GAAG,oBAAoB,CAiBhG"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.encryptSm4EcbPkcs7Base64 = encryptSm4EcbPkcs7Base64;
|
|
37
|
+
exports.decryptSm4EcbPkcs7Base64 = decryptSm4EcbPkcs7Base64;
|
|
38
|
+
exports.computeIndicator = computeIndicator;
|
|
39
|
+
exports.generateOrderNo = generateOrderNo;
|
|
40
|
+
exports.encryptClawtipOrderData = encryptClawtipOrderData;
|
|
41
|
+
exports.buildClawtipPaymentParams = buildClawtipPaymentParams;
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const uuid_1 = require("uuid");
|
|
44
|
+
/**
|
|
45
|
+
* Decode a base64 SM4 key to a 16-byte Buffer.
|
|
46
|
+
* Throws an error if the decoded key is not exactly 16 bytes.
|
|
47
|
+
*/
|
|
48
|
+
function decodeSm4Key(sm4KeyBase64) {
|
|
49
|
+
const key = Buffer.from(sm4KeyBase64, "base64");
|
|
50
|
+
if (key.length !== 16) {
|
|
51
|
+
throw new Error(`SM4 key must decode to 16 bytes, got ${key.length}`);
|
|
52
|
+
}
|
|
53
|
+
return key;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Encrypt a plaintext string using SM4-ECB with PKCS7 padding.
|
|
57
|
+
* Returns the ciphertext encoded as a base64 string.
|
|
58
|
+
*/
|
|
59
|
+
function encryptSm4EcbPkcs7Base64(plaintext, sm4KeyBase64) {
|
|
60
|
+
try {
|
|
61
|
+
const key = decodeSm4Key(sm4KeyBase64);
|
|
62
|
+
// Node.js crypto uses 'sm4-ecb' which defaults to PKCS7 padding for ECB
|
|
63
|
+
const cipher = crypto.createCipheriv("sm4-ecb", key, null);
|
|
64
|
+
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
65
|
+
encrypted += cipher.final("base64");
|
|
66
|
+
return encrypted;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error(`SM4 encryption failed: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Decrypt a base64 ciphertext string using SM4-ECB with PKCS7 padding.
|
|
74
|
+
* Returns the decrypted plaintext string.
|
|
75
|
+
*/
|
|
76
|
+
function decryptSm4EcbPkcs7Base64(encryptedBase64, sm4KeyBase64) {
|
|
77
|
+
try {
|
|
78
|
+
const key = decodeSm4Key(sm4KeyBase64);
|
|
79
|
+
const decipher = crypto.createDecipheriv("sm4-ecb", key, null);
|
|
80
|
+
let decrypted = decipher.update(encryptedBase64, "base64", "utf8");
|
|
81
|
+
decrypted += decipher.final("utf8");
|
|
82
|
+
return decrypted;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new Error(`SM4 decryption failed: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compute md5 hex digest of a string (indicator).
|
|
90
|
+
*/
|
|
91
|
+
function computeIndicator(slug) {
|
|
92
|
+
return crypto.createHash("md5").update(slug, "utf8").digest("hex");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Generate a 32-character lowercase hex string as orderNo (UUID v4 without dashes).
|
|
96
|
+
*/
|
|
97
|
+
function generateOrderNo() {
|
|
98
|
+
return (0, uuid_1.v4)().replace(/-/g, "");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Encrypt Clawtip order data: {"orderNo":..., "amount":..., "payTo":...}
|
|
102
|
+
* Returns the base64 encrypted data.
|
|
103
|
+
*/
|
|
104
|
+
function encryptClawtipOrderData(orderNo, amount, payTo, sm4KeyBase64) {
|
|
105
|
+
const plaintext = JSON.stringify({
|
|
106
|
+
orderNo,
|
|
107
|
+
amount,
|
|
108
|
+
payTo
|
|
109
|
+
});
|
|
110
|
+
return encryptSm4EcbPkcs7Base64(plaintext, sm4KeyBase64);
|
|
111
|
+
}
|
|
112
|
+
function buildClawtipPaymentParams(input) {
|
|
113
|
+
if (!Number.isInteger(input.amountFen) || input.amountFen < 1) {
|
|
114
|
+
throw new Error("amount_fen must be greater than zero");
|
|
115
|
+
}
|
|
116
|
+
const orderNo = generateOrderNo();
|
|
117
|
+
return {
|
|
118
|
+
orderNo,
|
|
119
|
+
amountFen: input.amountFen,
|
|
120
|
+
payTo: input.payTo,
|
|
121
|
+
encryptedData: encryptClawtipOrderData(orderNo, input.amountFen, input.payTo, input.sm4KeyBase64),
|
|
122
|
+
indicator: computeIndicator(input.skillSlug),
|
|
123
|
+
slug: input.skillSlug,
|
|
124
|
+
skillId: input.skillId,
|
|
125
|
+
description: input.description,
|
|
126
|
+
resourceUrl: input.resourceUrl
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare enum RetryClass {
|
|
2
|
+
FailClosed = "fail_closed",
|
|
3
|
+
RetryableWithBackoff = "retryable_with_backoff",
|
|
4
|
+
NotRetryable = "not_retryable"
|
|
5
|
+
}
|
|
6
|
+
export declare enum ErrorCode {
|
|
7
|
+
BusyCapacity = "busy_capacity",
|
|
8
|
+
PaymentRequired = "payment_required",
|
|
9
|
+
PaymentAccountInsufficient = "payment_account_insufficient",
|
|
10
|
+
PaymentMethodNotReady = "payment_method_not_ready",
|
|
11
|
+
PaymentAuthorizationRequired = "payment_authorization_required",
|
|
12
|
+
PaymentCancelled = "payment_cancelled",
|
|
13
|
+
PaymentTimeout = "payment_timeout",
|
|
14
|
+
PaymentProviderFailed = "payment_provider_failed",
|
|
15
|
+
InvalidPurchaseRequest = "invalid_purchase_request",
|
|
16
|
+
ClawtipCredentialInvalid = "clawtip_credential_invalid",
|
|
17
|
+
ClawtipOrderMismatch = "clawtip_order_mismatch",
|
|
18
|
+
InsufficientFunds = "insufficient_funds",
|
|
19
|
+
InvalidOrExpiredToken = "invalid_or_expired_token",
|
|
20
|
+
TokenClassRevoked = "token_class_revoked",
|
|
21
|
+
RequestIdRequired = "request_id_required",
|
|
22
|
+
StreamingNotSupported = "streaming_not_supported",
|
|
23
|
+
IdempotencyConflict = "idempotency_conflict",
|
|
24
|
+
PurchaseNotFound = "purchase_not_found",
|
|
25
|
+
RequestNotFound = "request_not_found",
|
|
26
|
+
ModelNotAvailable = "model_not_available",
|
|
27
|
+
UpstreamNotConfigured = "upstream_not_configured",
|
|
28
|
+
ProtocolNotAvailable = "protocol_not_available",
|
|
29
|
+
LedgerFailClosed = "ledger_fail_closed",
|
|
30
|
+
UpstreamTimeout = "upstream_timeout",
|
|
31
|
+
ContractVersionDrift = "contract_version_drift"
|
|
32
|
+
}
|
|
33
|
+
export declare function getRetryClass(code: ErrorCode): RetryClass;
|
|
34
|
+
export declare function getHttpStatus(code: ErrorCode): number;
|
|
35
|
+
export declare function getDefaultMessage(code: ErrorCode): string;
|
|
36
|
+
export interface ErrorBody {
|
|
37
|
+
code: ErrorCode;
|
|
38
|
+
retryClass: RetryClass;
|
|
39
|
+
message: string;
|
|
40
|
+
httpStatus: number;
|
|
41
|
+
retryAfterSeconds?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface ErrorEnvelope {
|
|
44
|
+
error: ErrorBody;
|
|
45
|
+
}
|
|
46
|
+
export declare function createErrorEnvelope(code: ErrorCode, message?: string, retryAfterSeconds?: number): ErrorEnvelope;
|
|
47
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IACpB,UAAU,gBAAgB;IAC1B,oBAAoB,2BAA2B;IAC/C,YAAY,kBAAkB;CAC/B;AAED,oBAAY,SAAS;IACnB,YAAY,kBAAkB;IAC9B,eAAe,qBAAqB;IACpC,0BAA0B,iCAAiC;IAC3D,qBAAqB,6BAA6B;IAClD,4BAA4B,mCAAmC;IAC/D,gBAAgB,sBAAsB;IACtC,cAAc,oBAAoB;IAClC,qBAAqB,4BAA4B;IACjD,sBAAsB,6BAA6B;IACnD,wBAAwB,+BAA+B;IACvD,oBAAoB,2BAA2B;IAC/C,iBAAiB,uBAAuB;IACxC,qBAAqB,6BAA6B;IAClD,iBAAiB,wBAAwB;IACzC,iBAAiB,wBAAwB;IACzC,qBAAqB,4BAA4B;IACjD,mBAAmB,yBAAyB;IAC5C,gBAAgB,uBAAuB;IACvC,eAAe,sBAAsB;IACrC,iBAAiB,wBAAwB;IACzC,qBAAqB,4BAA4B;IACjD,oBAAoB,2BAA2B;IAC/C,gBAAgB,uBAAuB;IACvC,eAAe,qBAAqB;IACpC,oBAAoB,2BAA2B;CAChD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAYzD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAuCrD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAuDzD;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,iBAAiB,CAAC,EAAE,MAAM,GACzB,aAAa,CAUf"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorCode = exports.RetryClass = void 0;
|
|
4
|
+
exports.getRetryClass = getRetryClass;
|
|
5
|
+
exports.getHttpStatus = getHttpStatus;
|
|
6
|
+
exports.getDefaultMessage = getDefaultMessage;
|
|
7
|
+
exports.createErrorEnvelope = createErrorEnvelope;
|
|
8
|
+
var RetryClass;
|
|
9
|
+
(function (RetryClass) {
|
|
10
|
+
RetryClass["FailClosed"] = "fail_closed";
|
|
11
|
+
RetryClass["RetryableWithBackoff"] = "retryable_with_backoff";
|
|
12
|
+
RetryClass["NotRetryable"] = "not_retryable";
|
|
13
|
+
})(RetryClass || (exports.RetryClass = RetryClass = {}));
|
|
14
|
+
var ErrorCode;
|
|
15
|
+
(function (ErrorCode) {
|
|
16
|
+
ErrorCode["BusyCapacity"] = "busy_capacity";
|
|
17
|
+
ErrorCode["PaymentRequired"] = "payment_required";
|
|
18
|
+
ErrorCode["PaymentAccountInsufficient"] = "payment_account_insufficient";
|
|
19
|
+
ErrorCode["PaymentMethodNotReady"] = "payment_method_not_ready";
|
|
20
|
+
ErrorCode["PaymentAuthorizationRequired"] = "payment_authorization_required";
|
|
21
|
+
ErrorCode["PaymentCancelled"] = "payment_cancelled";
|
|
22
|
+
ErrorCode["PaymentTimeout"] = "payment_timeout";
|
|
23
|
+
ErrorCode["PaymentProviderFailed"] = "payment_provider_failed";
|
|
24
|
+
ErrorCode["InvalidPurchaseRequest"] = "invalid_purchase_request";
|
|
25
|
+
ErrorCode["ClawtipCredentialInvalid"] = "clawtip_credential_invalid";
|
|
26
|
+
ErrorCode["ClawtipOrderMismatch"] = "clawtip_order_mismatch";
|
|
27
|
+
ErrorCode["InsufficientFunds"] = "insufficient_funds";
|
|
28
|
+
ErrorCode["InvalidOrExpiredToken"] = "invalid_or_expired_token";
|
|
29
|
+
ErrorCode["TokenClassRevoked"] = "token_class_revoked";
|
|
30
|
+
ErrorCode["RequestIdRequired"] = "request_id_required";
|
|
31
|
+
ErrorCode["StreamingNotSupported"] = "streaming_not_supported";
|
|
32
|
+
ErrorCode["IdempotencyConflict"] = "idempotency_conflict";
|
|
33
|
+
ErrorCode["PurchaseNotFound"] = "purchase_not_found";
|
|
34
|
+
ErrorCode["RequestNotFound"] = "request_not_found";
|
|
35
|
+
ErrorCode["ModelNotAvailable"] = "model_not_available";
|
|
36
|
+
ErrorCode["UpstreamNotConfigured"] = "upstream_not_configured";
|
|
37
|
+
ErrorCode["ProtocolNotAvailable"] = "protocol_not_available";
|
|
38
|
+
ErrorCode["LedgerFailClosed"] = "ledger_fail_closed";
|
|
39
|
+
ErrorCode["UpstreamTimeout"] = "upstream_timeout";
|
|
40
|
+
ErrorCode["ContractVersionDrift"] = "contract_version_drift";
|
|
41
|
+
})(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
|
|
42
|
+
function getRetryClass(code) {
|
|
43
|
+
switch (code) {
|
|
44
|
+
case ErrorCode.BusyCapacity:
|
|
45
|
+
case ErrorCode.UpstreamTimeout:
|
|
46
|
+
return RetryClass.RetryableWithBackoff;
|
|
47
|
+
case ErrorCode.LedgerFailClosed:
|
|
48
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
49
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
50
|
+
return RetryClass.FailClosed;
|
|
51
|
+
default:
|
|
52
|
+
return RetryClass.NotRetryable;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function getHttpStatus(code) {
|
|
56
|
+
switch (code) {
|
|
57
|
+
case ErrorCode.BusyCapacity:
|
|
58
|
+
return 429;
|
|
59
|
+
case ErrorCode.PaymentRequired:
|
|
60
|
+
case ErrorCode.PaymentAccountInsufficient:
|
|
61
|
+
case ErrorCode.PaymentMethodNotReady:
|
|
62
|
+
case ErrorCode.PaymentAuthorizationRequired:
|
|
63
|
+
case ErrorCode.PaymentCancelled:
|
|
64
|
+
case ErrorCode.PaymentTimeout:
|
|
65
|
+
case ErrorCode.PaymentProviderFailed:
|
|
66
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
67
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
68
|
+
case ErrorCode.InsufficientFunds:
|
|
69
|
+
return 402;
|
|
70
|
+
case ErrorCode.InvalidPurchaseRequest:
|
|
71
|
+
return 400;
|
|
72
|
+
case ErrorCode.InvalidOrExpiredToken:
|
|
73
|
+
case ErrorCode.TokenClassRevoked:
|
|
74
|
+
return 401;
|
|
75
|
+
case ErrorCode.RequestIdRequired:
|
|
76
|
+
case ErrorCode.StreamingNotSupported:
|
|
77
|
+
case ErrorCode.ModelNotAvailable:
|
|
78
|
+
case ErrorCode.UpstreamNotConfigured:
|
|
79
|
+
case ErrorCode.ProtocolNotAvailable:
|
|
80
|
+
case ErrorCode.ContractVersionDrift:
|
|
81
|
+
return 400;
|
|
82
|
+
case ErrorCode.IdempotencyConflict:
|
|
83
|
+
return 409;
|
|
84
|
+
case ErrorCode.PurchaseNotFound:
|
|
85
|
+
case ErrorCode.RequestNotFound:
|
|
86
|
+
return 404;
|
|
87
|
+
case ErrorCode.LedgerFailClosed:
|
|
88
|
+
return 500;
|
|
89
|
+
case ErrorCode.UpstreamTimeout:
|
|
90
|
+
return 504;
|
|
91
|
+
default:
|
|
92
|
+
return 500;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function getDefaultMessage(code) {
|
|
96
|
+
switch (code) {
|
|
97
|
+
case ErrorCode.BusyCapacity:
|
|
98
|
+
return "Seller capacity is currently exhausted.";
|
|
99
|
+
case ErrorCode.PaymentRequired:
|
|
100
|
+
return "Additional credit is required before this operation can proceed.";
|
|
101
|
+
case ErrorCode.PaymentAccountInsufficient:
|
|
102
|
+
return "Payment account balance is insufficient. Add funds to the selected payment method and retry.";
|
|
103
|
+
case ErrorCode.PaymentMethodNotReady:
|
|
104
|
+
return "Selected payment method is not ready. Configure or enable a payment method and retry.";
|
|
105
|
+
case ErrorCode.PaymentAuthorizationRequired:
|
|
106
|
+
return "Selected payment method requires authorization before payment can proceed.";
|
|
107
|
+
case ErrorCode.PaymentCancelled:
|
|
108
|
+
return "Payment was cancelled before completion.";
|
|
109
|
+
case ErrorCode.PaymentTimeout:
|
|
110
|
+
return "Payment did not complete before the timeout.";
|
|
111
|
+
case ErrorCode.PaymentProviderFailed:
|
|
112
|
+
return "Selected payment provider could not complete the payment.";
|
|
113
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
114
|
+
return "Clawtip payment credential is invalid.";
|
|
115
|
+
case ErrorCode.InvalidPurchaseRequest:
|
|
116
|
+
return "Purchase request is invalid.";
|
|
117
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
118
|
+
return "Clawtip payment does not match the expected order.";
|
|
119
|
+
case ErrorCode.InsufficientFunds:
|
|
120
|
+
return "Purchase balance is exhausted.";
|
|
121
|
+
case ErrorCode.InvalidOrExpiredToken:
|
|
122
|
+
return "Bearer token is invalid or expired.";
|
|
123
|
+
case ErrorCode.TokenClassRevoked:
|
|
124
|
+
return "Token class has been revoked by seller operator.";
|
|
125
|
+
case ErrorCode.RequestIdRequired:
|
|
126
|
+
return "Chat requests must include requestId.";
|
|
127
|
+
case ErrorCode.StreamingNotSupported:
|
|
128
|
+
return "Streaming responses are not supported in P0.";
|
|
129
|
+
case ErrorCode.IdempotencyConflict:
|
|
130
|
+
return "Idempotency key was reused with a different request payload.";
|
|
131
|
+
case ErrorCode.PurchaseNotFound:
|
|
132
|
+
return "Purchase could not be found.";
|
|
133
|
+
case ErrorCode.RequestNotFound:
|
|
134
|
+
return "Request could not be found.";
|
|
135
|
+
case ErrorCode.ModelNotAvailable:
|
|
136
|
+
return "Requested model is not available on this seller.";
|
|
137
|
+
case ErrorCode.UpstreamNotConfigured:
|
|
138
|
+
return "Seller upstream is not configured. Ask the seller operator to configure upstreams.";
|
|
139
|
+
case ErrorCode.ProtocolNotAvailable:
|
|
140
|
+
return "Requested protocol is not available for this seller upstream.";
|
|
141
|
+
case ErrorCode.LedgerFailClosed:
|
|
142
|
+
return "Ledger entered fail-closed mode.";
|
|
143
|
+
case ErrorCode.UpstreamTimeout:
|
|
144
|
+
return "Upstream model request timed out.";
|
|
145
|
+
case ErrorCode.ContractVersionDrift:
|
|
146
|
+
return "Contract version drift detected.";
|
|
147
|
+
default:
|
|
148
|
+
return "An unknown error occurred.";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function createErrorEnvelope(code, message, retryAfterSeconds) {
|
|
152
|
+
return {
|
|
153
|
+
error: {
|
|
154
|
+
code,
|
|
155
|
+
retryClass: getRetryClass(code),
|
|
156
|
+
message: message || getDefaultMessage(code),
|
|
157
|
+
httpStatus: getHttpStatus(code),
|
|
158
|
+
...(retryAfterSeconds !== undefined ? { retryAfterSeconds } : {})
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./errors.js"), exports);
|
|
18
|
+
__exportStar(require("./crypto.js"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,8CAA4B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tokenbuddy/contracts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared contract models and crypto functions",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"uuid": "^9.0.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/uuid": "^9.0.8"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as crypto from "crypto";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decode a base64 SM4 key to a 16-byte Buffer.
|
|
6
|
+
* Throws an error if the decoded key is not exactly 16 bytes.
|
|
7
|
+
*/
|
|
8
|
+
function decodeSm4Key(sm4KeyBase64: string): Buffer {
|
|
9
|
+
const key = Buffer.from(sm4KeyBase64, "base64");
|
|
10
|
+
if (key.length !== 16) {
|
|
11
|
+
throw new Error(`SM4 key must decode to 16 bytes, got ${key.length}`);
|
|
12
|
+
}
|
|
13
|
+
return key;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Encrypt a plaintext string using SM4-ECB with PKCS7 padding.
|
|
18
|
+
* Returns the ciphertext encoded as a base64 string.
|
|
19
|
+
*/
|
|
20
|
+
export function encryptSm4EcbPkcs7Base64(plaintext: string, sm4KeyBase64: string): string {
|
|
21
|
+
try {
|
|
22
|
+
const key = decodeSm4Key(sm4KeyBase64);
|
|
23
|
+
// Node.js crypto uses 'sm4-ecb' which defaults to PKCS7 padding for ECB
|
|
24
|
+
const cipher = crypto.createCipheriv("sm4-ecb", key, null);
|
|
25
|
+
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
26
|
+
encrypted += cipher.final("base64");
|
|
27
|
+
return encrypted;
|
|
28
|
+
} catch (error: any) {
|
|
29
|
+
throw new Error(`SM4 encryption failed: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decrypt a base64 ciphertext string using SM4-ECB with PKCS7 padding.
|
|
35
|
+
* Returns the decrypted plaintext string.
|
|
36
|
+
*/
|
|
37
|
+
export function decryptSm4EcbPkcs7Base64(encryptedBase64: string, sm4KeyBase64: string): string {
|
|
38
|
+
try {
|
|
39
|
+
const key = decodeSm4Key(sm4KeyBase64);
|
|
40
|
+
const decipher = crypto.createDecipheriv("sm4-ecb", key, null);
|
|
41
|
+
let decrypted = decipher.update(encryptedBase64, "base64", "utf8");
|
|
42
|
+
decrypted += decipher.final("utf8");
|
|
43
|
+
return decrypted;
|
|
44
|
+
} catch (error: any) {
|
|
45
|
+
throw new Error(`SM4 decryption failed: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute md5 hex digest of a string (indicator).
|
|
51
|
+
*/
|
|
52
|
+
export function computeIndicator(slug: string): string {
|
|
53
|
+
return crypto.createHash("md5").update(slug, "utf8").digest("hex");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a 32-character lowercase hex string as orderNo (UUID v4 without dashes).
|
|
58
|
+
*/
|
|
59
|
+
export function generateOrderNo(): string {
|
|
60
|
+
return uuidv4().replace(/-/g, "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Encrypt Clawtip order data: {"orderNo":..., "amount":..., "payTo":...}
|
|
65
|
+
* Returns the base64 encrypted data.
|
|
66
|
+
*/
|
|
67
|
+
export function encryptClawtipOrderData(
|
|
68
|
+
orderNo: string,
|
|
69
|
+
amount: number,
|
|
70
|
+
payTo: string,
|
|
71
|
+
sm4KeyBase64: string
|
|
72
|
+
): string {
|
|
73
|
+
const plaintext = JSON.stringify({
|
|
74
|
+
orderNo,
|
|
75
|
+
amount,
|
|
76
|
+
payTo
|
|
77
|
+
});
|
|
78
|
+
return encryptSm4EcbPkcs7Base64(plaintext, sm4KeyBase64);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ClawtipPaymentParams {
|
|
82
|
+
orderNo: string;
|
|
83
|
+
amountFen: number;
|
|
84
|
+
payTo: string;
|
|
85
|
+
encryptedData: string;
|
|
86
|
+
indicator: string;
|
|
87
|
+
slug: string;
|
|
88
|
+
skillId: string;
|
|
89
|
+
description: string;
|
|
90
|
+
resourceUrl: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ClawtipPaymentParamsInput {
|
|
94
|
+
payTo: string;
|
|
95
|
+
sm4KeyBase64: string;
|
|
96
|
+
skillSlug: string;
|
|
97
|
+
skillId: string;
|
|
98
|
+
description: string;
|
|
99
|
+
resourceUrl: string;
|
|
100
|
+
amountFen: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildClawtipPaymentParams(input: ClawtipPaymentParamsInput): ClawtipPaymentParams {
|
|
104
|
+
if (!Number.isInteger(input.amountFen) || input.amountFen < 1) {
|
|
105
|
+
throw new Error("amount_fen must be greater than zero");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const orderNo = generateOrderNo();
|
|
109
|
+
return {
|
|
110
|
+
orderNo,
|
|
111
|
+
amountFen: input.amountFen,
|
|
112
|
+
payTo: input.payTo,
|
|
113
|
+
encryptedData: encryptClawtipOrderData(orderNo, input.amountFen, input.payTo, input.sm4KeyBase64),
|
|
114
|
+
indicator: computeIndicator(input.skillSlug),
|
|
115
|
+
slug: input.skillSlug,
|
|
116
|
+
skillId: input.skillId,
|
|
117
|
+
description: input.description,
|
|
118
|
+
resourceUrl: input.resourceUrl
|
|
119
|
+
};
|
|
120
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export enum RetryClass {
|
|
2
|
+
FailClosed = "fail_closed",
|
|
3
|
+
RetryableWithBackoff = "retryable_with_backoff",
|
|
4
|
+
NotRetryable = "not_retryable"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum ErrorCode {
|
|
8
|
+
BusyCapacity = "busy_capacity",
|
|
9
|
+
PaymentRequired = "payment_required",
|
|
10
|
+
PaymentAccountInsufficient = "payment_account_insufficient",
|
|
11
|
+
PaymentMethodNotReady = "payment_method_not_ready",
|
|
12
|
+
PaymentAuthorizationRequired = "payment_authorization_required",
|
|
13
|
+
PaymentCancelled = "payment_cancelled",
|
|
14
|
+
PaymentTimeout = "payment_timeout",
|
|
15
|
+
PaymentProviderFailed = "payment_provider_failed",
|
|
16
|
+
InvalidPurchaseRequest = "invalid_purchase_request",
|
|
17
|
+
ClawtipCredentialInvalid = "clawtip_credential_invalid",
|
|
18
|
+
ClawtipOrderMismatch = "clawtip_order_mismatch",
|
|
19
|
+
InsufficientFunds = "insufficient_funds",
|
|
20
|
+
InvalidOrExpiredToken = "invalid_or_expired_token",
|
|
21
|
+
TokenClassRevoked = "token_class_revoked",
|
|
22
|
+
RequestIdRequired = "request_id_required",
|
|
23
|
+
StreamingNotSupported = "streaming_not_supported",
|
|
24
|
+
IdempotencyConflict = "idempotency_conflict",
|
|
25
|
+
PurchaseNotFound = "purchase_not_found",
|
|
26
|
+
RequestNotFound = "request_not_found",
|
|
27
|
+
ModelNotAvailable = "model_not_available",
|
|
28
|
+
UpstreamNotConfigured = "upstream_not_configured",
|
|
29
|
+
ProtocolNotAvailable = "protocol_not_available",
|
|
30
|
+
LedgerFailClosed = "ledger_fail_closed",
|
|
31
|
+
UpstreamTimeout = "upstream_timeout",
|
|
32
|
+
ContractVersionDrift = "contract_version_drift"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getRetryClass(code: ErrorCode): RetryClass {
|
|
36
|
+
switch (code) {
|
|
37
|
+
case ErrorCode.BusyCapacity:
|
|
38
|
+
case ErrorCode.UpstreamTimeout:
|
|
39
|
+
return RetryClass.RetryableWithBackoff;
|
|
40
|
+
case ErrorCode.LedgerFailClosed:
|
|
41
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
42
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
43
|
+
return RetryClass.FailClosed;
|
|
44
|
+
default:
|
|
45
|
+
return RetryClass.NotRetryable;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getHttpStatus(code: ErrorCode): number {
|
|
50
|
+
switch (code) {
|
|
51
|
+
case ErrorCode.BusyCapacity:
|
|
52
|
+
return 429;
|
|
53
|
+
case ErrorCode.PaymentRequired:
|
|
54
|
+
case ErrorCode.PaymentAccountInsufficient:
|
|
55
|
+
case ErrorCode.PaymentMethodNotReady:
|
|
56
|
+
case ErrorCode.PaymentAuthorizationRequired:
|
|
57
|
+
case ErrorCode.PaymentCancelled:
|
|
58
|
+
case ErrorCode.PaymentTimeout:
|
|
59
|
+
case ErrorCode.PaymentProviderFailed:
|
|
60
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
61
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
62
|
+
case ErrorCode.InsufficientFunds:
|
|
63
|
+
return 402;
|
|
64
|
+
case ErrorCode.InvalidPurchaseRequest:
|
|
65
|
+
return 400;
|
|
66
|
+
case ErrorCode.InvalidOrExpiredToken:
|
|
67
|
+
case ErrorCode.TokenClassRevoked:
|
|
68
|
+
return 401;
|
|
69
|
+
case ErrorCode.RequestIdRequired:
|
|
70
|
+
case ErrorCode.StreamingNotSupported:
|
|
71
|
+
case ErrorCode.ModelNotAvailable:
|
|
72
|
+
case ErrorCode.UpstreamNotConfigured:
|
|
73
|
+
case ErrorCode.ProtocolNotAvailable:
|
|
74
|
+
case ErrorCode.ContractVersionDrift:
|
|
75
|
+
return 400;
|
|
76
|
+
case ErrorCode.IdempotencyConflict:
|
|
77
|
+
return 409;
|
|
78
|
+
case ErrorCode.PurchaseNotFound:
|
|
79
|
+
case ErrorCode.RequestNotFound:
|
|
80
|
+
return 404;
|
|
81
|
+
case ErrorCode.LedgerFailClosed:
|
|
82
|
+
return 500;
|
|
83
|
+
case ErrorCode.UpstreamTimeout:
|
|
84
|
+
return 504;
|
|
85
|
+
default:
|
|
86
|
+
return 500;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getDefaultMessage(code: ErrorCode): string {
|
|
91
|
+
switch (code) {
|
|
92
|
+
case ErrorCode.BusyCapacity:
|
|
93
|
+
return "Seller capacity is currently exhausted.";
|
|
94
|
+
case ErrorCode.PaymentRequired:
|
|
95
|
+
return "Additional credit is required before this operation can proceed.";
|
|
96
|
+
case ErrorCode.PaymentAccountInsufficient:
|
|
97
|
+
return "Payment account balance is insufficient. Add funds to the selected payment method and retry.";
|
|
98
|
+
case ErrorCode.PaymentMethodNotReady:
|
|
99
|
+
return "Selected payment method is not ready. Configure or enable a payment method and retry.";
|
|
100
|
+
case ErrorCode.PaymentAuthorizationRequired:
|
|
101
|
+
return "Selected payment method requires authorization before payment can proceed.";
|
|
102
|
+
case ErrorCode.PaymentCancelled:
|
|
103
|
+
return "Payment was cancelled before completion.";
|
|
104
|
+
case ErrorCode.PaymentTimeout:
|
|
105
|
+
return "Payment did not complete before the timeout.";
|
|
106
|
+
case ErrorCode.PaymentProviderFailed:
|
|
107
|
+
return "Selected payment provider could not complete the payment.";
|
|
108
|
+
case ErrorCode.ClawtipCredentialInvalid:
|
|
109
|
+
return "Clawtip payment credential is invalid.";
|
|
110
|
+
case ErrorCode.InvalidPurchaseRequest:
|
|
111
|
+
return "Purchase request is invalid.";
|
|
112
|
+
case ErrorCode.ClawtipOrderMismatch:
|
|
113
|
+
return "Clawtip payment does not match the expected order.";
|
|
114
|
+
case ErrorCode.InsufficientFunds:
|
|
115
|
+
return "Purchase balance is exhausted.";
|
|
116
|
+
case ErrorCode.InvalidOrExpiredToken:
|
|
117
|
+
return "Bearer token is invalid or expired.";
|
|
118
|
+
case ErrorCode.TokenClassRevoked:
|
|
119
|
+
return "Token class has been revoked by seller operator.";
|
|
120
|
+
case ErrorCode.RequestIdRequired:
|
|
121
|
+
return "Chat requests must include requestId.";
|
|
122
|
+
case ErrorCode.StreamingNotSupported:
|
|
123
|
+
return "Streaming responses are not supported in P0.";
|
|
124
|
+
case ErrorCode.IdempotencyConflict:
|
|
125
|
+
return "Idempotency key was reused with a different request payload.";
|
|
126
|
+
case ErrorCode.PurchaseNotFound:
|
|
127
|
+
return "Purchase could not be found.";
|
|
128
|
+
case ErrorCode.RequestNotFound:
|
|
129
|
+
return "Request could not be found.";
|
|
130
|
+
case ErrorCode.ModelNotAvailable:
|
|
131
|
+
return "Requested model is not available on this seller.";
|
|
132
|
+
case ErrorCode.UpstreamNotConfigured:
|
|
133
|
+
return "Seller upstream is not configured. Ask the seller operator to configure upstreams.";
|
|
134
|
+
case ErrorCode.ProtocolNotAvailable:
|
|
135
|
+
return "Requested protocol is not available for this seller upstream.";
|
|
136
|
+
case ErrorCode.LedgerFailClosed:
|
|
137
|
+
return "Ledger entered fail-closed mode.";
|
|
138
|
+
case ErrorCode.UpstreamTimeout:
|
|
139
|
+
return "Upstream model request timed out.";
|
|
140
|
+
case ErrorCode.ContractVersionDrift:
|
|
141
|
+
return "Contract version drift detected.";
|
|
142
|
+
default:
|
|
143
|
+
return "An unknown error occurred.";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ErrorBody {
|
|
148
|
+
code: ErrorCode;
|
|
149
|
+
retryClass: RetryClass;
|
|
150
|
+
message: string;
|
|
151
|
+
httpStatus: number;
|
|
152
|
+
retryAfterSeconds?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ErrorEnvelope {
|
|
156
|
+
error: ErrorBody;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createErrorEnvelope(
|
|
160
|
+
code: ErrorCode,
|
|
161
|
+
message?: string,
|
|
162
|
+
retryAfterSeconds?: number
|
|
163
|
+
): ErrorEnvelope {
|
|
164
|
+
return {
|
|
165
|
+
error: {
|
|
166
|
+
code,
|
|
167
|
+
retryClass: getRetryClass(code),
|
|
168
|
+
message: message || getDefaultMessage(code),
|
|
169
|
+
httpStatus: getHttpStatus(code),
|
|
170
|
+
...(retryAfterSeconds !== undefined ? { retryAfterSeconds } : {})
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encryptSm4EcbPkcs7Base64,
|
|
3
|
+
decryptSm4EcbPkcs7Base64,
|
|
4
|
+
computeIndicator,
|
|
5
|
+
generateOrderNo,
|
|
6
|
+
encryptClawtipOrderData,
|
|
7
|
+
buildClawtipPaymentParams
|
|
8
|
+
} from "../src/crypto.js";
|
|
9
|
+
|
|
10
|
+
// base64("0123456789ABCDEF") — exactly 16 bytes
|
|
11
|
+
const KEY = "MDEyMzQ1Njc4OUFCQ0RFRg==";
|
|
12
|
+
|
|
13
|
+
describe("SM4 Cryptography and Helpers Tests", () => {
|
|
14
|
+
test("sm4 ecb pkcs7 roundtrip", () => {
|
|
15
|
+
const plaintext = '{"orderNo":"abc123","amount":100,"payTo":"payto-test"}';
|
|
16
|
+
const encrypted = encryptSm4EcbPkcs7Base64(plaintext, KEY);
|
|
17
|
+
const decrypted = decryptSm4EcbPkcs7Base64(encrypted, KEY);
|
|
18
|
+
|
|
19
|
+
expect(encrypted).not.toBe(plaintext);
|
|
20
|
+
expect(decrypted).toBe(plaintext);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("sm4 invalid base64 key throws error", () => {
|
|
24
|
+
expect(() => {
|
|
25
|
+
encryptSm4EcbPkcs7Base64("hello", "!!!not-base64!!!");
|
|
26
|
+
}).toThrow();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("sm4 short key throws error", () => {
|
|
30
|
+
// base64("short") = 8 bytes
|
|
31
|
+
expect(() => {
|
|
32
|
+
encryptSm4EcbPkcs7Base64("hello", "c2hvcnQ=");
|
|
33
|
+
}).toThrow("16 bytes");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("compute_indicator is md5 hex of slug", () => {
|
|
37
|
+
// echo -n "test" | md5sum => 098f6bcd4621d373cade4e832627b4f6
|
|
38
|
+
expect(computeIndicator("test")).toBe("098f6bcd4621d373cade4e832627b4f6");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("generate_order_no is 32 lowercase hex chars", () => {
|
|
42
|
+
const no = generateOrderNo();
|
|
43
|
+
expect(no.length).toBe(32);
|
|
44
|
+
expect(/^[0-9a-f]{32}$/.test(no)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("encryptClawtipOrderData produces valid json after decrypt", () => {
|
|
48
|
+
const enc = encryptClawtipOrderData("ord001", 500, "pay-to-addr", KEY);
|
|
49
|
+
const dec = decryptSm4EcbPkcs7Base64(enc, KEY);
|
|
50
|
+
const val = JSON.parse(dec);
|
|
51
|
+
expect(val.orderNo).toBe("ord001");
|
|
52
|
+
expect(val.amount).toBe(500);
|
|
53
|
+
expect(val.payTo).toBe("pay-to-addr");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("buildClawtipPaymentParams produces the shared wallet bootstrap shape", () => {
|
|
57
|
+
const params = buildClawtipPaymentParams({
|
|
58
|
+
payTo: "pay-to-addr",
|
|
59
|
+
sm4KeyBase64: KEY,
|
|
60
|
+
skillSlug: "tb-wallet-bootstrap",
|
|
61
|
+
skillId: "si-tb-wallet-bootstrap",
|
|
62
|
+
description: "Activate",
|
|
63
|
+
resourceUrl: "https://example.com",
|
|
64
|
+
amountFen: 1
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(params.amountFen).toBe(1);
|
|
68
|
+
expect(params.payTo).toBe("pay-to-addr");
|
|
69
|
+
expect(params.slug).toBe("tb-wallet-bootstrap");
|
|
70
|
+
expect(params.skillId).toBe("si-tb-wallet-bootstrap");
|
|
71
|
+
expect(params.orderNo).toMatch(/^[0-9a-f]{32}$/);
|
|
72
|
+
expect(params.indicator).toBe(computeIndicator("tb-wallet-bootstrap"));
|
|
73
|
+
|
|
74
|
+
const decoded = JSON.parse(decryptSm4EcbPkcs7Base64(params.encryptedData, KEY));
|
|
75
|
+
expect(decoded.orderNo).toBe(params.orderNo);
|
|
76
|
+
expect(decoded.amount).toBe(1);
|
|
77
|
+
expect(decoded.payTo).toBe("pay-to-addr");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("buildClawtipPaymentParams rejects non-positive amountFen", () => {
|
|
81
|
+
expect(() => buildClawtipPaymentParams({
|
|
82
|
+
payTo: "pay-to-addr",
|
|
83
|
+
sm4KeyBase64: KEY,
|
|
84
|
+
skillSlug: "tb-wallet-bootstrap",
|
|
85
|
+
skillId: "si-tb-wallet-bootstrap",
|
|
86
|
+
description: "Activate",
|
|
87
|
+
resourceUrl: "https://example.com",
|
|
88
|
+
amountFen: 0
|
|
89
|
+
})).toThrow("amount_fen");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorCode,
|
|
3
|
+
RetryClass,
|
|
4
|
+
getRetryClass,
|
|
5
|
+
getHttpStatus,
|
|
6
|
+
getDefaultMessage,
|
|
7
|
+
createErrorEnvelope
|
|
8
|
+
} from "../src/errors.js";
|
|
9
|
+
|
|
10
|
+
describe("ErrorCode and RetryClass Mapping Tests", () => {
|
|
11
|
+
test("retry class maps correctly", () => {
|
|
12
|
+
expect(getRetryClass(ErrorCode.BusyCapacity)).toBe(RetryClass.RetryableWithBackoff);
|
|
13
|
+
expect(getRetryClass(ErrorCode.UpstreamTimeout)).toBe(RetryClass.RetryableWithBackoff);
|
|
14
|
+
expect(getRetryClass(ErrorCode.LedgerFailClosed)).toBe(RetryClass.FailClosed);
|
|
15
|
+
expect(getRetryClass(ErrorCode.ClawtipCredentialInvalid)).toBe(RetryClass.FailClosed);
|
|
16
|
+
expect(getRetryClass(ErrorCode.PaymentRequired)).toBe(RetryClass.NotRetryable);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("http status maps correctly", () => {
|
|
20
|
+
expect(getHttpStatus(ErrorCode.BusyCapacity)).toBe(429);
|
|
21
|
+
expect(getHttpStatus(ErrorCode.PaymentRequired)).toBe(402);
|
|
22
|
+
expect(getHttpStatus(ErrorCode.InvalidOrExpiredToken)).toBe(401);
|
|
23
|
+
expect(getHttpStatus(ErrorCode.LedgerFailClosed)).toBe(500);
|
|
24
|
+
expect(getHttpStatus(ErrorCode.UpstreamTimeout)).toBe(504);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("createErrorEnvelope produces standard wire structure", () => {
|
|
28
|
+
const envelope = createErrorEnvelope(ErrorCode.BusyCapacity);
|
|
29
|
+
expect(envelope.error.code).toBe(ErrorCode.BusyCapacity);
|
|
30
|
+
expect(envelope.error.retryClass).toBe(RetryClass.RetryableWithBackoff);
|
|
31
|
+
expect(envelope.error.httpStatus).toBe(429);
|
|
32
|
+
expect(envelope.error.message).toContain("capacity");
|
|
33
|
+
});
|
|
34
|
+
});
|