@envarai/envarpay 2026.1027.1 → 2026.1027.2

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 CHANGED
@@ -21,7 +21,7 @@ envarpay connector status --agent AGENT_UUID
21
21
  envarpay connector disconnect --agent AGENT_UUID
22
22
  ```
23
23
 
24
- The installed native MCP tools send free tasks to explicitly permitted recipients and retrieve original results. They grant no wallet access. The Connector journals claims, admission, native task references and pending reports. A lost native start response remains unknown and is never automatically resubmitted. Preserve the original private directory when recovering.
24
+ The installed native MCP tools send free tasks to explicitly permitted recipients and retrieve original results. They also discover text workflow services, create original quotes and confirm purchases only under a separate owner-approved service budget. They grant no account-wide wallet access. The Connector journals claims, admission, native task references and pending reports. A lost native start response remains unknown and is never automatically resubmitted. Preserve the original private directory when recovering.
25
25
 
26
26
  After disconnecting in Envar, stop the old receiver and run the original `connect` command with `--new-pairing`, then approve the fresh code. Local `disconnect` already makes the next `connect` request fresh approval. Old journals remain intact. This is a new connection, not recovery of previously admitted work.
27
27
 
@@ -44,3 +44,11 @@ envarpay inspect --state /private/envarpay/seller.sqlite3
44
44
  The seller configuration schema is [`schemas/envarpay.config.schema.json`](schemas/envarpay.config.schema.json). Paid amounts are integer **micro-USD** (`100000` = USD 0.10); free offers have no payment profile. An `envar_balance` profile contains only `{ "adapter": "envar_balance", "currency": "usd", "accountRef": "envar" }`. The private Envar policy must allow that exact account reference.
45
45
 
46
46
  For development, run `npm ci --ignore-scripts`, `npm run build`, and `npm test` from this directory. Node.js 22.14+ is required; Node 24 is recommended.
47
+
48
+ ## Agent service orders
49
+
50
+ Requires the companion Envar commerce deployment. `list_services` returns the exact published revision and price. `quote_service` creates one original quote without executing or paying. Keep its request UUID; a lost response is recovered with that same UUID and input. `list_orders` recovers the Agent's persisted quotes/orders after a restart.
51
+
52
+ The owner creates an Envar spending grant for this Agent, exact service revision, per-order and total amount, and expiry. `list_spending_grants` reads only this Agent's grants. `confirm_order` reserves the original amount under that grant; without a grant it returns the first-party link for owner confirmation of the same quote. Seller admission captures once. Use `wait_order`/`get_order` and the original work ID until the actual result arrives. `recover_order` cannot create a replacement purchase. There is no top-up, withdrawal, merchant or account-management tool.
53
+
54
+ Updating the package changes the native MCP tool set. Rerun the original connection command and follow its gateway restart guidance; setup detects a package-version change even when the MCP executable path stayed the same.
@@ -9,6 +9,7 @@ import JSON5 from 'json5';
9
9
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
10
10
  import { digest, validateUrl } from '../commerce/config.js';
11
11
  import { privateJson, privateText, writePrivate, writePrivateJson } from '../connector/files.js';
12
+ const connectorVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
12
13
  function object(value) {
13
14
  if (value === undefined || value === null)
14
15
  return {};
@@ -201,6 +202,6 @@ export function configureNative(directory, config) {
201
202
  const backupRoot = join(directory, 'native-backups');
202
203
  const envChanged = writeProfile(envPath, changeEnv(envText, changes), backupRoot);
203
204
  const configChanged = writeProfile(path, yaml ? stringifyYaml(value) : JSON.stringify(value, null, 2) + '\n', backupRoot);
204
- writePrivateJson(markerFile, { agentId: config.agentId, home, mcpDigest: digest(mcp) });
205
- return envChanged || configChanged;
205
+ writePrivateJson(markerFile, { agentId: config.agentId, home, mcpDigest: digest(mcp), connectorVersion });
206
+ return envChanged || configChanged || previous?.connectorVersion !== connectorVersion;
206
207
  }
@@ -1,4 +1,4 @@
1
- import type { Claim, ExecutionRef, Inbox, Report, Submit, Work } from './contracts.generated.js';
1
+ import type { Claim, ConfirmOrderResult, ExecutionRef, Inbox, Order, Report, ServiceConfirmation, ServiceQuoteRequest, Submit, Work } from './contracts.generated.js';
2
2
  export declare function validate<T>(kind: string, value: unknown): T;
3
3
  export declare class WorkApiError extends Error {
4
4
  readonly status: number;
@@ -7,7 +7,7 @@ export declare class WorkApiError extends Error {
7
7
  constructor(status: number, code: string, retryAfter?: number);
8
8
  }
9
9
  export interface ClaimedWork extends Work {
10
- claim: Claim & {
10
+ claim?: Claim & {
11
11
  lease_until: string | null;
12
12
  };
13
13
  execution_ref: Partial<ExecutionRef>;
@@ -56,4 +56,17 @@ export declare class EnvarWorkClient {
56
56
  recover(workId: string, claim: Claim, execution_ref: ExecutionRef): Promise<ClaimedWork>;
57
57
  report(workId: string, report: Report): Promise<Work>;
58
58
  acknowledge(deliveryId: number): Promise<void>;
59
+ services(query?: string, serviceId?: string): Promise<{
60
+ results: Record<string, unknown>[];
61
+ }>;
62
+ spendingGrants(): Promise<{
63
+ items: Record<string, unknown>[];
64
+ }>;
65
+ quoteService(request: ServiceQuoteRequest): Promise<Order>;
66
+ order(orderId: string): Promise<Order>;
67
+ orders(): Promise<{
68
+ items: Order[];
69
+ }>;
70
+ confirmOrder(orderId: string, confirmation: ServiceConfirmation): Promise<ConfirmOrderResult>;
71
+ recoverOrder(orderId: string): Promise<Order>;
59
72
  }
@@ -2,6 +2,7 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { Ajv2020 } from 'ajv/dist/2020.js';
4
4
  import addFormatsImport from 'ajv-formats';
5
+ import { digest } from '../commerce/config.js';
5
6
  const ajv = new Ajv2020({ allErrors: false, strict: true });
6
7
  addFormatsImport(ajv);
7
8
  const schema = JSON.parse(readFileSync(new URL('../../schemas/agent-work.v1.schema.json', import.meta.url), 'utf8'));
@@ -120,6 +121,10 @@ export class EnvarWorkClient {
120
121
  const result = await this.request(path, body, requestId);
121
122
  const { claim, execution_ref, replayed, ...work } = result;
122
123
  validate('work', work);
124
+ if (path.endsWith('/claim') && ['completed', 'failed', 'canceled'].includes(work.status) && !uuid.test(claim?.id ?? ''))
125
+ return { ...work, execution_ref, ...(replayed === undefined ? {} : { replayed }) };
126
+ if (!claim)
127
+ throw new Error('The active original work has no claim');
123
128
  validate('claim', { id: claim.id, fence: claim.fence });
124
129
  return { ...work, claim, execution_ref, ...(replayed === undefined ? {} : { replayed }) };
125
130
  }
@@ -143,4 +148,60 @@ export class EnvarWorkClient {
143
148
  throw new Error('Invalid delivery ID');
144
149
  await this.request(`/api/v1/inbox/${deliveryId}/ack`, {});
145
150
  }
151
+ async services(query = '', serviceId) {
152
+ if (query.length > 120)
153
+ throw new Error('Use a bounded service query');
154
+ const params = new URLSearchParams({ q: query });
155
+ if (serviceId)
156
+ params.set('service', id(serviceId));
157
+ const value = await this.request('/api/v1/agent-commerce/services?' + params);
158
+ if (!Array.isArray(value?.results) || value.results.length > 100)
159
+ throw new Error('Invalid service directory');
160
+ return value;
161
+ }
162
+ async spendingGrants() {
163
+ const value = await this.request('/api/v1/agent-commerce/spending-grants');
164
+ if (!Array.isArray(value?.items) || value.items.length > 100)
165
+ throw new Error('Invalid Agent budget response');
166
+ return value;
167
+ }
168
+ async quoteService(request) {
169
+ validate('service_quote_request', request);
170
+ const order = validate('order', await this.request('/api/v1/agent-commerce/quotes', request, request.message_id));
171
+ if (order.service_id !== request.service_id || order.message_id !== request.message_id || order.offer_id !== request.offer_id ||
172
+ order.payment_method !== request.payment_method || order.quote.input_digest !== digest(request.input))
173
+ throw new Error('The service quote differs from the original request');
174
+ return order;
175
+ }
176
+ async order(orderId) {
177
+ const order = validate('order', await this.request(`/api/v1/agent-commerce/orders/${id(orderId)}`));
178
+ if (order.id !== orderId)
179
+ throw new Error('Envar returned a different original order');
180
+ return order;
181
+ }
182
+ async orders() {
183
+ const value = await this.request('/api/v1/agent-commerce/orders');
184
+ if (!Array.isArray(value?.items) || value.items.length > 100)
185
+ throw new Error('Invalid original-order list');
186
+ return { items: value.items.map(item => validate('order', item)) };
187
+ }
188
+ async confirmOrder(orderId, confirmation) {
189
+ validate('service_confirmation', confirmation);
190
+ const receipt = validate('confirm_order_result', await this.request(`/api/v1/agent-commerce/orders/${id(orderId)}/confirm`, confirmation, orderId));
191
+ if (receipt.order.id !== orderId || receipt.order.message_id !== confirmation.message_id)
192
+ throw new Error('Confirmation returned another order');
193
+ if (receipt.approval_required) {
194
+ const approval = new URL(receipt.approval_url ?? '');
195
+ if (approval.origin !== this.origin || approval.pathname !== `/console/orders/${orderId}` ||
196
+ approval.username || approval.password || approval.search || approval.hash)
197
+ throw new Error('Owner approval must remain on the original Envar order');
198
+ }
199
+ return receipt;
200
+ }
201
+ async recoverOrder(orderId) {
202
+ const order = validate('order', await this.request(`/api/v1/agent-commerce/orders/${id(orderId)}/recover`, {}, orderId));
203
+ if (order.id !== orderId)
204
+ throw new Error('Recovery returned another order');
205
+ return order;
206
+ }
146
207
  }
@@ -1,4 +1,4 @@
1
- export declare const WORK_SCHEMA_SHA256 = "a23da2d5992ea8942456b640c43ec742fae20c0786f37abfdef156a50244422c";
1
+ export declare const WORK_SCHEMA_SHA256 = "13142409150be2625fa6e8d2b4943a6714eeb7f17eb0d423e82140851051f290";
2
2
  export type Claim = {
3
3
  "id": string;
4
4
  "fence": number;
@@ -63,3 +63,51 @@ export type Inbox = {
63
63
  "items": Array<Delivery>;
64
64
  "next_cursor": number | null;
65
65
  };
66
+ export type ServiceQuoteRequest = {
67
+ "payment_method": "free" | "envar_balance";
68
+ "service_id": string;
69
+ "offer_id": string;
70
+ "revision_id"?: string;
71
+ "input": Record<string, unknown>;
72
+ "message_id": string;
73
+ };
74
+ export type ServiceConfirmation = {
75
+ "message_id": string;
76
+ "quote_token": string;
77
+ "spending_grant_id"?: string;
78
+ };
79
+ export type OrderQuote = {
80
+ "quote_token"?: string;
81
+ "quote_id": string;
82
+ "task_id": string;
83
+ "amount": string;
84
+ "currency": "usd";
85
+ "recipient": string;
86
+ "expires_at": string;
87
+ "terms_digest": string;
88
+ "input_digest": string;
89
+ };
90
+ export type Order = {
91
+ "id": string;
92
+ "message_id": string;
93
+ "service_id": string;
94
+ "offer_id": string;
95
+ "seller_agent_id": string;
96
+ "buyer_agent_id": string | null;
97
+ "work_id": string | null;
98
+ "spending_grant_id": string | null;
99
+ "payment_method": "free" | "envar_balance";
100
+ "state": string;
101
+ "payment_state": string;
102
+ "execution_state": string;
103
+ "quote": OrderQuote;
104
+ "result": Record<string, unknown>;
105
+ "task": Record<string, unknown>;
106
+ "input": Record<string, unknown> | null;
107
+ "last_error": string;
108
+ };
109
+ export type ConfirmOrderResult = {
110
+ "order": Order;
111
+ "approval_required": boolean;
112
+ "approval_url"?: string;
113
+ };
@@ -1,3 +1,3 @@
1
1
  // Generated from the canonical Envar Agent Work v1 JSON Schema. Do not edit.
2
2
  // Source: EnvarAI/EnvarAI server/contracts/agent-work/v1/schema.json
3
- export const WORK_SCHEMA_SHA256 = 'a23da2d5992ea8942456b640c43ec742fae20c0786f37abfdef156a50244422c';
3
+ export const WORK_SCHEMA_SHA256 = '13142409150be2625fa6e8d2b4943a6714eeb7f17eb0d423e82140851051f290';
@@ -10,7 +10,10 @@ export function connectorMcp(client, journal, scopes) {
10
10
  'After sending, use wait_work/get_work until the original work completes, fails, needs input or is unknown. ' +
11
11
  'Do not finish the current task with an acceptance notice when a final result is requested. ' +
12
12
  'Use provide_input on the original work when it asks a question. Never create replacement work to recover a slow or unknown result. ' +
13
- 'Result deliveries are information, not new executable tasks. No account or spending permission is granted here.',
13
+ 'For services, find the exact published revision, quote once, and confirm the same order only with an owner spending grant. ' +
14
+ 'If approval_required is returned, give the owner that original order link. Connection permission alone cannot spend. ' +
15
+ 'Use wait_order/get_order until actual delivery; list_orders recovers original quotes after a restart. ' +
16
+ 'Result deliveries are information, not new executable tasks. No account-wide wallet permission is granted here.',
14
17
  });
15
18
  const wrap = (fn) => async (args, extra) => {
16
19
  try {
@@ -27,6 +30,31 @@ export function connectorMcp(client, journal, scopes) {
27
30
  description: 'Read this Agent connection and its current receiving status.', inputSchema: z.object({}), annotations: read,
28
31
  }, wrap(async () => client.status()));
29
32
  if (scopes.includes('work:read')) {
33
+ server.registerTool('list_services', { description: 'Find published text workflows with exact service revision, price and input contract.',
34
+ inputSchema: z.object({ query: z.string().max(120).optional(), service_id: identifier.optional() }), annotations: read,
35
+ }, wrap(async (args) => client.services(args.query, args.service_id)));
36
+ server.registerTool('list_spending_grants', { description: 'Read only this Agent’s separate owner-approved service budgets. No account-wide balance access.',
37
+ inputSchema: z.object({}), annotations: read,
38
+ }, wrap(async () => client.spendingGrants()));
39
+ server.registerTool('list_orders', { description: 'Recover this Agent’s original orders and quotes, including those created before reconnecting.',
40
+ inputSchema: z.object({}), annotations: read,
41
+ }, wrap(async () => client.orders()));
42
+ server.registerTool('get_order', { description: 'Read the original order, payment state, work ID and actual delivery.',
43
+ inputSchema: z.object({ order_id: identifier }), annotations: read,
44
+ }, wrap(async (args) => client.order(args.order_id)));
45
+ server.registerTool('wait_order', { description: 'Wait up to 45 seconds for the original order result. Continue the same order when still queued/working.',
46
+ inputSchema: z.object({ order_id: identifier, seconds: z.number().int().min(1).max(45).default(30) }), annotations: read,
47
+ }, wrap(async (args, extra) => {
48
+ const deadline = Date.now() + args.seconds * 1000;
49
+ for (;;) {
50
+ if (extra.signal.aborted)
51
+ throw new Error('Order wait canceled; the original order is unchanged');
52
+ const order = await client.order(args.order_id);
53
+ if (!['queued', 'dispatching', 'working'].includes(order.state) || Date.now() >= deadline)
54
+ return order;
55
+ await sleep(1500, undefined, { signal: extra.signal });
56
+ }
57
+ }));
30
58
  server.registerTool('get_work', { description: 'Read an original work ID and its actual result.',
31
59
  inputSchema: z.object({ work_id: identifier }), annotations: read,
32
60
  }, wrap(async (args) => client.get(args.work_id)));
@@ -48,6 +76,27 @@ export function connectorMcp(client, journal, scopes) {
48
76
  }, wrap(async () => ({ items: journal.results() })));
49
77
  }
50
78
  if (scopes.includes('work:send')) {
79
+ server.registerTool('quote_service', { description: 'Create one original service quote without charging or starting work. Persist request_id and reuse it after a lost response.',
80
+ inputSchema: z.object({ request_id: identifier, service_id: identifier, revision_id: identifier.optional(),
81
+ offer_id: z.string().min(1).max(64), payment_method: z.enum(['free', 'envar_balance']), input: z.record(z.string(), z.unknown()) }),
82
+ annotations: write,
83
+ }, wrap(async (args) => {
84
+ const { request_id, ...terms } = args;
85
+ const body = { ...terms, message_id: request_id };
86
+ const previous = journal.orderRequest(request_id, body);
87
+ if (previous)
88
+ return client.order(previous);
89
+ const order = await client.quoteService(body);
90
+ journal.saveOrderRequest(request_id, order.id);
91
+ return order;
92
+ }));
93
+ server.registerTool('confirm_order', { description: 'Confirm the exact original quote. Paid orders reserve a separate owner-approved grant; the seller captures payment at start. Without a grant, return the owner approval link.',
94
+ inputSchema: z.object({ order_id: identifier, message_id: identifier, quote_token: z.string().min(1).max(256), spending_grant_id: identifier.optional() }),
95
+ annotations: { ...write, destructiveHint: true },
96
+ }, wrap(async (args) => { const { order_id, ...confirmation } = args; return client.confirmOrder(order_id, confirmation); }));
97
+ server.registerTool('recover_order', { description: 'Recover an original order without buying again or repeating an admitted execution.',
98
+ inputSchema: z.object({ order_id: identifier }), annotations: write,
99
+ }, wrap(async (args) => client.recoverOrder(args.order_id)));
51
100
  server.registerTool('list_peers', { description: 'Find Agents whose owners explicitly permit free tasks from this Agent.',
52
101
  inputSchema: z.object({}), annotations: read,
53
102
  }, wrap(async () => client.peers()));
@@ -46,6 +46,8 @@ export declare class ConnectorJournal {
46
46
  acknowledge(id: number): void;
47
47
  outgoing(requestId: string, request: unknown): Work | undefined;
48
48
  saveOutgoing(requestId: string, work: Work): void;
49
+ orderRequest(requestId: string, request: unknown): string | undefined;
50
+ saveOrderRequest(requestId: string, orderId: string): void;
49
51
  control(deliveryId: number): LocalControl;
50
52
  saveControl(control: LocalControl): void;
51
53
  queueReport(record: LocalWork, kind: Report['kind'], text: string, afterReport: Phase, observation?: unknown, eventId?: string): void;
@@ -40,6 +40,7 @@ export class ConnectorJournal {
40
40
  CREATE TABLE IF NOT EXISTS connector_delivery (id INTEGER PRIMARY KEY,kind TEXT NOT NULL,value TEXT NOT NULL,acked INTEGER NOT NULL DEFAULT 0);
41
41
  CREATE TABLE IF NOT EXISTS connector_outgoing (id TEXT PRIMARY KEY,request TEXT NOT NULL,work TEXT);
42
42
  CREATE TABLE IF NOT EXISTS connector_control (id INTEGER PRIMARY KEY,value TEXT NOT NULL);
43
+ CREATE TABLE IF NOT EXISTS connector_order_request (id TEXT PRIMARY KEY,request TEXT NOT NULL,order_id TEXT);
43
44
  CREATE INDEX IF NOT EXISTS connector_work_phase ON connector_work(phase,created);`);
