@nuanu-ai/agentify-contracts 0.3.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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +32 -0
- package/dist/api.d.ts +1948 -0
- package/dist/api.js +956 -0
- package/dist/card.d.ts +423 -0
- package/dist/card.js +700 -0
- package/dist/envelope.d.ts +174 -0
- package/dist/envelope.js +101 -0
- package/dist/events.d.ts +56 -0
- package/dist/events.js +54 -0
- package/dist/evm-address.d.ts +77 -0
- package/dist/evm-address.js +265 -0
- package/dist/handler.d.ts +90 -0
- package/dist/handler.js +102 -0
- package/dist/index.d.ts +1008 -0
- package/dist/index.js +167 -0
- package/dist/merchant.d.ts +281 -0
- package/dist/merchant.js +433 -0
- package/dist/order-status.d.ts +47 -0
- package/dist/order-status.js +102 -0
- package/dist/order.d.ts +50 -0
- package/dist/order.js +78 -0
- package/dist/param-spec.d.ts +145 -0
- package/dist/param-spec.js +147 -0
- package/dist/primitives.d.ts +103 -0
- package/dist/primitives.js +130 -0
- package/dist/quote.d.ts +79 -0
- package/dist/quote.js +92 -0
- package/dist/receipt.d.ts +79 -0
- package/dist/receipt.js +83 -0
- package/dist/results.d.ts +230 -0
- package/dist/results.js +269 -0
- package/dist/selling.d.ts +24 -0
- package/dist/selling.js +35 -0
- package/package.json +46 -0
package/dist/merchant.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a merchant comes to exist, the name their products are sold under, the
|
|
3
|
+
* wallet their sales are paid into, and the keys they open the door with.
|
|
4
|
+
*
|
|
5
|
+
* The first two belong together because registering is the act that produces
|
|
6
|
+
* both: one call makes the merchant and the key its cabinet will call as them
|
|
7
|
+
* with, and what comes back carries that key once. Split across two files, a
|
|
8
|
+
* reader working out what registering leaves a merchant holding would have to
|
|
9
|
+
* read both to find that it is a key of a kind no list here carries. The name is here for the same reason read the other way round — it is a
|
|
10
|
+
* fact about the merchant and about none of their cards, and the one question a
|
|
11
|
+
* reader arrives with is which of the two names a merchant has is which.
|
|
12
|
+
*
|
|
13
|
+
* Two rules run through the file and are worth saying once.
|
|
14
|
+
*
|
|
15
|
+
* The secret appears in exactly three documents, and every one of them is the
|
|
16
|
+
* answer to a call that has just made a key. Nothing that is ever drawn again —
|
|
17
|
+
* the list a merchant reads, the row that comes back from disabling one — can
|
|
18
|
+
* carry it, and the shapes below refuse it rather than merely omit it. What is
|
|
19
|
+
* kept on our side is a digest, so there is nothing to put in those documents
|
|
20
|
+
* even if somebody wanted to.
|
|
21
|
+
*
|
|
22
|
+
* And a key that has been revoked stays in the list. `disabled_at` is a moment
|
|
23
|
+
* rather than a flag, and it is always present: after an incident the question
|
|
24
|
+
* is when a key stopped working, and a list that dropped the key answers
|
|
25
|
+
* nothing, while a flag answers only half.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { ServiceNameSchema } from "./card.js";
|
|
29
|
+
import { EvmAddressSchema } from "./evm-address.js";
|
|
30
|
+
import { IdentifierSchema, TimestampSchema } from "./primitives.js";
|
|
31
|
+
/**
|
|
32
|
+
* What a merchant calls one of their keys, so one of several can be told from
|
|
33
|
+
* the others.
|
|
34
|
+
*
|
|
35
|
+
* Not empty, and not padded with spaces. Both defeat the only thing a label is
|
|
36
|
+
* for: a blank label is a row in a list with nothing in it, and a space at
|
|
37
|
+
* either end makes two labels that look identical wherever they are printed
|
|
38
|
+
* while being two different strings.
|
|
39
|
+
*
|
|
40
|
+
* What it does not do is worth saying, because a reader could take the rule for
|
|
41
|
+
* more than it is. Nothing here bounds the length, since no channel outside us
|
|
42
|
+
* carries this text and a number would be a bound nobody's format asks for, and
|
|
43
|
+
* nothing here holds a label to one alphabet: unlike the name a discovery
|
|
44
|
+
* catalog lists a seller under, a label never leaves the merchant's own
|
|
45
|
+
* screens, so a label written in Cyrillic is a label. A character that shows
|
|
46
|
+
* nothing is allowed through as well, which is the omission a reader is likeliest
|
|
47
|
+
* to be surprised by — an identifier refuses one, because an identifier is
|
|
48
|
+
* matched on and two that look alike are two keys nobody can tell apart, and a
|
|
49
|
+
* label is only ever read, so the same character makes one odd-looking row.
|
|
50
|
+
*/
|
|
51
|
+
const KeyLabelSchema = z
|
|
52
|
+
.string()
|
|
53
|
+
.regex(/^\S(?:[\s\S]*\S)?$/u, "a label must not be empty or padded with spaces");
|
|
54
|
+
/**
|
|
55
|
+
* The key itself, in the only form its owner will ever see it.
|
|
56
|
+
*
|
|
57
|
+
* No whitespace anywhere in it, and that bound belongs to the transport rather
|
|
58
|
+
* than to us: a key travels as a bearer token, and the reader on the other side
|
|
59
|
+
* takes everything up to the first space. A secret with a space in it would
|
|
60
|
+
* arrive as a prefix of itself and open nothing, and the merchant would spend
|
|
61
|
+
* an afternoon on a key that looks perfectly good.
|
|
62
|
+
*
|
|
63
|
+
* Nothing here says how long a key is or what it starts with. Those are the
|
|
64
|
+
* gateway's, which is the side that generates them, and a shape written down
|
|
65
|
+
* here would be a promise about a format we mean to be free to change.
|
|
66
|
+
*/
|
|
67
|
+
const KeySecretSchema = z
|
|
68
|
+
.string()
|
|
69
|
+
.regex(/^\S+$/, "a key travels as a bearer token, so it carries no whitespace and is not empty");
|
|
70
|
+
/**
|
|
71
|
+
* The code that stands in the door of registration.
|
|
72
|
+
*
|
|
73
|
+
* It is one value out of the gateway's configuration, handed to a person along
|
|
74
|
+
* with the address of the site (ADR-0014 §3). All this shape asks is that
|
|
75
|
+
* something was actually typed: a form submitted with an empty field is a
|
|
76
|
+
* mistake at the keyboard rather than a wrong code, and the two are worth
|
|
77
|
+
* telling apart before anything is compared.
|
|
78
|
+
*/
|
|
79
|
+
const InvitationSchema = z
|
|
80
|
+
.string()
|
|
81
|
+
.regex(/\S/, "an invitation is the code handed over with the address of the site");
|
|
82
|
+
/**
|
|
83
|
+
* One key a merchant holds, as they read it.
|
|
84
|
+
*
|
|
85
|
+
* The secret is not here and cannot be put here. This is the document a screen
|
|
86
|
+
* lists, drawn again on every visit, and the secret is shown once by the call
|
|
87
|
+
* that made the key and never again — so a shape that could carry one is a
|
|
88
|
+
* shape that eventually does.
|
|
89
|
+
*/
|
|
90
|
+
export const MerchantKeySchema = z
|
|
91
|
+
.strictObject({
|
|
92
|
+
id: IdentifierSchema,
|
|
93
|
+
label: KeyLabelSchema,
|
|
94
|
+
created_at: TimestampSchema,
|
|
95
|
+
/**
|
|
96
|
+
* The last call this key was seen on, and null where none is recorded.
|
|
97
|
+
*
|
|
98
|
+
* It is behind the truth by minutes and says so in the description: the
|
|
99
|
+
* instant is refreshed only once the one on the row has gone stale, because
|
|
100
|
+
* the alternative is a write in front of every purchase for a fact somebody
|
|
101
|
+
* reads when they are deciding what to revoke. What it answers is "this key
|
|
102
|
+
* was in use around then".
|
|
103
|
+
*
|
|
104
|
+
* Null is the absence of a record and not the absence of calls, and nothing
|
|
105
|
+
* here tells a reader which. A key made before this gateway began writing
|
|
106
|
+
* the field carries the same null as a key nobody has ever called with, and
|
|
107
|
+
* there is no field beside this one that separates them — one would be a
|
|
108
|
+
* permanent question on a public surface for an ambiguity that lasts weeks,
|
|
109
|
+
* so what carries it is the sentence a screen puts under the column.
|
|
110
|
+
*/
|
|
111
|
+
last_used_at: TimestampSchema.nullable(),
|
|
112
|
+
/**
|
|
113
|
+
* When this key was revoked, and null while it still opens the door.
|
|
114
|
+
*
|
|
115
|
+
* Required and nullable rather than optional, because the two readings of
|
|
116
|
+
* an absent field are "this key works" and "nobody wrote it down", and a
|
|
117
|
+
* screen that guessed wrong would show a revoked key as live. The instant
|
|
118
|
+
* rather than a flag: a flag answers whether the key works, and the
|
|
119
|
+
* question somebody asks afterwards is when it stopped.
|
|
120
|
+
*/
|
|
121
|
+
disabled_at: TimestampSchema.nullable(),
|
|
122
|
+
})
|
|
123
|
+
.meta({
|
|
124
|
+
description: "One key a merchant opens the door with, as they read it: what they called it, when it was made, when a call was last seen on it, and when it was revoked. A null disabled_at means the key still works; the field is always present, because an absent one is a silence a reader cannot tell from an oversight. The key itself is not in this document and never will be — what is kept is a digest of it, so nothing here or anywhere else can show a merchant their key a second time. last_used_at is not the instant of the last call and is not offered as one: it is refreshed at most once every few minutes per key, so a key under constant use carries a time that far behind, which is the price of not writing to the database on every call made with it. Null there means no call has been recorded against this key, which is not the same as no call having been made: a key made before this gateway began recording carries that same null, and nothing in this document tells such a key from one nobody has ever used. Nor is it a ledger of calls or an exact one: a mark that could not be written is swallowed rather than refusing the call it belonged to, so this is what was recorded and not everything that happened.",
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* The keys one merchant made for their own code, and the one this call was made
|
|
128
|
+
* with.
|
|
129
|
+
*
|
|
130
|
+
* `this_call` is the field the list cannot be assembled without, and the reason
|
|
131
|
+
* is a rule in the route rather than anything about the shape: a merchant
|
|
132
|
+
* cannot disable the key their own call was made with (ADR-0014 §5). A screen
|
|
133
|
+
* that did not know which of these that was would offer a button the route
|
|
134
|
+
* refuses, on the one page where being refused looks like the product being
|
|
135
|
+
* broken.
|
|
136
|
+
*
|
|
137
|
+
* It is not always one of the keys beside it, and that is the thing a reader is
|
|
138
|
+
* likeliest to assume and be wrong about. A cabinet calls with a key made for a
|
|
139
|
+
* cabinet, and those are in nobody's list, so a client that looked this
|
|
140
|
+
* identifier up among the rows would find nothing — which is an answer rather
|
|
141
|
+
* than an error, and a screen has to be built for it.
|
|
142
|
+
*
|
|
143
|
+
* An object rather than a bare array, for that reason before any other — an
|
|
144
|
+
* array has nowhere to put it.
|
|
145
|
+
*/
|
|
146
|
+
export const MerchantKeyListSchema = z
|
|
147
|
+
.strictObject({
|
|
148
|
+
/**
|
|
149
|
+
* The keys this merchant made for their own code, the revoked ones among
|
|
150
|
+
* them.
|
|
151
|
+
*
|
|
152
|
+
* The keys a cabinet holds are not here and never will be: the merchant did
|
|
153
|
+
* not issue one and has nothing to do with one. A list is what somebody
|
|
154
|
+
* acts on, and a row nobody has any business acting on is a row that only
|
|
155
|
+
* raises the question of why it will not go away.
|
|
156
|
+
*/
|
|
157
|
+
keys: z.array(MerchantKeySchema),
|
|
158
|
+
/** The key the request carrying this answer was made with. */
|
|
159
|
+
this_call: IdentifierSchema,
|
|
160
|
+
})
|
|
161
|
+
.meta({
|
|
162
|
+
description: "The keys one merchant made for their own code, working and revoked together, and the identifier of the key this very call was made with. That last field is here because a merchant cannot disable the key they are holding: without it a screen would offer a button the gateway refuses. It is not always among the keys listed — a cabinet calls with a key of its own, and those are in no list here — so a client matching it against the rows has to be built for finding none. The keys a cabinet holds are left out entirely: they are not issued by the merchant and cannot be revoked by them. This document does not say whether it is the whole list either — paging is not designed, and the absence of a field about it is not a promise that there is no more.",
|
|
163
|
+
});
|
|
164
|
+
/** What a merchant sends to have a key made. */
|
|
165
|
+
export const IssueKeyRequestSchema = z
|
|
166
|
+
.strictObject({
|
|
167
|
+
label: KeyLabelSchema,
|
|
168
|
+
})
|
|
169
|
+
.meta({
|
|
170
|
+
description: "What a merchant asks for when they want another key: the name they will know it by, and nothing else. There is nowhere here to put a secret, because a key is generated rather than chosen — one somebody picks is one somebody reuses somewhere else.",
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* A key that has just been made: the row, and the secret, once.
|
|
174
|
+
*
|
|
175
|
+
* Both halves are needed by the caller. Without the secret there is no key to
|
|
176
|
+
* hand to a worker; without the row there is a string and nothing to say which
|
|
177
|
+
* of the merchant's keys it is, which is the thing they need to disable it
|
|
178
|
+
* later.
|
|
179
|
+
*/
|
|
180
|
+
export const IssuedKeySchema = z
|
|
181
|
+
.strictObject({
|
|
182
|
+
key: MerchantKeySchema,
|
|
183
|
+
/** The only moment this is readable. Nothing on our side keeps it. */
|
|
184
|
+
secret: KeySecretSchema,
|
|
185
|
+
})
|
|
186
|
+
.meta({
|
|
187
|
+
description: "A key as it comes back from being issued: the row a merchant will see in their list from now on, and the key itself. It carries the key once. Three answers in this contract carry one — this, what registering gives back, and the key a cabinet asks for — and nothing else does, because what is written down on our side is a digest. A key that is lost is replaced by a new one rather than read back.",
|
|
188
|
+
});
|
|
189
|
+
/**
|
|
190
|
+
* A key that has been revoked, as it now stands.
|
|
191
|
+
*
|
|
192
|
+
* An object rather than the key itself, so the day this answer has to say
|
|
193
|
+
* anything beside the key — how many of the merchant's keys still work, say — it
|
|
194
|
+
* grows a field instead of changing shape under every reader.
|
|
195
|
+
*/
|
|
196
|
+
export const DisabledKeySchema = z
|
|
197
|
+
.strictObject({
|
|
198
|
+
key: MerchantKeySchema,
|
|
199
|
+
})
|
|
200
|
+
.meta({
|
|
201
|
+
description: "The key that was just revoked, with the instant it stopped working on it, so a merchant reads back what happened rather than taking the call's word for it. Revoking a key that was already revoked answers this same way and keeps the first instant, because that is the true one and a retry after a dropped connection must not rewrite it.",
|
|
202
|
+
});
|
|
203
|
+
/**
|
|
204
|
+
* A key made for a cabinet, which is the secret and nothing else.
|
|
205
|
+
*
|
|
206
|
+
* Every other answer that makes a key carries the row beside it, and this one
|
|
207
|
+
* cannot. A key made for a cabinet is in no merchant's list — they did not
|
|
208
|
+
* issue it and have no reason to know it exists — so an identifier here would
|
|
209
|
+
* name a row that no screen of theirs draws and no call of theirs reaches: not
|
|
210
|
+
* the list it is absent from, and not the revoking, which takes the keys a
|
|
211
|
+
* merchant issued and refuses this kind by name. What the caller does with this
|
|
212
|
+
* is put it on the row of whoever just signed in, and that is the whole of what
|
|
213
|
+
* it needs.
|
|
214
|
+
*/
|
|
215
|
+
export const CabinetKeySchema = z
|
|
216
|
+
.strictObject({
|
|
217
|
+
/** The only moment this is readable. Nothing on our side keeps it. */
|
|
218
|
+
secret: KeySecretSchema,
|
|
219
|
+
})
|
|
220
|
+
.meta({
|
|
221
|
+
description: "A key made for a cabinet to call as one merchant, carried once and readable nowhere afterwards. There is no row beside it and there is nothing to put one: a key made this way is in no merchant's list of keys, and the call that revokes a key refuses this kind by name — so an identifier for it would name something no answer shows and no call acts on. Whoever asked for this holds it until they ask for another.",
|
|
222
|
+
});
|
|
223
|
+
/**
|
|
224
|
+
* That the key a call was made with is gone.
|
|
225
|
+
*
|
|
226
|
+
* A constant, and deliberately: the call has one outcome. It removes the key it
|
|
227
|
+
* was made with and no other, so there is nothing to count — a number here
|
|
228
|
+
* could only be about rows this call cannot reach — and nothing to name, since
|
|
229
|
+
* the caller is holding the only key it names. What is left to say is that it
|
|
230
|
+
* is done, and it is said in a field rather than left to a status code so that
|
|
231
|
+
* a client reads one document and not two kinds of evidence.
|
|
232
|
+
*/
|
|
233
|
+
export const ForgottenCabinetKeySchema = z
|
|
234
|
+
.strictObject({
|
|
235
|
+
/** The key this call was made with no longer exists. */
|
|
236
|
+
forgotten: z.literal(true),
|
|
237
|
+
})
|
|
238
|
+
.meta({
|
|
239
|
+
description: "That the key this call was made with has been removed, which is the only thing this call does. There is nothing to count and nothing to name: it reaches one key, the one in the caller's hand, and the caller already knows which that is.",
|
|
240
|
+
});
|
|
241
|
+
/**
|
|
242
|
+
* What a merchant's products are sold under, as the merchant reads it back.
|
|
243
|
+
*
|
|
244
|
+
* Null is the fact "nobody has chosen one", which is every merchant on the day
|
|
245
|
+
* they register and is the state they stay in until they do. It is a value
|
|
246
|
+
* rather than an absent field, because an absent field would be
|
|
247
|
+
* indistinguishable from a client that dropped it, and a settings screen
|
|
248
|
+
* reading the second as the first would tell a merchant they are listed under
|
|
249
|
+
* nothing while they are listed under something.
|
|
250
|
+
*
|
|
251
|
+
* The name is held to the rule of the catalog that carries it, which is where
|
|
252
|
+
* this name is going: at most thirty-two characters of printable ASCII. That
|
|
253
|
+
* bound is not ours and is not about our own storage — the catalog drops what
|
|
254
|
+
* it cannot render and tells nobody, so a name refused here is a name the
|
|
255
|
+
* merchant is told about, and a name accepted here and dropped there is a
|
|
256
|
+
* seller trading under something they did not choose.
|
|
257
|
+
*/
|
|
258
|
+
export const SellerNameSchema = z
|
|
259
|
+
.strictObject({
|
|
260
|
+
/** What buyers read beside this merchant's products, or nothing. */
|
|
261
|
+
seller_name: ServiceNameSchema.nullable(),
|
|
262
|
+
})
|
|
263
|
+
.meta({
|
|
264
|
+
description: "The name a merchant's products are sold under: what a discovery catalog lists them under and what a buyer's agent is shown beside the price. Null means nobody has chosen one, which is where every merchant starts. The field is always present rather than left out when there is no name: an absent field would be indistinguishable from a client that dropped it. What a name may be is the catalog's rule rather than ours — at most 32 characters of printable ASCII — because a name outside it is dropped there in silence, so it is refused here where somebody is told. A merchant with no name cannot publish a card: a card published without one reaches a buyer's agent inside a payment request that names no seller at all.",
|
|
265
|
+
});
|
|
266
|
+
/**
|
|
267
|
+
* What a merchant sends to change what their products are sold under.
|
|
268
|
+
*
|
|
269
|
+
* The same field held to the same rule, and one difference: there is no null.
|
|
270
|
+
* A merchant can go from having no name to having one and from one name to
|
|
271
|
+
* another, and not back. Having none is a starting state rather than a setting,
|
|
272
|
+
* because a payment request names the seller and there would be nobody to name:
|
|
273
|
+
* every card they had already published would come off sale, which is an end to
|
|
274
|
+
* their selling arriving under the name of editing a setting. What somebody
|
|
275
|
+
* reaching for that actually wants is one of two other acts: a different name,
|
|
276
|
+
* which is this same call, or an end to selling, which is the pause — and the
|
|
277
|
+
* pause leaves their cards where they can find them again.
|
|
278
|
+
*
|
|
279
|
+
* So it is two documents rather than one, and a cabinet still reads back the
|
|
280
|
+
* shape it sent. The message on a null is written here rather than left to a
|
|
281
|
+
* type error, because "expected string, received null" describes the shape and
|
|
282
|
+
* says nothing about which act the sender was reaching for.
|
|
283
|
+
*/
|
|
284
|
+
export const SellerNameRequestSchema = z
|
|
285
|
+
.strictObject({
|
|
286
|
+
/**
|
|
287
|
+
* What buyers are to read beside this merchant's products.
|
|
288
|
+
*
|
|
289
|
+
* The rule lives once, in `ServiceNameSchema`, and this reaches it through
|
|
290
|
+
* a string that carries its own words for "this is not a name at all". A
|
|
291
|
+
* second copy of the length and the alphabet written out here is the copy
|
|
292
|
+
* that goes stale.
|
|
293
|
+
*/
|
|
294
|
+
seller_name: z
|
|
295
|
+
.string({
|
|
296
|
+
// A field that is missing is a client with a bug and a field holding
|
|
297
|
+
// null is a client with a misunderstanding. Only the second gets this
|
|
298
|
+
// sentence; the first falls through to the ordinary words about a
|
|
299
|
+
// field that is not there, which is what its author needs to read.
|
|
300
|
+
error: (issue) => issue.input === undefined
|
|
301
|
+
? undefined
|
|
302
|
+
: "a seller name cannot be taken away, only changed: a merchant who wants to stop being listed pauses their selling, which leaves their cards where they can put them back on sale",
|
|
303
|
+
})
|
|
304
|
+
.pipe(ServiceNameSchema),
|
|
305
|
+
})
|
|
306
|
+
.meta({
|
|
307
|
+
description: "What a merchant sends to change the name their products are sold under. The same rule as the answer — at most 32 characters of printable ASCII, the catalog's rule rather than ours — and one difference: null is refused. A merchant goes from no name to a name and from one name to another, never back to none, because a payment request names the seller and there would be nobody to name: every card they have published would come off sale, which is an end to their selling arriving under the name of editing a setting. Somebody reaching for null wants one of two other things: a different name, which is this call with a different value, or an end to selling, which is the pause.",
|
|
308
|
+
});
|
|
309
|
+
/**
|
|
310
|
+
* The wallet a merchant's sales are paid into, as the merchant reads it back.
|
|
311
|
+
*
|
|
312
|
+
* Payments here are not held by anybody on the way: a buyer's agent pays the
|
|
313
|
+
* merchant's own address directly, and this is that address. There is no
|
|
314
|
+
* account of theirs on our side with a balance in it and no moment at which
|
|
315
|
+
* their money is ours, which is why this field exists at all — without it there
|
|
316
|
+
* would have to be.
|
|
317
|
+
*
|
|
318
|
+
* Null is the fact "nobody has said where the money goes", which is every
|
|
319
|
+
* merchant on the day they register. It is a value rather than an absent field
|
|
320
|
+
* for the reason the seller name is: a settings screen cannot tell a silence
|
|
321
|
+
* from a client that dropped the field, and reading the second as the first
|
|
322
|
+
* tells a merchant they are set up to be paid when nothing of theirs can be.
|
|
323
|
+
*
|
|
324
|
+
* What comes back is always the spelling a wallet shows, whichever of the two
|
|
325
|
+
* accepted spellings was sent. The door takes both and everything behind it
|
|
326
|
+
* holds one (ADR-0017), and this is the one for a reason that is about the
|
|
327
|
+
* person rather than about the storage: a merchant pastes forty characters out
|
|
328
|
+
* of their wallet and reads them back on a settings screen, and handed the same
|
|
329
|
+
* address in lower case they cannot tell it from a different address without
|
|
330
|
+
* comparing character by character. On the one field money is sent to, that
|
|
331
|
+
* glance is the whole of the checking anybody does.
|
|
332
|
+
*/
|
|
333
|
+
export const PayoutWalletSchema = z
|
|
334
|
+
.strictObject({
|
|
335
|
+
/** Where this merchant's sales are paid, or nothing at all. */
|
|
336
|
+
payout_wallet: EvmAddressSchema.nullable(),
|
|
337
|
+
})
|
|
338
|
+
.meta({
|
|
339
|
+
description: "The address a merchant's sales are paid into. Payments are not held by anybody on the way: a buyer's agent pays this address directly, and it is the payTo of every payment request made for this merchant's products. Null means nobody has set one, which is where every merchant starts; the field is always present rather than left out, because an absent field is indistinguishable from a client that dropped it. The address comes back in the mixed-case spelling a wallet shows, whichever of the two accepted spellings was sent — so what a merchant reads back on a screen is character for character what they copied out of their wallet. On a deployment that settles on a real chain a merchant with no wallet here cannot publish a card, because the money from that card's sales would have nowhere to go.",
|
|
340
|
+
});
|
|
341
|
+
/**
|
|
342
|
+
* What a merchant sends to change where their sales are paid.
|
|
343
|
+
*
|
|
344
|
+
* The same field held to the same rule, and one difference, which is the same
|
|
345
|
+
* difference the seller name has and rests on something harder. There is no
|
|
346
|
+
* null. A merchant goes from having no wallet to having one and from one wallet
|
|
347
|
+
* to another, and not back: a merchant who took their address away would keep
|
|
348
|
+
* every card they had already published on sale, and the payment request an
|
|
349
|
+
* agent is answered with cannot be built at all without an address — so the
|
|
350
|
+
* products would stop being buyable and nothing anywhere would say why. What
|
|
351
|
+
* somebody reaching for null actually wants is one of two other acts: a
|
|
352
|
+
* different address, which is this same call, or an end to selling, which is
|
|
353
|
+
* the pause, and the pause leaves their cards where they can put them back.
|
|
354
|
+
*
|
|
355
|
+
* There is nowhere here to put a key, and a document carrying one is refused
|
|
356
|
+
* rather than trimmed. This contract knows where a merchant is paid and has no
|
|
357
|
+
* business knowing anything that could spend it.
|
|
358
|
+
*/
|
|
359
|
+
export const PayoutWalletRequestSchema = z
|
|
360
|
+
.strictObject({
|
|
361
|
+
/**
|
|
362
|
+
* Where this merchant's sales are to be paid.
|
|
363
|
+
*
|
|
364
|
+
* The rule lives once, in `EvmAddressSchema`, and this reaches it through a
|
|
365
|
+
* string that carries its own words for "this is not an address at all".
|
|
366
|
+
*/
|
|
367
|
+
payout_wallet: z
|
|
368
|
+
.string({
|
|
369
|
+
// A field that is missing is a client with a bug and a field holding
|
|
370
|
+
// null is a client with a misunderstanding. Only the second gets this
|
|
371
|
+
// sentence; the first falls through to the ordinary words about a
|
|
372
|
+
// field that is not there.
|
|
373
|
+
error: (issue) => issue.input === undefined
|
|
374
|
+
? undefined
|
|
375
|
+
: "a payout wallet cannot be taken away, only changed: a merchant who wants to stop being paid pauses their selling, which leaves their cards where they can put them back on sale — a merchant with cards on sale and no wallet has products a payment request cannot even be written for",
|
|
376
|
+
})
|
|
377
|
+
.pipe(EvmAddressSchema),
|
|
378
|
+
})
|
|
379
|
+
.meta({
|
|
380
|
+
description: "What a merchant sends to set or change the address their sales are paid into. The same rule as the answer — 0x and forty hexadecimal characters, in lower case or in the exact mixed-case spelling a wallet shows — and one difference: null is refused. A merchant goes from no wallet to a wallet and from one wallet to another, never back to none, because their published cards stay on sale and a payment request for one of them cannot be written without an address. Somebody reaching for null wants either a different address, which is this call with a different value, or an end to selling, which is the pause. Nothing here takes a private key or a seed phrase, and a document carrying one is refused rather than ignored: this contract knows where a merchant is paid and nothing that could spend it.",
|
|
381
|
+
});
|
|
382
|
+
/**
|
|
383
|
+
* What somebody sends to become a merchant.
|
|
384
|
+
*
|
|
385
|
+
* One field, and what is not here is most of what is worth reading. The address
|
|
386
|
+
* and the password belong to the account rather than to the merchant, and they
|
|
387
|
+
* stay on the other side of the boundary (ADR-0014 §1) — a gateway that took
|
|
388
|
+
* either would be holding a person's credentials on the money path, which is
|
|
389
|
+
* what this route's whole shape is arranged to avoid.
|
|
390
|
+
*
|
|
391
|
+
* The name the seller's products are sold under is not here either, and that
|
|
392
|
+
* omission is a decision rather than a simplification. It is a public answer,
|
|
393
|
+
* and asking for it here asks for it at the one moment a merchant knows least:
|
|
394
|
+
* no products, no catalogue seen, no idea what the name is for. It is asked for
|
|
395
|
+
* on the screen after this one instead, where there is room to say why it
|
|
396
|
+
* matters, and it can be changed afterwards from the merchant's own settings.
|
|
397
|
+
*
|
|
398
|
+
* The shape refuses a name rather than ignoring one, because a field quietly
|
|
399
|
+
* dropped is a person believing they have chosen what buyers will read.
|
|
400
|
+
*/
|
|
401
|
+
export const RegistrationRequestSchema = z
|
|
402
|
+
.strictObject({
|
|
403
|
+
/** The code handed over with the address of the site. */
|
|
404
|
+
invitation: InvitationSchema,
|
|
405
|
+
})
|
|
406
|
+
.meta({
|
|
407
|
+
description: "What somebody sends to become a merchant: the invitation code they were given, and nothing else. The name their products are sold under is deliberately not here — it is a public answer, and asked for on the way in it is answered by somebody with no products and no idea what the name is for; it is set afterwards, and changed afterwards, through the merchant's own call for it. Nothing about an account is here either: an address and a password belong to whatever signs the person in, and are never sent to the gateway. A document carrying either is refused rather than trimmed, because a field accepted and dropped is somebody believing they said something.",
|
|
408
|
+
});
|
|
409
|
+
/**
|
|
410
|
+
* What registering answers with: a merchant and the key their cabinet will call
|
|
411
|
+
* as them with.
|
|
412
|
+
*
|
|
413
|
+
* The key is made for a cabinet rather than for the merchant's own code, and
|
|
414
|
+
* that is what the caller of this route is. So it is in no list: a merchant who
|
|
415
|
+
* has just registered has no keys of their own at all, and the first one they
|
|
416
|
+
* do have is one they ask for. No row travels beside the secret for the same
|
|
417
|
+
* reason no row appears in the list — the merchant did not issue it and cannot
|
|
418
|
+
* disable it, so an identifier for it would be a value with nothing to do.
|
|
419
|
+
*
|
|
420
|
+
* No name comes back either, because none was chosen. A merchant who has just
|
|
421
|
+
* registered is listed under nothing at all, and a field here would either be a
|
|
422
|
+
* name this call invented or a null that says the same thing at more length.
|
|
423
|
+
*/
|
|
424
|
+
export const RegisteredMerchantSchema = z
|
|
425
|
+
.strictObject({
|
|
426
|
+
/** The merchant that now exists, which every key and card of theirs names. */
|
|
427
|
+
merchant_id: IdentifierSchema,
|
|
428
|
+
/** The key itself, shown once, exactly as issuing one shows it. */
|
|
429
|
+
secret: KeySecretSchema,
|
|
430
|
+
})
|
|
431
|
+
.meta({
|
|
432
|
+
description: "What registering produced: the merchant, and the key whoever registered them will call as them with. The key is readable here and nowhere afterwards, so whoever made this call is the only party that can keep it. It is a key made for a cabinet rather than one of the merchant's own: it appears in no list of their keys and the call that revokes a key refuses its kind by name, so no row for it comes back here either. A merchant who has just registered has no keys of their own until they ask for one. The merchant is listed under no name yet and this answer carries none — the name their products are sold under is chosen afterwards, and until it is, publishing a card is refused. What this answer does not carry either is any notion of an account or a session: registering makes a merchant and a key, and whatever signs a person in is on the other side of this call.",
|
|
433
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What state an order is in, in the words an agent and a merchant both read.
|
|
3
|
+
*
|
|
4
|
+
* These are the state machine's own endings, and they live here because they
|
|
5
|
+
* are wire names: an agent asks what became of its purchase, a merchant
|
|
6
|
+
* restarting a worker asks what is still open, and both have to get the same
|
|
7
|
+
* answer. A second list kept anywhere else would be a second answer to one
|
|
8
|
+
* question, and the one the machine keeps would win without anybody noticing.
|
|
9
|
+
*
|
|
10
|
+
* Every value below says what the agent can be told truthfully, which is not
|
|
11
|
+
* always everything the machine knows. Where the two differ the value says so
|
|
12
|
+
* in its own words: `rejected` is coarser than the machine's states behind it,
|
|
13
|
+
* and `in_progress` covers every step of a purchase that has not finished —
|
|
14
|
+
* an order just sent to the merchant and one the merchant has taken on and
|
|
15
|
+
* owes a delivery for are one word here, because to the buyer they are one
|
|
16
|
+
* situation. A merchant who needs those apart is not reading this vocabulary;
|
|
17
|
+
* they are reading their own order, which carries the answer they gave.
|
|
18
|
+
*
|
|
19
|
+
* The word for a purchase still running exists on purpose: an agent that had
|
|
20
|
+
* to read silence as an answer would take an unfinished order for a refused
|
|
21
|
+
* one, and hold a budget against a sale that is still going to happen.
|
|
22
|
+
*
|
|
23
|
+
* Two of the machine's own distinctions are deliberately not here, and saying
|
|
24
|
+
* which is the point — a list that claims to name every ending has to admit
|
|
25
|
+
* what it folded. The machine separates a purchase that never reached the
|
|
26
|
+
* merchant from one the merchant refused, and it separates an order sent to
|
|
27
|
+
* the merchant from one the merchant has taken on. Both distinctions are the
|
|
28
|
+
* merchant's: the first their metrics need, the second their own record
|
|
29
|
+
* already holds. To the buyer each pair is one situation, and this vocabulary
|
|
30
|
+
* is the buyer's. A merchant reading it and expecting their own view of the
|
|
31
|
+
* order is reading the wrong list.
|
|
32
|
+
*/
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
export declare const ORDER_STATUSES: readonly ["in_progress", "delivered", "rejected", "payment_unresolved", "declined", "expired", "cancelled", "refund_due", "refunded", "delivered_unpaid"];
|
|
35
|
+
export declare const OrderStatusSchema: z.ZodEnum<{
|
|
36
|
+
delivered: "delivered";
|
|
37
|
+
in_progress: "in_progress";
|
|
38
|
+
rejected: "rejected";
|
|
39
|
+
payment_unresolved: "payment_unresolved";
|
|
40
|
+
declined: "declined";
|
|
41
|
+
expired: "expired";
|
|
42
|
+
cancelled: "cancelled";
|
|
43
|
+
refund_due: "refund_due";
|
|
44
|
+
refunded: "refunded";
|
|
45
|
+
delivered_unpaid: "delivered_unpaid";
|
|
46
|
+
}>;
|
|
47
|
+
export type OrderStatus = z.infer<typeof OrderStatusSchema>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What state an order is in, in the words an agent and a merchant both read.
|
|
3
|
+
*
|
|
4
|
+
* These are the state machine's own endings, and they live here because they
|
|
5
|
+
* are wire names: an agent asks what became of its purchase, a merchant
|
|
6
|
+
* restarting a worker asks what is still open, and both have to get the same
|
|
7
|
+
* answer. A second list kept anywhere else would be a second answer to one
|
|
8
|
+
* question, and the one the machine keeps would win without anybody noticing.
|
|
9
|
+
*
|
|
10
|
+
* Every value below says what the agent can be told truthfully, which is not
|
|
11
|
+
* always everything the machine knows. Where the two differ the value says so
|
|
12
|
+
* in its own words: `rejected` is coarser than the machine's states behind it,
|
|
13
|
+
* and `in_progress` covers every step of a purchase that has not finished —
|
|
14
|
+
* an order just sent to the merchant and one the merchant has taken on and
|
|
15
|
+
* owes a delivery for are one word here, because to the buyer they are one
|
|
16
|
+
* situation. A merchant who needs those apart is not reading this vocabulary;
|
|
17
|
+
* they are reading their own order, which carries the answer they gave.
|
|
18
|
+
*
|
|
19
|
+
* The word for a purchase still running exists on purpose: an agent that had
|
|
20
|
+
* to read silence as an answer would take an unfinished order for a refused
|
|
21
|
+
* one, and hold a budget against a sale that is still going to happen.
|
|
22
|
+
*
|
|
23
|
+
* Two of the machine's own distinctions are deliberately not here, and saying
|
|
24
|
+
* which is the point — a list that claims to name every ending has to admit
|
|
25
|
+
* what it folded. The machine separates a purchase that never reached the
|
|
26
|
+
* merchant from one the merchant refused, and it separates an order sent to
|
|
27
|
+
* the merchant from one the merchant has taken on. Both distinctions are the
|
|
28
|
+
* merchant's: the first their metrics need, the second their own record
|
|
29
|
+
* already holds. To the buyer each pair is one situation, and this vocabulary
|
|
30
|
+
* is the buyer's. A merchant reading it and expecting their own view of the
|
|
31
|
+
* order is reading the wrong list.
|
|
32
|
+
*/
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
export const ORDER_STATUSES = Object.freeze([
|
|
35
|
+
/** Running: no terminal answer yet, and no reason to read one into it. */
|
|
36
|
+
"in_progress",
|
|
37
|
+
/** Success: the product is with the agent and the money with the merchant. */
|
|
38
|
+
"delivered",
|
|
39
|
+
/**
|
|
40
|
+
* Closed and the buyer's money is known not to have moved — the product was
|
|
41
|
+
* gone, the parameters did not fit, the payment failed its check, the charge
|
|
42
|
+
* came back failed, or a synchronous handler refused.
|
|
43
|
+
*
|
|
44
|
+
* The machine keeps a finer distinction behind this word: a purchase that
|
|
45
|
+
* never reached the merchant and one the merchant refused are separate
|
|
46
|
+
* states there, because the merchant's own accounting needs them apart. To
|
|
47
|
+
* the agent both are one sentence — a refusal — and folding them was argued
|
|
48
|
+
* on the grounds that the reason travels separately, in the refusal code.
|
|
49
|
+
*
|
|
50
|
+
* It does travel, and what it carries is worth being exact about. The
|
|
51
|
+
* agent's status document has a `refusal` beside this word, and where a
|
|
52
|
+
* merchant refused the order it holds the two things the merchant actually
|
|
53
|
+
* wrote: a short code to branch on and a sentence to show. The word here
|
|
54
|
+
* stays coarse because that is the agent's vocabulary, and the pair beside
|
|
55
|
+
* it is what makes the coarseness affordable.
|
|
56
|
+
*
|
|
57
|
+
* What it does not carry is the other half of the fold, and an agent
|
|
58
|
+
* planning around this value needs it said. Two endings reach this word with
|
|
59
|
+
* nobody's words behind them — a product that was gone, and a payment this
|
|
60
|
+
* gateway would not vouch for — so they arrive as a bare `rejected` and are
|
|
61
|
+
* not distinguishable from each other here. The first is not worded because
|
|
62
|
+
* the price answer that reports it is an availability flag and carries no
|
|
63
|
+
* reason at all; the second is not worded here because it is refused at the
|
|
64
|
+
* agent's door in an error envelope that says what the payment layer said,
|
|
65
|
+
* which is a better place for it than a status read afterwards. Inventing
|
|
66
|
+
* codes of our own for either would be putting words in a mouth that never
|
|
67
|
+
* opened.
|
|
68
|
+
*/
|
|
69
|
+
"rejected",
|
|
70
|
+
/**
|
|
71
|
+
* Closed, and nobody can say whether the buyer was charged: the payment
|
|
72
|
+
* network was asked and never answered.
|
|
73
|
+
*
|
|
74
|
+
* Deliberately not `rejected`, and this is the fifth gate in one value. An
|
|
75
|
+
* agent told its purchase did not happen goes and buys the same thing
|
|
76
|
+
* elsewhere without looking at its wallet — a claim we would be making with
|
|
77
|
+
* no evidence behind it. What we know is that we asked and heard nothing;
|
|
78
|
+
* saying so is the honest answer, and it is what a dispute, an error text
|
|
79
|
+
* and the merchant's reconciliation all read.
|
|
80
|
+
*/
|
|
81
|
+
"payment_unresolved",
|
|
82
|
+
/** The merchant answered a confirmation request with "I will not deliver". */
|
|
83
|
+
"declined",
|
|
84
|
+
/**
|
|
85
|
+
* A deadline passed with nobody at fault: the confirmation, the payment that
|
|
86
|
+
* was to follow it, or the synchronous delivery.
|
|
87
|
+
*/
|
|
88
|
+
"expired",
|
|
89
|
+
/** The merchant left, and orders still open closed with them. */
|
|
90
|
+
"cancelled",
|
|
91
|
+
/** The money moved and the delivery did not happen. */
|
|
92
|
+
"refund_due",
|
|
93
|
+
/** That debt has since been paid back to the buyer. */
|
|
94
|
+
"refunded",
|
|
95
|
+
/**
|
|
96
|
+
* The merchant delivered synchronously and the payment did not execute. The
|
|
97
|
+
* goods exist, nothing was paid, and the order stays open until a repeat
|
|
98
|
+
* purchase carries the payment through.
|
|
99
|
+
*/
|
|
100
|
+
"delivered_unpaid",
|
|
101
|
+
]);
|
|
102
|
+
export const OrderStatusSchema = z.enum(ORDER_STATUSES);
|
package/dist/order.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The order, as it reaches the merchant's handler.
|
|
3
|
+
*
|
|
4
|
+
* By the time an order exists the purchase has already been checked on our
|
|
5
|
+
* side: the card was found, the parameters passed the card's own declaration,
|
|
6
|
+
* the price was settled and the payment was verified. Invalid purchases, stale
|
|
7
|
+
* prices and payments that did not verify never reach a handler, which is why
|
|
8
|
+
* this schema is short.
|
|
9
|
+
*
|
|
10
|
+
* Everything here answers a question the handler would otherwise have to ask
|
|
11
|
+
* us: what to deliver, to whom, under which of its own keys, at what price,
|
|
12
|
+
* and whether the money behind it is real.
|
|
13
|
+
*
|
|
14
|
+
* What state an order is in is not a field here. Reading state back — one
|
|
15
|
+
* order, or every order still open — is a separate call, and the words it
|
|
16
|
+
* answers with are `OrderStatusSchema` in `order-status.ts`.
|
|
17
|
+
*
|
|
18
|
+
* A second thing an order does not carry is its deadline. The design canon
|
|
19
|
+
* says an order carries one and the portal says a synchronous order does, but
|
|
20
|
+
* the portal's own table of what an order consists of does not list it, and
|
|
21
|
+
* this schema follows the table. The consequence stands either way: this
|
|
22
|
+
* package is the whole of the SDK's dependency tree, so nothing in it lets a
|
|
23
|
+
* synchronous handler learn how long it has — and in that mode the budget is
|
|
24
|
+
* ours, not something the merchant set and could look up. Which of the two
|
|
25
|
+
* readings is right is a question for whoever settles the numbers.
|
|
26
|
+
*
|
|
27
|
+
* One shape the documentation promises is still missing, and it is named here
|
|
28
|
+
* rather than left to be found as an absence. In the mode where the merchant
|
|
29
|
+
* is asked before the money moves, a confirmation request reaches the same
|
|
30
|
+
* subscription as the orders and is marked as a request rather than an order —
|
|
31
|
+
* no payment has happened, and delivering against one is not allowed. Nothing
|
|
32
|
+
* in this package carries that mark, so the two would be indistinguishable on
|
|
33
|
+
* the wire. Rather than leave that as a trap, the card refuses to publish in
|
|
34
|
+
* that mode at all until the request has a shape; see `card.ts`.
|
|
35
|
+
*/
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
export declare const OrderSchema: z.ZodObject<{
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
merchant_item_id: z.ZodString;
|
|
40
|
+
params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
41
|
+
price: z.ZodObject<{
|
|
42
|
+
amount: z.ZodString;
|
|
43
|
+
currency: z.ZodString;
|
|
44
|
+
at: z.ZodISODateTime;
|
|
45
|
+
as_of: z.ZodISODateTime;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
price_id: z.ZodOptional<z.ZodString>;
|
|
48
|
+
test: z.ZodBoolean;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
export type Order = z.infer<typeof OrderSchema>;
|