@visa/cli 4.1.0-rc.153 → 4.1.0-rc.155
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 +1 -0
- package/dist/checkout-engine/adapters/generic.d.ts +10 -0
- package/dist/checkout-engine/adapters/generic.js +15 -3
- package/dist/checkout-engine/adapters/shopify.d.ts +25 -1
- package/dist/checkout-engine/adapters/shopify.js +103 -12
- package/dist/checkout-engine/cli-engine.d.ts +4 -0
- package/dist/checkout-engine/cli-engine.js +7 -6
- package/dist/checkout-engine/confirmed-merchants.d.ts +31 -0
- package/dist/checkout-engine/confirmed-merchants.js +165 -0
- package/dist/checkout-engine/executor.js +40 -2
- package/dist/checkout-engine/index.d.ts +3 -0
- package/dist/checkout-engine/index.js +3 -0
- package/dist/checkout-engine/known-merchants.d.ts +10 -0
- package/dist/checkout-engine/known-merchants.js +38 -0
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +13 -0
- package/dist/checkout-engine/mandate/mandate-ledger.js +22 -0
- package/dist/checkout-engine/receipt-dir.d.ts +6 -0
- package/dist/checkout-engine/receipt-dir.js +8 -0
- package/dist/checkout-engine/receipt.d.ts +42 -2
- package/dist/checkout-engine/receipt.js +30 -14
- package/dist/cli.js +351 -349
- package/dist/mcp-server/index.js +264 -264
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Curated identities for checkout URLs the registry cannot name on its own.
|
|
2
|
+
//
|
|
3
|
+
// Hosted payment links are anonymous by construction: the page host is the
|
|
4
|
+
// PSP's (donate.stripe.com, buy.stripe.com), the path is an opaque token, and
|
|
5
|
+
// the receipt records nothing else. So a registry row for a completed payment
|
|
6
|
+
// link answers "where can I use my card?" with a string no human recognizes.
|
|
7
|
+
// This map carries the once-per-link human identification, keyed by the exact
|
|
8
|
+
// checkout URL the receipts group on.
|
|
9
|
+
//
|
|
10
|
+
// Curation rule: an entry is added only after a checkout at that URL completed
|
|
11
|
+
// for real (engine outcome 'confirmed' + VIC APPROVED — the same admission rule
|
|
12
|
+
// confirmed-merchants.ts enforces) and the merchant behind the link was
|
|
13
|
+
// identified by a person. The map never creates registry rows; it only names
|
|
14
|
+
// rows the device's own receipts already earned. A URL missing here still
|
|
15
|
+
// lists — it just shows as its host.
|
|
16
|
+
/** Checkout URL → who is actually behind it. Confirmed live 2026-07/2026-08. */
|
|
17
|
+
export const KNOWN_MERCHANT_IDENTITIES = {
|
|
18
|
+
'https://donate.stripe.com/3cscPcaqY20H8ta4gh': {
|
|
19
|
+
name: 'NC Seaside Animal Rescue',
|
|
20
|
+
category: 'charity (animal rescue, NC nonprofit)',
|
|
21
|
+
website: 'https://www.ncseasideanimalrescue.org/make-a-donation',
|
|
22
|
+
},
|
|
23
|
+
'https://donate.stripe.com/14k3ei9TYgwFclq145': {
|
|
24
|
+
name: 'FreeCAD',
|
|
25
|
+
category: 'open-source project donation (CAD software)',
|
|
26
|
+
website: 'https://www.freecad.org',
|
|
27
|
+
},
|
|
28
|
+
'https://buy.stripe.com/dR6eWGaK41MX2YgaEF': {
|
|
29
|
+
name: 'scribepod',
|
|
30
|
+
category: 'indie creator tip jar (AI podcast project)',
|
|
31
|
+
website: 'https://github.com/yacineMTB/scribepod',
|
|
32
|
+
},
|
|
33
|
+
'https://donate.stripe.com/7sIcNj9Gs4WO6d29AA': {
|
|
34
|
+
name: 'n0bleKing GRAFFITI',
|
|
35
|
+
category: 'indie creator tip jar (pixel art and fonts)',
|
|
36
|
+
website: 'https://nobleking-graffiti.itch.io',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -61,6 +61,13 @@ export type CardMandateRecord = {
|
|
|
61
61
|
* created record attempted registration. ISO 8601.
|
|
62
62
|
*/
|
|
63
63
|
registerFailedAt?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Set after an authenticated owner-scoped server read proves this mandate was
|
|
66
|
+
* revoked. The local file is only an execution cache; server revocation is
|
|
67
|
+
* canonical and permanently retires the cached mandate from selection while
|
|
68
|
+
* retaining its receipt history for the owner.
|
|
69
|
+
*/
|
|
70
|
+
serverRevokedAt?: string;
|
|
64
71
|
/**
|
|
65
72
|
* A cross-host retail budget, not scoped to one `merchantHost`. The local
|
|
66
73
|
* selector may attempt it at any host, while the provider/network still
|
|
@@ -127,6 +134,12 @@ export declare class MandateLedger {
|
|
|
127
134
|
* Throws only if the mandate is unknown.
|
|
128
135
|
*/
|
|
129
136
|
markRegisterFailed(mandateId: string, now?: Date): Promise<CardMandateRecord>;
|
|
137
|
+
/**
|
|
138
|
+
* Retire a locally cached mandate after the authenticated account API proves
|
|
139
|
+
* it was revoked. This is idempotent and monotonic: an owner revocation can
|
|
140
|
+
* never be undone by a later stale/local read.
|
|
141
|
+
*/
|
|
142
|
+
markServerRevoked(mandateId: string, revokedAt: string): Promise<CardMandateRecord>;
|
|
130
143
|
/**
|
|
131
144
|
* ONE SPENDING LIMIT: adopt the ceiling the SERVER actually approved.
|
|
132
145
|
*
|
|
@@ -203,6 +203,7 @@ export class MandateLedger {
|
|
|
203
203
|
m.currencyCode.toUpperCase() === query.currencyCode.toUpperCase() &&
|
|
204
204
|
!m.unhonoredAt &&
|
|
205
205
|
!m.registerFailedAt &&
|
|
206
|
+
!m.serverRevokedAt &&
|
|
206
207
|
!isExpired(m, now) &&
|
|
207
208
|
hasDrawCountHeadroom(m) &&
|
|
208
209
|
remainingMinor(m) >= query.amountMinor);
|
|
@@ -252,6 +253,27 @@ export class MandateLedger {
|
|
|
252
253
|
return record;
|
|
253
254
|
});
|
|
254
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Retire a locally cached mandate after the authenticated account API proves
|
|
258
|
+
* it was revoked. This is idempotent and monotonic: an owner revocation can
|
|
259
|
+
* never be undone by a later stale/local read.
|
|
260
|
+
*/
|
|
261
|
+
markServerRevoked(mandateId, revokedAt) {
|
|
262
|
+
return this.run(async () => {
|
|
263
|
+
const parsed = new Date(revokedAt);
|
|
264
|
+
if (Number.isNaN(parsed.getTime()))
|
|
265
|
+
throw new Error('server revocation timestamp is invalid');
|
|
266
|
+
const file = await this.load();
|
|
267
|
+
const record = file.mandates.find((m) => m.mandateId === mandateId);
|
|
268
|
+
if (!record)
|
|
269
|
+
throw new Error(`no such mandate ${mandateId}`);
|
|
270
|
+
if (!record.serverRevokedAt) {
|
|
271
|
+
record.serverRevokedAt = parsed.toISOString();
|
|
272
|
+
await this.save(file);
|
|
273
|
+
}
|
|
274
|
+
return record;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
255
277
|
/**
|
|
256
278
|
* ONE SPENDING LIMIT: adopt the ceiling the SERVER actually approved.
|
|
257
279
|
*
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Where this device's checkout receipts live. Owned by its own module so the
|
|
5
|
+
* writer (cli-engine.ts) and the reader (confirmed-merchants.ts) can never
|
|
6
|
+
* drift onto two paths.
|
|
7
|
+
*/
|
|
8
|
+
export const RECEIPT_DIR = join(homedir(), '.visa-mcp', 'checkout-receipts');
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { EvidenceStep } from './evidence.js';
|
|
2
2
|
import type { CheckoutMode, CheckoutOutcome, CheckoutResult, CredentialLifecycle, CredentialTiming } from './executor.js';
|
|
3
3
|
import type { VicConfirmationReport } from './vic-confirmation.js';
|
|
4
|
-
|
|
4
|
+
/** Published v1 artifact shape. Retained so patch releases remain readable/typable. */
|
|
5
|
+
export type CheckoutReceiptV1 = {
|
|
5
6
|
schema: 'checkout-agent-receipt/v1';
|
|
6
7
|
recordedAt: string;
|
|
7
8
|
mode: CheckoutMode;
|
|
@@ -32,6 +33,42 @@ export type CheckoutReceipt = {
|
|
|
32
33
|
snapshotSummary: string | null;
|
|
33
34
|
};
|
|
34
35
|
};
|
|
36
|
+
export type CheckoutReceiptV2 = {
|
|
37
|
+
schema: 'checkout-agent-receipt/v2';
|
|
38
|
+
recordedAt: string;
|
|
39
|
+
merchant: {
|
|
40
|
+
name: string;
|
|
41
|
+
host: string;
|
|
42
|
+
checkoutUrl: string | null;
|
|
43
|
+
};
|
|
44
|
+
transaction: {
|
|
45
|
+
amount: string;
|
|
46
|
+
amountMinor: number;
|
|
47
|
+
currency: string;
|
|
48
|
+
};
|
|
49
|
+
outcome: CheckoutOutcome;
|
|
50
|
+
agent: {
|
|
51
|
+
name: string;
|
|
52
|
+
} | null;
|
|
53
|
+
rail: {
|
|
54
|
+
type: 'card';
|
|
55
|
+
cardLast4: string | null;
|
|
56
|
+
};
|
|
57
|
+
network: {
|
|
58
|
+
confirmation: 'APPROVED' | 'DECLINED' | null;
|
|
59
|
+
};
|
|
60
|
+
/** Local reviewed-attempt identifier. Keeps filenames stable across schema versions. */
|
|
61
|
+
receiptId: string | null;
|
|
62
|
+
/** Merchant confirmation reference only; never substituted with an internal review id. */
|
|
63
|
+
reference: string | null;
|
|
64
|
+
recovery: {
|
|
65
|
+
required: boolean;
|
|
66
|
+
retrySafe: boolean;
|
|
67
|
+
action: string;
|
|
68
|
+
actions: string[];
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export type CheckoutReceipt = CheckoutReceiptV1 | CheckoutReceiptV2;
|
|
35
72
|
export type ReceiptWriteReport = {
|
|
36
73
|
written: true;
|
|
37
74
|
path: string;
|
|
@@ -46,6 +83,7 @@ export declare function buildReceipt(input: {
|
|
|
46
83
|
merchant: {
|
|
47
84
|
name: string;
|
|
48
85
|
host: string;
|
|
86
|
+
url?: string;
|
|
49
87
|
};
|
|
50
88
|
transaction: {
|
|
51
89
|
amount: string;
|
|
@@ -54,9 +92,11 @@ export declare function buildReceipt(input: {
|
|
|
54
92
|
};
|
|
55
93
|
result: CheckoutResult;
|
|
56
94
|
vicConfirmation: VicConfirmationReport | null;
|
|
95
|
+
agentName?: string;
|
|
96
|
+
cardLast4?: string;
|
|
57
97
|
/** Injectable for tests; defaults to now. */
|
|
58
98
|
recordedAt?: Date;
|
|
59
|
-
}):
|
|
99
|
+
}): CheckoutReceiptV2;
|
|
60
100
|
/**
|
|
61
101
|
* Defense-in-depth scrub before anything touches disk: any standalone
|
|
62
102
|
* 12–19-digit run that passes Luhn is replaced — contiguous OR separated by
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { KNOWN_MERCHANT_IDENTITIES } from './known-merchants.js';
|
|
3
4
|
import { submitClickedWithoutConfirmation } from './live-fill-approval.js';
|
|
4
5
|
/**
|
|
5
6
|
* What the operator still owes after this run. Empty for a clean dry-run fill
|
|
@@ -29,23 +30,37 @@ function reconciliationFor(result, vicConfirmation) {
|
|
|
29
30
|
}
|
|
30
31
|
export function buildReceipt(input) {
|
|
31
32
|
const { result } = input;
|
|
33
|
+
const reconciliation = reconciliationFor(result, input.vicConfirmation);
|
|
34
|
+
const fallbackAction = input.mode === 'dry-run'
|
|
35
|
+
? 'Dry run only — nothing was submitted; safe to run again.'
|
|
36
|
+
: result.outcome === 'confirmed'
|
|
37
|
+
? 'No action needed.'
|
|
38
|
+
: result.outcome === 'declined'
|
|
39
|
+
? 'Review the decline before trying again.'
|
|
40
|
+
: 'Review this outcome before retrying.';
|
|
41
|
+
const recoveryActions = reconciliation.reasons.length > 0 ? reconciliation.reasons : [fallbackAction];
|
|
42
|
+
const cardLast4 = input.cardLast4 && /^\d{4}$/.test(input.cardLast4) ? input.cardLast4 : null;
|
|
43
|
+
const checkoutUrl = input.merchant.url && KNOWN_MERCHANT_IDENTITIES[input.merchant.url] ? input.merchant.url : null;
|
|
44
|
+
const networkConfirmation = input.vicConfirmation?.posted === true ? input.vicConfirmation.transactionStatus : null;
|
|
32
45
|
return {
|
|
33
|
-
schema: 'checkout-agent-receipt/
|
|
46
|
+
schema: 'checkout-agent-receipt/v2',
|
|
34
47
|
recordedAt: (input.recordedAt ?? new Date()).toISOString(),
|
|
35
|
-
|
|
36
|
-
reviewId: input.reviewId,
|
|
37
|
-
merchant: { name: input.merchant.name, host: input.merchant.host },
|
|
48
|
+
merchant: { name: input.merchant.name, host: input.merchant.host, checkoutUrl },
|
|
38
49
|
transaction: input.transaction,
|
|
39
50
|
outcome: result.outcome,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
agent: input.agentName ? { name: input.agentName } : null,
|
|
52
|
+
rail: { type: 'card', cardLast4 },
|
|
53
|
+
network: { confirmation: networkConfirmation },
|
|
54
|
+
receiptId: input.reviewId,
|
|
55
|
+
reference: result.confirmationRef ?? null,
|
|
56
|
+
recovery: {
|
|
57
|
+
required: reconciliation.required,
|
|
58
|
+
// A clean dry-run did not submit anything, so repeating inspection is
|
|
59
|
+
// safe. Every submit outcome requires either no retry or owner review.
|
|
60
|
+
retrySafe: input.mode === 'dry-run' && !reconciliation.required,
|
|
61
|
+
action: recoveryActions[0],
|
|
62
|
+
actions: recoveryActions,
|
|
63
|
+
},
|
|
49
64
|
};
|
|
50
65
|
}
|
|
51
66
|
function luhnValid(digits) {
|
|
@@ -88,7 +103,8 @@ export function serializeReceipt(receipt) {
|
|
|
88
103
|
}
|
|
89
104
|
export function receiptFileName(receipt) {
|
|
90
105
|
const ts = receipt.recordedAt.replace(/[:.]/g, '-');
|
|
91
|
-
const
|
|
106
|
+
const receiptId = receipt.schema === 'checkout-agent-receipt/v1' ? receipt.reviewId : receipt.receiptId;
|
|
107
|
+
const review = (receiptId ?? 'no-review').replace(/[^A-Za-z0-9-]/g, '');
|
|
92
108
|
return `receipt-${ts}-${review || 'no-review'}.json`;
|
|
93
109
|
}
|
|
94
110
|
/**
|