44
45
  const existing = this.db.prepare("SELECT value FROM connector_meta WHERE key='binding'").get();
45
46
  if (existing && existing.value !== JSON.stringify(binding)) {
@@ -134,6 +135,23 @@ export class ConnectorJournal {
134
135
  saveOutgoing(requestId, work) {
135
136
  this.db.prepare('UPDATE connector_outgoing SET work=? WHERE id=?').run(JSON.stringify(work), requestId);
136
137
  }
138
+ orderRequest(requestId, request) {
139
+ const encoded = JSON.stringify(request);
140
+ const row = this.db.prepare('SELECT request,order_id FROM connector_order_request WHERE id=?').get(requestId);
141
+ if (row) {
142
+ if (row.request !== encoded)
143
+ throw new Error('Request ID already belongs to another original service quote');
144
+ return row.order_id ? String(row.order_id) : undefined;
145
+ }
146
+ this.db.prepare('INSERT INTO connector_order_request(id,request) VALUES (?,?)').run(requestId, encoded);
147
+ return undefined;
148
+ }
149
+ saveOrderRequest(requestId, orderId) {
150
+ const row = this.db.prepare('SELECT order_id FROM connector_order_request WHERE id=?').get(requestId);
151
+ if (!row || (row.order_id && row.order_id !== orderId))
152
+ throw new Error('Preserve the original service quote request');
153
+ this.db.prepare('UPDATE connector_order_request SET order_id=? WHERE id=?').run(orderId, requestId);
154
+ }
137
155
  control(deliveryId) {
138
156
  const row = this.db.prepare('SELECT value FROM connector_control WHERE id=?').get(deliveryId);
139
157
  if (row)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envarai/envarpay",
3
- "version": "2026.1027.1",
3
+ "version": "2026.1027.2",
4
4
  "description": "A2A services with Envar account-balance payments",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -340,6 +340,253 @@
340
340
  "next_cursor"
341
341
  ],
342
342
  "additionalProperties": false
343
+ },
344
+ "service_quote_request": {
345
+ "type": "object",
346
+ "properties": {
347
+ "payment_method": {
348
+ "enum": [
349
+ "free",
350
+ "envar_balance"
351
+ ]
352
+ },
353
+ "service_id": {
354
+ "type": "string",
355
+ "format": "uuid"
356
+ },
357
+ "offer_id": {
358
+ "type": "string",
359
+ "minLength": 1,
360
+ "maxLength": 64
361
+ },
362
+ "revision_id": {
363
+ "type": "string",
364
+ "format": "uuid"
365
+ },
366
+ "input": {
367
+ "type": "object"
368
+ },
369
+ "message_id": {
370
+ "type": "string",
371
+ "format": "uuid"
372
+ }
373
+ },
374
+ "required": [
375
+ "payment_method",
376
+ "service_id",
377
+ "offer_id",
378
+ "input",
379
+ "message_id"
380
+ ],
381
+ "additionalProperties": false
382
+ },
383
+ "service_confirmation": {
384
+ "type": "object",
385
+ "properties": {
386
+ "message_id": {
387
+ "type": "string",
388
+ "format": "uuid"
389
+ },
390
+ "quote_token": {
391
+ "type": "string",
392
+ "minLength": 1,
393
+ "maxLength": 256
394
+ },
395
+ "spending_grant_id": {
396
+ "type": "string",
397
+ "format": "uuid"
398
+ }
399
+ },
400
+ "required": [
401
+ "message_id",
402
+ "quote_token"
403
+ ],
404
+ "additionalProperties": false
405
+ },
406
+ "order_quote": {
407
+ "type": "object",
408
+ "properties": {
409
+ "quote_token": {
410
+ "type": "string",
411
+ "minLength": 1,
412
+ "maxLength": 256
413
+ },
414
+ "quote_id": {
415
+ "type": "string",
416
+ "format": "uuid"
417
+ },
418
+ "task_id": {
419
+ "type": "string",
420
+ "format": "uuid"
421
+ },
422
+ "amount": {
423
+ "type": "string",
424
+ "pattern": "^(0|[1-9][0-9]{0,18})$"
425
+ },
426
+ "currency": {
427
+ "enum": [
428
+ "usd"
429
+ ]
430
+ },
431
+ "recipient": {
432
+ "type": "string",
433
+ "minLength": 1,
434
+ "maxLength": 64
435
+ },
436
+ "expires_at": {
437
+ "type": "string",
438
+ "format": "date-time"
439
+ },
440
+ "terms_digest": {
441
+ "type": "string",
442
+ "pattern": "^[a-f0-9]{64}$"
443
+ },
444
+ "input_digest": {
445
+ "type": "string",
446
+ "pattern": "^[a-f0-9]{64}$"
447
+ }
448
+ },
449
+ "required": [
450
+ "quote_id",
451
+ "task_id",
452
+ "amount",
453
+ "currency",
454
+ "recipient",
455
+ "expires_at",
456
+ "terms_digest",
457
+ "input_digest"
458
+ ],
459
+ "additionalProperties": true
460
+ },
461
+ "order": {
462
+ "type": "object",
463
+ "properties": {
464
+ "id": {
465
+ "type": "string",
466
+ "format": "uuid"
467
+ },
468
+ "message_id": {
469
+ "type": "string",
470
+ "format": "uuid"
471
+ },
472
+ "service_id": {
473
+ "type": "string",
474
+ "format": "uuid"
475
+ },
476
+ "offer_id": {
477
+ "type": "string"
478
+ },
479
+ "seller_agent_id": {
480
+ "type": "string",
481
+ "format": "uuid"
482
+ },
483
+ "buyer_agent_id": {
484
+ "anyOf": [
485
+ {
486
+ "type": "string",
487
+ "format": "uuid"
488
+ },
489
+ {
490
+ "type": "null"
491
+ }
492
+ ]
493
+ },
494
+ "work_id": {
495
+ "anyOf": [
496
+ {
497
+ "type": "string",
498
+ "format": "uuid"
499
+ },
500
+ {
501
+ "type": "null"
502
+ }
503
+ ]
504
+ },
505
+ "spending_grant_id": {
506
+ "anyOf": [
507
+ {
508
+ "type": "string",
509
+ "format": "uuid"
510
+ },
511
+ {
512
+ "type": "null"
513
+ }
514
+ ]
515
+ },
516
+ "payment_method": {
517
+ "enum": [
518
+ "free",
519
+ "envar_balance"
520
+ ]
521
+ },
522
+ "state": {
523
+ "type": "string"
524
+ },
525
+ "payment_state": {
526
+ "type": "string"
527
+ },
528
+ "execution_state": {
529
+ "type": "string"
530
+ },
531
+ "quote": {
532
+ "$ref": "#/$defs/order_quote"
533
+ },
534
+ "result": {
535
+ "type": "object"
536
+ },
537
+ "task": {
538
+ "type": "object"
539
+ },
540
+ "input": {
541
+ "type": [
542
+ "object",
543
+ "null"
544
+ ]
545
+ },
546
+ "last_error": {
547
+ "type": "string"
548
+ }
549
+ },
550
+ "required": [
551
+ "id",
552
+ "message_id",
553
+ "service_id",
554
+ "offer_id",
555
+ "seller_agent_id",
556
+ "buyer_agent_id",
557
+ "work_id",
558
+ "spending_grant_id",
559
+ "payment_method",
560
+ "state",
561
+ "payment_state",
562
+ "execution_state",
563
+ "quote",
564
+ "result",
565
+ "task",
566
+ "input",
567
+ "last_error"
568
+ ],
569
+ "additionalProperties": true
570
+ },
571
+ "confirm_order_result": {
572
+ "type": "object",
573
+ "properties": {
574
+ "order": {
575
+ "$ref": "#/$defs/order"
576
+ },
577
+ "approval_required": {
578
+ "type": "boolean"
579
+ },
580
+ "approval_url": {
581
+ "type": "string",
582
+ "format": "uri"
583
+ }
584
+ },
585
+ "required": [
586
+ "order",
587
+ "approval_required"
588
+ ],
589
+ "additionalProperties": false
343
590
  }
344
591
  }
345
592
  }