@envarai/envarpay 0.2.0-alpha.2 → 0.2.0-alpha.20
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/README.md +18 -56
- package/dist/commerce/agent-setup.d.ts +19 -0
- package/dist/commerce/agent-setup.js +210 -0
- package/dist/commerce/alerts.d.ts +27 -0
- package/dist/commerce/alerts.js +71 -0
- package/dist/commerce/card.js +3 -3
- package/dist/commerce/cli.js +91 -119
- package/dist/commerce/config.d.ts +3 -2
- package/dist/commerce/config.js +19 -77
- package/dist/commerce/configure.d.ts +2 -0
- package/dist/commerce/configure.js +78 -0
- package/dist/commerce/discovery.d.ts +20 -0
- package/dist/commerce/discovery.js +162 -0
- package/dist/commerce/envar-balance.d.ts +25 -0
- package/dist/commerce/envar-balance.js +65 -0
- package/dist/commerce/envar-runtime.d.ts +3 -1
- package/dist/commerce/envar-runtime.js +28 -1
- package/dist/commerce/envar.d.ts +13 -31
- package/dist/commerce/envar.js +35 -83
- package/dist/commerce/external-agent.d.ts +26 -0
- package/dist/commerce/external-agent.js +129 -0
- package/dist/commerce/external-events.d.ts +76 -0
- package/dist/commerce/external-events.js +273 -0
- package/dist/commerce/external-mcp.d.ts +10 -0
- package/dist/commerce/external-mcp.js +199 -0
- package/dist/commerce/external-oauth.d.ts +12 -0
- package/dist/commerce/external-oauth.js +230 -0
- package/dist/commerce/external-runtime.d.ts +25 -0
- package/dist/commerce/external-runtime.js +244 -0
- package/dist/commerce/index.d.ts +1 -0
- package/dist/commerce/index.js +1 -0
- package/dist/commerce/managed.d.ts +3 -0
- package/dist/commerce/managed.js +143 -0
- package/dist/commerce/native-inventory.d.ts +9 -0
- package/dist/commerce/native-inventory.js +75 -0
- package/dist/commerce/native-skills.d.ts +32 -0
- package/dist/commerce/native-skills.js +174 -0
- package/dist/commerce/openclaw-setup.d.ts +28 -0
- package/dist/commerce/openclaw-setup.js +43 -0
- package/dist/commerce/operations.d.ts +8 -3
- package/dist/commerce/operations.js +21 -18
- package/dist/commerce/pricing.d.ts +0 -1
- package/dist/commerce/pricing.js +3 -5
- package/dist/commerce/runtime.d.ts +4 -2
- package/dist/commerce/runtime.js +4 -2
- package/dist/commerce/server.d.ts +3 -2
- package/dist/commerce/server.js +26 -10
- package/dist/commerce/setup.d.ts +25 -0
- package/dist/commerce/setup.js +379 -0
- package/dist/commerce/skill-gate.d.ts +54 -0
- package/dist/commerce/skill-gate.js +153 -0
- package/dist/commerce/store.d.ts +11 -1
- package/dist/commerce/store.js +36 -2
- package/dist/commerce/types.d.ts +6 -57
- package/examples/envar-integration-policy.json +1 -9
- package/examples/hermes-setup-probe.py +109 -0
- package/examples/hermes-skill-inventory.py +108 -0
- package/examples/native-skill-runner.py +197 -0
- package/examples/seller.json +143 -26
- package/package.json +5 -16
- package/schemas/envarpay.config.schema.json +28 -87
- package/dist/commerce/buyer-store.d.ts +0 -101
- package/dist/commerce/buyer-store.js +0 -168
- package/dist/commerce/buyer.d.ts +0 -132
- package/dist/commerce/buyer.js +0 -755
- package/dist/commerce/chain.d.ts +0 -24
- package/dist/commerce/chain.js +0 -146
- package/dist/commerce/client-runtime.d.ts +0 -8
- package/dist/commerce/client-runtime.js +0 -7
- package/dist/commerce/management.d.ts +0 -14
- package/dist/commerce/management.js +0 -138
- package/dist/commerce/mpp-client.d.ts +0 -59
- package/dist/commerce/mpp-client.js +0 -24
- package/dist/commerce/mpp-runtime.d.ts +0 -3
- package/dist/commerce/mpp-runtime.js +0 -3
- package/dist/commerce/mpp.d.ts +0 -53
- package/dist/commerce/mpp.js +0 -163
- package/dist/commerce/peer-proxy.d.ts +0 -44
- package/dist/commerce/peer-proxy.js +0 -673
- package/dist/commerce/stripe-provider.d.ts +0 -9
- package/dist/commerce/stripe-provider.js +0 -97
- package/dist/commerce/x402.d.ts +0 -37
- package/dist/commerce/x402.js +0 -174
- package/examples/buyer-policy.json +0 -18
- package/schemas/buyer-policy.schema.json +0 -253
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { CommerceError } from './types.js';
|
|
2
|
-
/** Stripe REST transport used by the official mppx Stripe method. No SPT creation. */
|
|
3
|
-
export function stripeProvider(config, fetchImpl = fetch) {
|
|
4
|
-
if (!/^acct_[A-Za-z0-9]+$/.test(config.accountId) || !/^profile_[A-Za-z0-9]+$/.test(config.merchantProfile) ||
|
|
5
|
-
!config.secretKey.startsWith(config.mode === 'live' ? 'sk_live_' : 'sk_test_')) {
|
|
6
|
-
throw new CommerceError('stripe_configuration', 'Explicit matching Stripe account, profile and test/live credentials are required');
|
|
7
|
-
}
|
|
8
|
-
const call = async (path, method = 'GET', params, options) => {
|
|
9
|
-
const body = new URLSearchParams();
|
|
10
|
-
const add = (key, value) => {
|
|
11
|
-
if (value === undefined || value === null)
|
|
12
|
-
return;
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
value.forEach((v, i) => add(`${key}[${i}]`, v));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
if (typeof value === 'object') {
|
|
18
|
-
for (const [k, v] of Object.entries(value))
|
|
19
|
-
add(key ? `${key}[${k}]` : k, v);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (!['boolean', 'number', 'string'].includes(typeof value))
|
|
23
|
-
throw new CommerceError('stripe_request', 'Unsupported Stripe value');
|
|
24
|
-
body.set(key, String(value));
|
|
25
|
-
};
|
|
26
|
-
for (const [key, value] of Object.entries(params ?? {}))
|
|
27
|
-
add(key, value);
|
|
28
|
-
const headers = new Headers({ Authorization: `Bearer ${config.secretKey}` });
|
|
29
|
-
if (options?.apiVersion)
|
|
30
|
-
headers.set('Stripe-Version', options.apiVersion);
|
|
31
|
-
if (options?.idempotencyKey)
|
|
32
|
-
headers.set('Idempotency-Key', options.idempotencyKey);
|
|
33
|
-
if (method !== 'GET')
|
|
34
|
-
headers.set('Content-Type', 'application/x-www-form-urlencoded');
|
|
35
|
-
const response = await fetchImpl(`https://api.stripe.com${path}${method === 'GET' && body.size ? '?' + body.toString() : ''}`, {
|
|
36
|
-
method, headers, ...(method === 'GET' ? {} : { body }), redirect: 'error', signal: AbortSignal.timeout(20000),
|
|
37
|
-
});
|
|
38
|
-
const reader = response.body?.getReader();
|
|
39
|
-
let size = 0;
|
|
40
|
-
const chunks = [];
|
|
41
|
-
if (reader)
|
|
42
|
-
for (;;) {
|
|
43
|
-
const { value, done } = await reader.read();
|
|
44
|
-
if (done)
|
|
45
|
-
break;
|
|
46
|
-
size += value.byteLength;
|
|
47
|
-
if (size > 1024 * 1024) {
|
|
48
|
-
await reader.cancel();
|
|
49
|
-
throw new CommerceError('stripe_response_size', 'Stripe response exceeds limit');
|
|
50
|
-
}
|
|
51
|
-
chunks.push(value);
|
|
52
|
-
}
|
|
53
|
-
let result;
|
|
54
|
-
try {
|
|
55
|
-
result = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
throw new CommerceError('stripe_response', 'Stripe returned invalid JSON');
|
|
59
|
-
}
|
|
60
|
-
// Do not surface provider error text that might echo SPT or customer details.
|
|
61
|
-
if (!response.ok)
|
|
62
|
-
throw new CommerceError(response.status === 429 ? 'stripe_rate_limit' : 'stripe_request_failed', `Stripe request failed (HTTP ${response.status})`);
|
|
63
|
-
return { ...result, lastResponse: { headers: { 'idempotent-replayed': response.headers.get('idempotent-replayed') ?? 'false' } } };
|
|
64
|
-
};
|
|
65
|
-
const validateIntent = (value) => {
|
|
66
|
-
if (typeof value.id !== 'string' || !/^pi_[A-Za-z0-9]+$/.test(value.id) || typeof value.status !== 'string' ||
|
|
67
|
-
!Number.isSafeInteger(value.amount) || typeof value.livemode !== 'boolean' || !value.metadata || typeof value.metadata !== 'object') {
|
|
68
|
-
throw new CommerceError('stripe_response', 'Stripe returned an invalid PaymentIntent');
|
|
69
|
-
}
|
|
70
|
-
return value;
|
|
71
|
-
};
|
|
72
|
-
return {
|
|
73
|
-
accountId: config.accountId, merchantProfile: config.merchantProfile, mode: config.mode,
|
|
74
|
-
async assertReady() {
|
|
75
|
-
const account = await call('/v1/account');
|
|
76
|
-
if (account.id !== config.accountId)
|
|
77
|
-
throw new CommerceError('stripe_account_mismatch', 'Stripe key belongs to another account');
|
|
78
|
-
if (config.mode === 'live' && account.charges_enabled !== true)
|
|
79
|
-
throw new CommerceError('stripe_charges_disabled', 'Live merchant is not enabled for charges');
|
|
80
|
-
},
|
|
81
|
-
async create(params, options) { return validateIntent(await call('/v1/payment_intents', 'POST', params, options)); },
|
|
82
|
-
async retrieve(id) {
|
|
83
|
-
if (!/^pi_[A-Za-z0-9]+$/.test(id))
|
|
84
|
-
throw new CommerceError('stripe_reference', 'Invalid original PaymentIntent');
|
|
85
|
-
return validateIntent(await call(`/v1/payment_intents/${id}`));
|
|
86
|
-
},
|
|
87
|
-
async findOriginal(orderId) {
|
|
88
|
-
if (!/^[0-9a-f-]{36}$/.test(orderId))
|
|
89
|
-
throw new CommerceError('order_reference', 'Invalid original order');
|
|
90
|
-
const result = await call('/v1/payment_intents/search', 'GET', { query: `metadata['envarpay_order']:'${orderId}'`, limit: 2 });
|
|
91
|
-
if (!Array.isArray(result.data) || result.has_more || result.data.length > 1)
|
|
92
|
-
throw new CommerceError('ambiguous_payment', 'Multiple intents require original-operation reconciliation');
|
|
93
|
-
// Stripe search may be eventually consistent. No match remains unknown.
|
|
94
|
-
return result.data.length ? validateIntent(result.data[0]) : undefined;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
package/dist/commerce/x402.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { type FacilitatorClient } from '@x402/core/server';
|
|
2
|
-
import type { PaymentPayload, PaymentRequirements, SettleResponse } from '@x402/core/types';
|
|
3
|
-
import { type PaymentProfile } from './types.js';
|
|
4
|
-
import { CommerceStore, type OrderRecord } from './store.js';
|
|
5
|
-
import { CredentialVault } from './vault.js';
|
|
6
|
-
type X402Profile = Extract<PaymentProfile, {
|
|
7
|
-
adapter: 'x402';
|
|
8
|
-
}>;
|
|
9
|
-
export interface X402GateOptions {
|
|
10
|
-
store: CommerceStore;
|
|
11
|
-
vault: CredentialVault;
|
|
12
|
-
payerFor: (caller: string) => string | undefined;
|
|
13
|
-
facilitator?: (profile: X402Profile) => FacilitatorClient;
|
|
14
|
-
verifyReceipt: (receipt: SettleResponse, payload: PaymentPayload, requirements: PaymentRequirements) => Promise<boolean>;
|
|
15
|
-
/** Read chain before broadcast, so a lost facilitator response remains recoverable. */
|
|
16
|
-
checkpoint?: (requirements: PaymentRequirements) => Promise<string>;
|
|
17
|
-
findOriginalReceipt?: (payload: PaymentPayload, requirements: PaymentRequirements, fromBlock: string) => Promise<SettleResponse | undefined>;
|
|
18
|
-
}
|
|
19
|
-
export type GateResult = {
|
|
20
|
-
response: Response;
|
|
21
|
-
} | {
|
|
22
|
-
headers: Record<string, string>;
|
|
23
|
-
};
|
|
24
|
-
/** Official x402 v2 exact settlement, with independent chain evidence before work. */
|
|
25
|
-
export declare class X402Gate {
|
|
26
|
-
private options;
|
|
27
|
-
private servers;
|
|
28
|
-
constructor(options: X402GateOptions);
|
|
29
|
-
check(profiles: readonly X402Profile[]): Promise<void>;
|
|
30
|
-
recover(orderId: string, caller: string): Promise<{
|
|
31
|
-
state: string;
|
|
32
|
-
taskId: string;
|
|
33
|
-
}>;
|
|
34
|
-
private resource;
|
|
35
|
-
handle(request: Request, order: OrderRecord): Promise<GateResult>;
|
|
36
|
-
}
|
|
37
|
-
export {};
|
package/dist/commerce/x402.js
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { findDefaultAsset } from '@x402/evm';
|
|
2
|
-
import { x402ResourceServer, HTTPFacilitatorClient } from '@x402/core/server';
|
|
3
|
-
import { decodePaymentSignatureHeader, encodePaymentRequiredHeader, encodePaymentResponseHeader } from '@x402/core/http';
|
|
4
|
-
import { ExactEvmScheme } from '@x402/evm/exact/server';
|
|
5
|
-
import { PAYMENT_IDENTIFIER, declarePaymentIdentifierExtension, extractPaymentIdentifier, validatePaymentIdentifier, paymentIdentifierResourceServerExtension, } from '@x402/extensions/payment-identifier';
|
|
6
|
-
import { CommerceError } from './types.js';
|
|
7
|
-
import { digest } from './config.js';
|
|
8
|
-
/** Official x402 v2 exact settlement, with independent chain evidence before work. */
|
|
9
|
-
export class X402Gate {
|
|
10
|
-
options;
|
|
11
|
-
servers = new Map();
|
|
12
|
-
constructor(options) {
|
|
13
|
-
this.options = options;
|
|
14
|
-
}
|
|
15
|
-
async check(profiles) {
|
|
16
|
-
for (const profile of profiles) {
|
|
17
|
-
const { facilitator } = await this.resource(profile);
|
|
18
|
-
const supported = await facilitator.getSupported();
|
|
19
|
-
if (!supported.kinds.some(k => k.x402Version === 2 && k.scheme === 'exact' && k.network === profile.network))
|
|
20
|
-
throw new CommerceError('facilitator_network', 'Configured facilitator does not support exact payments on this network');
|
|
21
|
-
if (this.options.checkpoint)
|
|
22
|
-
await this.options.checkpoint({ scheme: 'exact', network: profile.network, asset: profile.asset, payTo: profile.payTo, amount: '1', maxTimeoutSeconds: 300, extra: {} });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async recover(orderId, caller) {
|
|
26
|
-
const order = this.options.store.getOrder(orderId, caller);
|
|
27
|
-
if (!order)
|
|
28
|
-
throw new CommerceError('order_not_found', 'Order not found for this buyer');
|
|
29
|
-
const attempt = this.options.store.paymentForOrder(order.id);
|
|
30
|
-
if (!attempt)
|
|
31
|
-
throw new CommerceError('attempt_not_found', 'No original payment to recover');
|
|
32
|
-
if (attempt.state === 'confirmed')
|
|
33
|
-
return { state: 'confirmed', taskId: order.taskId };
|
|
34
|
-
if (attempt.state === 'rejected')
|
|
35
|
-
return { state: 'rejected', taskId: order.taskId };
|
|
36
|
-
const saved = this.options.vault.get(attempt.credentialRef);
|
|
37
|
-
const required = this.options.store.paymentChallenge(order.id);
|
|
38
|
-
if (saved.quoteId !== order.quote.quoteId || !required?.accepts.some(r => digest(r) === digest(saved.requirements))) {
|
|
39
|
-
throw new CommerceError('recovery_conflict', 'Stored authorization differs from the frozen challenge');
|
|
40
|
-
}
|
|
41
|
-
let receipt = attempt.receipt;
|
|
42
|
-
if (!receipt?.transaction && saved.fromBlock && this.options.findOriginalReceipt) {
|
|
43
|
-
receipt = await this.options.findOriginalReceipt(saved.payload, saved.requirements, saved.fromBlock) ?? null;
|
|
44
|
-
}
|
|
45
|
-
if (!receipt?.transaction)
|
|
46
|
-
return { state: 'unknown', taskId: order.taskId };
|
|
47
|
-
let proven = false;
|
|
48
|
-
try {
|
|
49
|
-
proven = await this.options.verifyReceipt(receipt, saved.payload, saved.requirements);
|
|
50
|
-
}
|
|
51
|
-
catch { }
|
|
52
|
-
// Persist the discovered transaction even if confirmations are still pending.
|
|
53
|
-
this.options.store.recordSettlement(attempt.id, proven ? 'confirmed' : 'unknown', receipt);
|
|
54
|
-
return { state: proven ? 'confirmed' : 'unknown', taskId: order.taskId };
|
|
55
|
-
}
|
|
56
|
-
async resource(profile) {
|
|
57
|
-
const key = digest(profile);
|
|
58
|
-
let promise = this.servers.get(key);
|
|
59
|
-
if (!promise) {
|
|
60
|
-
promise = (async () => {
|
|
61
|
-
const facilitator = this.options.facilitator?.(profile) ?? new HTTPFacilitatorClient({ url: profile.facilitatorUrl });
|
|
62
|
-
const server = new x402ResourceServer(facilitator)
|
|
63
|
-
.register(profile.network, new ExactEvmScheme())
|
|
64
|
-
.registerExtension(paymentIdentifierResourceServerExtension);
|
|
65
|
-
await server.initialize();
|
|
66
|
-
return { server, facilitator };
|
|
67
|
-
})();
|
|
68
|
-
this.servers.set(key, promise);
|
|
69
|
-
void promise.catch(() => this.servers.delete(key));
|
|
70
|
-
}
|
|
71
|
-
return promise;
|
|
72
|
-
}
|
|
73
|
-
async handle(request, order) {
|
|
74
|
-
const profile = order.quote.paymentProfile;
|
|
75
|
-
if (profile?.adapter !== 'x402')
|
|
76
|
-
throw new CommerceError('payment_method_unavailable', 'This offer requires another installed payment adapter');
|
|
77
|
-
const previous = this.options.store.paymentForOrder(order.id);
|
|
78
|
-
if (order.paymentState === 'confirmed' && previous?.receipt) {
|
|
79
|
-
return { headers: { 'PAYMENT-RESPONSE': encodePaymentResponseHeader(previous.receipt) } };
|
|
80
|
-
}
|
|
81
|
-
if (['settling', 'unknown'].includes(order.paymentState)) {
|
|
82
|
-
const recovered = await this.recover(order.id, order.caller);
|
|
83
|
-
if (recovered.state === 'confirmed') {
|
|
84
|
-
const receipt = this.options.store.paymentForOrder(order.id).receipt;
|
|
85
|
-
return { headers: { 'PAYMENT-RESPONSE': encodePaymentResponseHeader(receipt) } };
|
|
86
|
-
}
|
|
87
|
-
return { response: Response.json({ error: 'payment_reconciliation_required', taskId: order.taskId }, { status: 503, headers: { 'Cache-Control': 'no-store' } }) };
|
|
88
|
-
}
|
|
89
|
-
if (Date.now() >= Date.parse(order.quote.expiresAt))
|
|
90
|
-
return { response: Response.json({ error: 'quote_expired' }, { status: 409 }) };
|
|
91
|
-
const { server, facilitator } = await this.resource(profile);
|
|
92
|
-
let required = this.options.store.paymentChallenge(order.id);
|
|
93
|
-
if (!required) {
|
|
94
|
-
const token = findDefaultAsset(profile.asset, profile.network);
|
|
95
|
-
if (!token)
|
|
96
|
-
throw new CommerceError('unsupported_asset', 'Official token signing domain is unavailable');
|
|
97
|
-
const requirements = await server.buildPaymentRequirements({
|
|
98
|
-
scheme: 'exact', network: profile.network, payTo: profile.payTo,
|
|
99
|
-
price: { asset: profile.asset, amount: order.quote.amount, extra: { name: token.name, version: token.version } },
|
|
100
|
-
maxTimeoutSeconds: 300, extra: { paymentFlow: 'upfront', assetTransferMethod: 'eip3009' },
|
|
101
|
-
});
|
|
102
|
-
const resource = new URL(request.url);
|
|
103
|
-
resource.search = '';
|
|
104
|
-
resource.searchParams.set('quote', order.quote.quoteId);
|
|
105
|
-
required = this.options.store.freezePaymentChallenge(order.id, await server.createPaymentRequiredResponse(requirements, { url: resource.href, description: order.quote.serviceId, mimeType: 'application/json' }, undefined, { [PAYMENT_IDENTIFIER]: declarePaymentIdentifierExtension(false), 'urn:envarpay:quote:1': { info: { quoteId: order.quote.quoteId, messageId: order.messageId, serviceId: order.quote.serviceId, serviceRevision: order.quote.serviceRevision, offerId: order.offerId, inputDigest: order.inputDigest, termsDigest: order.quote.termsDigest, expiresAt: order.quote.expiresAt, amount: order.quote.amount, currency: order.quote.currency, recipient: order.quote.recipient } } }));
|
|
106
|
-
}
|
|
107
|
-
// Identifier is optional for unextended standard clients; nonce dedup is mandatory.
|
|
108
|
-
const challenge = () => ({ response: new Response('{}', {
|
|
109
|
-
status: 402, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', 'PAYMENT-REQUIRED': encodePaymentRequiredHeader(required) },
|
|
110
|
-
}) });
|
|
111
|
-
const header = request.headers.get('PAYMENT-SIGNATURE');
|
|
112
|
-
if (!header)
|
|
113
|
-
return challenge();
|
|
114
|
-
if (header.length > 32768)
|
|
115
|
-
throw new CommerceError('payment_payload_size', 'Payment credential exceeds limit');
|
|
116
|
-
let payload;
|
|
117
|
-
try {
|
|
118
|
-
payload = decodePaymentSignatureHeader(header);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
throw new CommerceError('invalid_payment', 'Malformed payment credential');
|
|
122
|
-
}
|
|
123
|
-
const matched = server.findMatchingRequirements(required.accepts, payload);
|
|
124
|
-
if (!matched || payload.x402Version !== 2 || payload.resource?.url !== required.resource.url || digest(payload.accepted) !== digest(matched)) {
|
|
125
|
-
throw new CommerceError('quote_mismatch', 'Payment does not match the frozen quote');
|
|
126
|
-
}
|
|
127
|
-
const extension = payload.extensions?.[PAYMENT_IDENTIFIER];
|
|
128
|
-
// Validate against our schema; never compile a schema supplied by the buyer.
|
|
129
|
-
if (extension !== undefined && !validatePaymentIdentifier({ info: extension?.info, schema: declarePaymentIdentifierExtension(false).schema }).valid) {
|
|
130
|
-
throw new CommerceError('invalid_payment_identifier', 'Invalid standard payment identifier');
|
|
131
|
-
}
|
|
132
|
-
const identifier = extractPaymentIdentifier(payload, false);
|
|
133
|
-
const native = payload.payload;
|
|
134
|
-
const auth = native.authorization;
|
|
135
|
-
const payer = this.options.payerFor(order.caller);
|
|
136
|
-
if (!payer || !auth?.from || auth.from.toLowerCase() !== payer.toLowerCase() || auth.to?.toLowerCase() !== profile.payTo.toLowerCase() || auth.value !== order.quote.amount || !/^0x[0-9a-fA-F]{64}$/.test(auth.nonce ?? '') || !native.signature) {
|
|
137
|
-
throw new CommerceError('payment_identity', 'Signed payer, recipient, amount and nonce must match authorized buyer and quote');
|
|
138
|
-
}
|
|
139
|
-
// Quote UI metadata is locally checked above. It is not a facilitator
|
|
140
|
-
// settlement extension; do not require third-party facilitators to implement it.
|
|
141
|
-
const settlementPayload = { ...payload, extensions: Object.fromEntries(Object.entries(payload.extensions ?? {}).filter(([key]) => key !== 'urn:envarpay:quote:1')) };
|
|
142
|
-
// Upfront SDK verification may skip /verify; explicitly perform it before reserve.
|
|
143
|
-
const verified = await facilitator.verify(settlementPayload, matched);
|
|
144
|
-
if (!verified.isValid)
|
|
145
|
-
return challenge();
|
|
146
|
-
if (verified.payer && verified.payer.toLowerCase() !== payer.toLowerCase())
|
|
147
|
-
throw new CommerceError('payment_identity', 'Verified payer differs from authorized buyer');
|
|
148
|
-
const economicKey = digest({ network: profile.network, asset: profile.asset.toLowerCase(), payer: payer.toLowerCase(), nonce: auth.nonce.toLowerCase() });
|
|
149
|
-
const fromBlock = await this.options.checkpoint?.(matched);
|
|
150
|
-
const saved = { payload, requirements: matched, quoteId: order.quote.quoteId, ...(fromBlock !== undefined ? { fromBlock } : {}) };
|
|
151
|
-
const credentialRef = this.options.vault.put(saved);
|
|
152
|
-
const attempt = this.options.store.reservePayment(order.id, order.caller, economicKey, credentialRef, identifier ?? undefined);
|
|
153
|
-
let result;
|
|
154
|
-
try {
|
|
155
|
-
result = await server.settlePayment(settlementPayload, matched, undefined, undefined, undefined, 'before-handler');
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
this.options.store.recordSettlement(attempt, 'unknown', {});
|
|
159
|
-
return { response: Response.json({ error: 'payment_outcome_unknown' }, { status: 503 }) };
|
|
160
|
-
}
|
|
161
|
-
// A failure can follow broadcast. Never create a second economic authorization.
|
|
162
|
-
let proven = false;
|
|
163
|
-
if (result.success) {
|
|
164
|
-
try {
|
|
165
|
-
proven = await this.options.verifyReceipt(result, payload, matched);
|
|
166
|
-
}
|
|
167
|
-
catch { }
|
|
168
|
-
}
|
|
169
|
-
this.options.store.recordSettlement(attempt, proven ? 'confirmed' : 'unknown', result);
|
|
170
|
-
if (!proven)
|
|
171
|
-
return { response: Response.json({ error: result.success ? 'receipt_verification_pending' : 'payment_outcome_unknown' }, { status: 503 }) };
|
|
172
|
-
return { headers: { 'PAYMENT-RESPONSE': encodePaymentResponseHeader(result) } };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"policyVersion":1,
|
|
3
|
-
"paymentsEnabled":false,
|
|
4
|
-
"approval":"per_purchase",
|
|
5
|
-
"peers":[{
|
|
6
|
-
"id":"research-seller",
|
|
7
|
-
"cardUrl":"https://seller.example/services/research/v1/offers/usdc-once/agent-card.json",
|
|
8
|
-
"protocol":"x402",
|
|
9
|
-
"currency":"eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
10
|
-
"recipient":"0x2222222222222222222222222222222222222222",
|
|
11
|
-
"maxPerPurchase":"3000000"
|
|
12
|
-
}],
|
|
13
|
-
"budgets":[{
|
|
14
|
-
"currency":"eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
15
|
-
"maxTotal":"10000000",
|
|
16
|
-
"period":"cumulative"
|
|
17
|
-
}]
|
|
18
|
-
}
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:envarpay:buyer-policy:1",
|
|
4
|
-
"title": "Local buyer permission policy; not supplied by the seller",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"additionalProperties": false,
|
|
7
|
-
"required": [
|
|
8
|
-
"policyVersion",
|
|
9
|
-
"paymentsEnabled",
|
|
10
|
-
"approval",
|
|
11
|
-
"peers",
|
|
12
|
-
"budgets"
|
|
13
|
-
],
|
|
14
|
-
"properties": {
|
|
15
|
-
"policyVersion": {
|
|
16
|
-
"const": 1
|
|
17
|
-
},
|
|
18
|
-
"paymentsEnabled": {
|
|
19
|
-
"type": "boolean"
|
|
20
|
-
},
|
|
21
|
-
"approval": {
|
|
22
|
-
"enum": [
|
|
23
|
-
"per_purchase",
|
|
24
|
-
"within_preapproved_limits"
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
|
-
"peers": {
|
|
28
|
-
"type": "array",
|
|
29
|
-
"minItems": 1,
|
|
30
|
-
"items": {
|
|
31
|
-
"type": "object",
|
|
32
|
-
"additionalProperties": false,
|
|
33
|
-
"required": [
|
|
34
|
-
"id",
|
|
35
|
-
"cardUrl",
|
|
36
|
-
"protocol",
|
|
37
|
-
"currency",
|
|
38
|
-
"recipient",
|
|
39
|
-
"maxPerPurchase"
|
|
40
|
-
],
|
|
41
|
-
"properties": {
|
|
42
|
-
"id": {
|
|
43
|
-
"type": "string",
|
|
44
|
-
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
45
|
-
},
|
|
46
|
-
"cardUrl": {
|
|
47
|
-
"type": "string",
|
|
48
|
-
"format": "uri",
|
|
49
|
-
"pattern": "^https://"
|
|
50
|
-
},
|
|
51
|
-
"protocol": {
|
|
52
|
-
"enum": [
|
|
53
|
-
"x402",
|
|
54
|
-
"mpp",
|
|
55
|
-
"free"
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
"currency": {
|
|
59
|
-
"type": [
|
|
60
|
-
"string",
|
|
61
|
-
"null"
|
|
62
|
-
]
|
|
63
|
-
},
|
|
64
|
-
"recipient": {
|
|
65
|
-
"type": [
|
|
66
|
-
"string",
|
|
67
|
-
"null"
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
|
-
"maxPerPurchase": {
|
|
71
|
-
"type": "string",
|
|
72
|
-
"pattern": "^(0|[1-9][0-9]{0,77})$"
|
|
73
|
-
},
|
|
74
|
-
"mode": {
|
|
75
|
-
"enum": [
|
|
76
|
-
"envarpay",
|
|
77
|
-
"standard-a2a"
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
"endpoint": {
|
|
81
|
-
"type": "string",
|
|
82
|
-
"format": "uri",
|
|
83
|
-
"pattern": "^https://"
|
|
84
|
-
},
|
|
85
|
-
"authentication": {
|
|
86
|
-
"enum": [
|
|
87
|
-
"none",
|
|
88
|
-
"bearer"
|
|
89
|
-
]
|
|
90
|
-
},
|
|
91
|
-
"localContract": {
|
|
92
|
-
"type": "object",
|
|
93
|
-
"additionalProperties": false,
|
|
94
|
-
"required": [
|
|
95
|
-
"revision",
|
|
96
|
-
"offerId",
|
|
97
|
-
"inputSchema",
|
|
98
|
-
"amount",
|
|
99
|
-
"paidOnly"
|
|
100
|
-
],
|
|
101
|
-
"properties": {
|
|
102
|
-
"revision": {
|
|
103
|
-
"type": "integer",
|
|
104
|
-
"minimum": 1,
|
|
105
|
-
"maximum": 9007199254740991
|
|
106
|
-
},
|
|
107
|
-
"offerId": {
|
|
108
|
-
"type": "string",
|
|
109
|
-
"pattern": "^[a-z][a-z0-9-]{0,63}$"
|
|
110
|
-
},
|
|
111
|
-
"inputSchema": {
|
|
112
|
-
"type": "object"
|
|
113
|
-
},
|
|
114
|
-
"amount": {
|
|
115
|
-
"$ref": "#/$defs/amount"
|
|
116
|
-
},
|
|
117
|
-
"paidOnly": {
|
|
118
|
-
"const": true
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
"allOf": [
|
|
124
|
-
{
|
|
125
|
-
"if": {
|
|
126
|
-
"properties": {
|
|
127
|
-
"protocol": {
|
|
128
|
-
"const": "free"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
"then": {
|
|
133
|
-
"properties": {
|
|
134
|
-
"currency": {
|
|
135
|
-
"const": null
|
|
136
|
-
},
|
|
137
|
-
"recipient": {
|
|
138
|
-
"const": null
|
|
139
|
-
},
|
|
140
|
-
"maxPerPurchase": {
|
|
141
|
-
"const": "0"
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
"else": {
|
|
146
|
-
"properties": {
|
|
147
|
-
"currency": {
|
|
148
|
-
"type": "string",
|
|
149
|
-
"minLength": 1
|
|
150
|
-
},
|
|
151
|
-
"recipient": {
|
|
152
|
-
"type": "string",
|
|
153
|
-
"minLength": 1
|
|
154
|
-
},
|
|
155
|
-
"maxPerPurchase": {
|
|
156
|
-
"$ref": "#/$defs/amount"
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
"if": {
|
|
163
|
-
"required": [
|
|
164
|
-
"mode"
|
|
165
|
-
],
|
|
166
|
-
"properties": {
|
|
167
|
-
"mode": {
|
|
168
|
-
"const": "standard-a2a"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
"then": {
|
|
173
|
-
"required": [
|
|
174
|
-
"endpoint",
|
|
175
|
-
"authentication",
|
|
176
|
-
"localContract"
|
|
177
|
-
],
|
|
178
|
-
"properties": {
|
|
179
|
-
"protocol": {
|
|
180
|
-
"const": "x402"
|
|
181
|
-
},
|
|
182
|
-
"endpoint": {},
|
|
183
|
-
"authentication": {},
|
|
184
|
-
"localContract": {}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
"else": {
|
|
188
|
-
"not": {
|
|
189
|
-
"anyOf": [
|
|
190
|
-
{
|
|
191
|
-
"required": [
|
|
192
|
-
"endpoint"
|
|
193
|
-
],
|
|
194
|
-
"properties": {
|
|
195
|
-
"endpoint": {}
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
"required": [
|
|
200
|
-
"authentication"
|
|
201
|
-
],
|
|
202
|
-
"properties": {
|
|
203
|
-
"authentication": {}
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
"required": [
|
|
208
|
-
"localContract"
|
|
209
|
-
],
|
|
210
|
-
"properties": {
|
|
211
|
-
"localContract": {}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
]
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
]
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
"budgets": {
|
|
222
|
-
"type": "array",
|
|
223
|
-
"minItems": 0,
|
|
224
|
-
"items": {
|
|
225
|
-
"type": "object",
|
|
226
|
-
"additionalProperties": false,
|
|
227
|
-
"required": [
|
|
228
|
-
"currency",
|
|
229
|
-
"maxTotal",
|
|
230
|
-
"period"
|
|
231
|
-
],
|
|
232
|
-
"properties": {
|
|
233
|
-
"currency": {
|
|
234
|
-
"type": "string",
|
|
235
|
-
"minLength": 1
|
|
236
|
-
},
|
|
237
|
-
"maxTotal": {
|
|
238
|
-
"$ref": "#/$defs/amount"
|
|
239
|
-
},
|
|
240
|
-
"period": {
|
|
241
|
-
"const": "cumulative"
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
"$defs": {
|
|
248
|
-
"amount": {
|
|
249
|
-
"type": "string",
|
|
250
|
-
"pattern": "^[1-9][0-9]{0,77}$"
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|