@nuanu-ai/agentify-contracts 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +57 -215
- package/dist/api.js +74 -30
- package/dist/card.d.ts +124 -72
- package/dist/card.js +147 -52
- package/dist/index.d.ts +45 -184
- package/dist/index.js +12 -9
- package/dist/merchant.d.ts +16 -14
- package/dist/merchant.js +55 -27
- package/dist/primitives.d.ts +13 -0
- package/dist/primitives.js +14 -0
- package/dist/results.d.ts +5 -4
- package/dist/results.js +6 -5
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
* at all.
|
|
48
48
|
*/
|
|
49
49
|
import { z } from "zod";
|
|
50
|
-
import { CardSchema, MerchantCardSchema, PublicCardSchema } from "./card.js";
|
|
50
|
+
import { CardSchema, FulfillmentSchema, MerchantCardSchema, PublicCardSchema, SellerSchema, } from "./card.js";
|
|
51
51
|
import { WorkerEnvelopeSchema } from "./envelope.js";
|
|
52
52
|
import { AcceptanceSchema, DeliverySchema, HandlerAnswerSchema, RefusalSchema } from "./handler.js";
|
|
53
|
-
import {
|
|
53
|
+
import { DashboardKeySchema, DisabledKeySchema, ForgottenDashboardKeySchema, IssuedKeySchema, IssueKeyRequestSchema, MerchantKeyListSchema, PayoutWalletRequestSchema, PayoutWalletSchema, RegisteredMerchantSchema, RegistrationRequestSchema, SellerNameRequestSchema, SellerNameSchema, } from "./merchant.js";
|
|
54
54
|
import { OrderSchema } from "./order.js";
|
|
55
55
|
import { OrderStatusSchema } from "./order-status.js";
|
|
56
56
|
import { ParamNameSchema } from "./param-spec.js";
|
|
57
|
-
import { IdentifierSchema, SalePriceSchema } from "./primitives.js";
|
|
57
|
+
import { IdentifierSchema, OpenWordSchema, SalePriceSchema } from "./primitives.js";
|
|
58
58
|
import { QuoteResponseSchema } from "./quote.js";
|
|
59
59
|
import { ReceiptSchema } from "./receipt.js";
|
|
60
60
|
import { CallErrorSchema, OrderCallResultSchema, PublishResultSchema } from "./results.js";
|
|
@@ -345,7 +345,7 @@ export const PurchaseRequestSchema = z
|
|
|
345
345
|
* because neither was worded by anybody.
|
|
346
346
|
*/
|
|
347
347
|
export const AgentOrderStatusSchema = z
|
|
348
|
-
.
|
|
348
|
+
.looseObject({
|
|
349
349
|
order_id: IdentifierSchema,
|
|
350
350
|
/**
|
|
351
351
|
* Where this order is read again: the whole address of its status route,
|
|
@@ -379,7 +379,16 @@ export const AgentOrderStatusSchema = z
|
|
|
379
379
|
status_url: z
|
|
380
380
|
.url()
|
|
381
381
|
.regex(/^https?:\/\//, "an order's status address is a whole http or https address, not a path"),
|
|
382
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Where the order stands, as a word whose known values are listed beside
|
|
384
|
+
* it (ADR-0006 §5). The storefront has no version, so a word added later
|
|
385
|
+
* reaches agents that still hold this contract: one they do not know is
|
|
386
|
+
* not an ending they know, and they ask again later rather than buy again
|
|
387
|
+
* on its strength. The merchant's own view of the order reads the same
|
|
388
|
+
* words from a closed list. Two branches, as a card's mode is
|
|
389
|
+
* (`PublicCardSchema`), so the known words still cross into the export.
|
|
390
|
+
*/
|
|
391
|
+
status: z.union([OrderStatusSchema, OpenWordSchema]),
|
|
383
392
|
/**
|
|
384
393
|
* The price this order was priced at, or null where nobody ever named one
|
|
385
394
|
* for it. It is the order's own price and not the card's number: a card
|
|
@@ -393,7 +402,7 @@ export const AgentOrderStatusSchema = z
|
|
|
393
402
|
* field's, and a reader taking this for an amount charged would be
|
|
394
403
|
* reconciling against sales that never happened.
|
|
395
404
|
*/
|
|
396
|
-
price: SalePriceSchema.nullable(),
|
|
405
|
+
price: SalePriceSchema.loose().nullable(),
|
|
397
406
|
/**
|
|
398
407
|
* The goods, once they are the buyer's — the delivery as the merchant
|
|
399
408
|
* wrote it, and null until then.
|
|
@@ -443,10 +452,21 @@ export const AgentOrderStatusSchema = z
|
|
|
443
452
|
* present pair is always somebody's actual answer rather than a word this
|
|
444
453
|
* gateway picked for them.
|
|
445
454
|
*/
|
|
446
|
-
refusal: RefusalSchema.optional(),
|
|
455
|
+
refusal: RefusalSchema.loose().optional(),
|
|
456
|
+
/**
|
|
457
|
+
* Who sold it: the name and the site the merchant gave, read as their
|
|
458
|
+
* merchant stands now (ADR-0034).
|
|
459
|
+
*
|
|
460
|
+
* An agent holding an order and nothing else is the one with a question
|
|
461
|
+
* the order cannot answer — a parcel that did not arrive, a return — and
|
|
462
|
+
* this is where it learns where to take it. It is what the catalog already
|
|
463
|
+
* shows every agent beside the merchant's cards and nothing more: not the
|
|
464
|
+
* merchant's account, not their own key for the product, not the card.
|
|
465
|
+
*/
|
|
466
|
+
seller: SellerSchema.loose(),
|
|
447
467
|
})
|
|
448
468
|
.meta({
|
|
449
|
-
description: 'What became of one purchase, in the words an agent and a merchant both read: where the order stands, what it was priced at, the goods once they are the buyer\'s, and why the merchant would not sell where that is what ended it. "status_url" is where this document is read again — the whole address of the order\'s status route, and the place an agent that bought goods that come later collects them. It is absolute and called as it stands, and it is the address the gateway is configured to answer as, never one taken from the request that asked. It is smaller than the merchant\'s own view of the same order on purpose — no merchant, no merchant\'s own key for the product, none of the purchase parameters and nothing about any other order. The price is what the buyer was asked for and not proof that anything was charged: an order that was priced and then ended without a sale still carries it, and the status is what says which happened. A null price means nobody ever named one for this order, and a null delivery means there are no goods here to hand over; both fields are always present, because an absent field is a silence a reader cannot tell from an oversight. "refusal" is the exception and is present only where a merchant refused: their own short code to branch on and their own sentence to show, carried across unchanged. The code is an open set — "out_of_stock", "invalid_params" and "cannot_fulfill" are read the same way by everybody, and a merchant whose reason fits none of them sends their own word, so an unfamiliar code has to fall through to the sentence rather than break a reader. An absent "refusal" means there is no refusal to quote and never that one was dropped, which leaves two endings still coarse: "rejected" also covers a product that was gone and a payment that failed its check, and neither of those was worded by anybody — the first because a price answer of "not available" carries no words, the second because it is refused at the door in an error envelope instead. A null delivery is likewise not a promise that no goods were ever made: a purchase whose charge failed or went unanswered can leave goods the buyer has not paid for, and this document withholds them rather than describing them. Every answer says whether the money behind the purchase was real: a gateway settling against nothing produces every other field here exactly as a real charge would, so a reader taking this for proof of a payment has to read that word first.',
|
|
469
|
+
description: 'What became of one purchase, in the words an agent and a merchant both read: where the order stands, what it was priced at, the goods once they are the buyer\'s, and why the merchant would not sell where that is what ended it. "status_url" is where this document is read again — the whole address of the order\'s status route, and the place an agent that bought goods that come later collects them. It is absolute and called as it stands, and it is the address the gateway is configured to answer as, never one taken from the request that asked. It is smaller than the merchant\'s own view of the same order on purpose — no merchant account, no merchant\'s own key for the product, none of the purchase parameters and nothing about any other order. The price is what the buyer was asked for and not proof that anything was charged: an order that was priced and then ended without a sale still carries it, and the status is what says which happened. A null price means nobody ever named one for this order, and a null delivery means there are no goods here to hand over; both fields are always present, because an absent field is a silence a reader cannot tell from an oversight. "refusal" is the exception and is present only where a merchant refused: their own short code to branch on and their own sentence to show, carried across unchanged. The code is an open set — "out_of_stock", "invalid_params" and "cannot_fulfill" are read the same way by everybody, and a merchant whose reason fits none of them sends their own word, so an unfamiliar code has to fall through to the sentence rather than break a reader. An absent "refusal" means there is no refusal to quote and never that one was dropped, which leaves two endings still coarse: "rejected" also covers a product that was gone and a payment that failed its check, and neither of those was worded by anybody — the first because a price answer of "not available" carries no words, the second because it is refused at the door in an error envelope instead. A null delivery is likewise not a promise that no goods were ever made: a purchase whose charge failed or went unanswered can leave goods the buyer has not paid for, and this document withholds them rather than describing them. Every answer says whether the money behind the purchase was real: a gateway settling against nothing produces every other field here exactly as a real charge would, so a reader taking this for proof of a payment has to read that word first. "seller" is who sold it, as the merchant gave it and as the catalog shows it beside their cards: the name and the site of their shop, which Agentify did not check, and the site is where to take what this document cannot answer. "status" is a word whose known values are listed beside it, and more may be added: a word a reader does not know is not an ending it knows, so it asks again later and does not buy again on its strength. This document and every part inside it may also gain fields; a reader ignores the ones it does not know.',
|
|
450
470
|
});
|
|
451
471
|
/**
|
|
452
472
|
* The catalog as an agent reads it.
|
|
@@ -456,13 +476,37 @@ export const AgentOrderStatusSchema = z
|
|
|
456
476
|
* Until then it makes no claim about completeness, and that is stated in the
|
|
457
477
|
* document itself: an agent must not read the absence of a field about paging
|
|
458
478
|
* as a promise that there is nothing more.
|
|
479
|
+
*
|
|
480
|
+
* It is read card by card (ADR-0006 §5). The storefront has no version, so a
|
|
481
|
+
* card of a mode or a shape added later reaches agents that still hold this
|
|
482
|
+
* contract, and one card they cannot read must leave every other card for
|
|
483
|
+
* sale. So the page holds its items as whatever they are, and `cardsOf` reads
|
|
484
|
+
* each one on its own against the card an agent reads.
|
|
459
485
|
*/
|
|
460
486
|
export const CatalogPageSchema = z
|
|
461
|
-
.
|
|
462
|
-
items: z.array(
|
|
487
|
+
.looseObject({
|
|
488
|
+
items: z.array(z.unknown().meta({
|
|
489
|
+
description: "One product, read on its own as a public_card. An item a reader cannot read as one is passed over, and the rest of the page stands.",
|
|
490
|
+
})),
|
|
463
491
|
})
|
|
464
492
|
.meta({
|
|
465
|
-
description: "Products offered for sale, as an agent reads them. This document does not say whether it is the whole catalog: paging is not designed, and when it is, this object grows the field that answers it. Until then the absence of such a field is not a promise that there is no more.",
|
|
493
|
+
description: "Products offered for sale, as an agent reads them. This document does not say whether it is the whole catalog: paging is not designed, and when it is, this object grows the field that answers it. Until then the absence of such a field is not a promise that there is no more. Each item is read on its own as a public_card, and one a reader cannot read — a mode or a shape added after the contract it holds — is passed over while the rest of the page stands. This document may also gain fields; a reader ignores the ones it does not know.",
|
|
494
|
+
});
|
|
495
|
+
/**
|
|
496
|
+
* The cards of a catalog page a reader of this contract can buy from, each read
|
|
497
|
+
* on its own.
|
|
498
|
+
*
|
|
499
|
+
* Two kinds of item are passed over, and the rest of the page stands. One that
|
|
500
|
+
* does not read as a card at all, and one that does but is sold in a mode this
|
|
501
|
+
* contract does not name: its reader cannot know when such goods arrive or when
|
|
502
|
+
* the money moves, so it must not buy on it.
|
|
503
|
+
*/
|
|
504
|
+
export const cardsOf = (page) => page.items.flatMap((item) => {
|
|
505
|
+
const read = PublicCardSchema.safeParse(item);
|
|
506
|
+
if (!read.success)
|
|
507
|
+
return [];
|
|
508
|
+
const mode = FulfillmentSchema.safeParse(read.data.fulfillment);
|
|
509
|
+
return mode.success ? [{ ...read.data, fulfillment: mode.data }] : [];
|
|
466
510
|
});
|
|
467
511
|
/**
|
|
468
512
|
* The methods this surface uses.
|
|
@@ -660,7 +704,7 @@ export const ERROR_CODES = Object.freeze([
|
|
|
660
704
|
"charset_unsupported",
|
|
661
705
|
"encoding_unsupported",
|
|
662
706
|
"gateway_failed",
|
|
663
|
-
"
|
|
707
|
+
"key_made_for_a_dashboard",
|
|
664
708
|
"key_opened_this_call",
|
|
665
709
|
"malformed_body",
|
|
666
710
|
"malformed_query",
|
|
@@ -669,7 +713,7 @@ export const ERROR_CODES = Object.freeze([
|
|
|
669
713
|
"no_such_key",
|
|
670
714
|
"no_such_order",
|
|
671
715
|
"no_such_route",
|
|
672
|
-
"
|
|
716
|
+
"not_a_dashboard_key",
|
|
673
717
|
"not_authorised",
|
|
674
718
|
"not_invited",
|
|
675
719
|
"not_selling",
|
|
@@ -718,7 +762,7 @@ export const API_ROUTES = Object.freeze({
|
|
|
718
762
|
method: "POST",
|
|
719
763
|
path: "/v0/catalog/publish",
|
|
720
764
|
auth: "merchant_key",
|
|
721
|
-
description: "Publishes one product, or says what is wrong with the card. Republishing under the same merchant_item_id is how a card is changed rather than how a second one appears. The catalog identifier comes back beside ok, and from then on it is what an agent, a purchase and a receipt all use. A card that is not published comes back as ok:false under the code card_rejected, never retryable, with every finding named in the error's problems. Three findings this can come back with are not about the card at all. A merchant who has not set the name their products are sold under gets no_seller_name, and the way past it is POST /v0/seller-name: a card published without one reaches a buyer's agent inside a payment request that names no seller. On a deployment that settles on a real chain, a merchant who has set no wallet gets no_payout_wallet, and the way past it is the Settings screen of the merchant's
|
|
765
|
+
description: "Publishes one product, or says what is wrong with the card. Republishing under the same merchant_item_id is how a card is changed rather than how a second one appears. The catalog identifier comes back beside ok, and from then on it is what an agent, a purchase and a receipt all use. A card that is not published comes back as ok:false under the code card_rejected, never retryable, with every finding named in the error's problems. Three findings this can come back with are not about the card at all. A merchant who has not set the name their products are sold under gets no_seller_name, and the way past it is POST /v0/seller-name: a card published without one reaches a buyer's agent inside a payment request that names no seller. On a deployment that settles on a real chain, a merchant who has set no wallet gets no_payout_wallet, and the way past it is the Settings screen of the merchant's dashboard, where a person signed in sets one: the money from that card's sales is paid to the merchant's own address directly, and without one there is nowhere for it to go. On the live deployment, a merchant the operator has not admitted once gets no_operator_approval. That decision has no merchant API: test publication remains available, while live publication remains closed until the operator grants it. Every applicable finding comes back in the same problems list as whatever is wrong with the card, so one answer carries everything standing between this card and the catalog.",
|
|
722
766
|
request: CardSchema,
|
|
723
767
|
response: { document: PublishResultSchema },
|
|
724
768
|
},
|
|
@@ -761,7 +805,7 @@ export const API_ROUTES = Object.freeze({
|
|
|
761
805
|
method: "POST",
|
|
762
806
|
path: "/v0/merchants",
|
|
763
807
|
auth: "none",
|
|
764
|
-
description: "Makes a merchant and the key whoever registered them will call as them with — both of those or neither. This call is not on the public origin: only the
|
|
808
|
+
description: "Makes a merchant and the key whoever registered them will call as them with — both of those or neither. This call is not on the public origin: only the dashboard makes it, from inside the stack, when a signed-in person presses its one control, and from outside the path answers as one the site does not have. It takes no key because nobody registering has one yet; what stands in the door instead is an invitation code out of the gateway's own configuration, and a gateway with no code configured refuses every registration in the same words a wrong code gets, so this call is not a way of finding out whether registration is open. The key comes back once and is readable nowhere afterwards, and it is a key made for a dashboard: it is in no list of the merchant's keys and is not disabled through them, so no row for it comes back here — only the key itself. What the new merchant does not have is a name: they are listed under nothing until somebody sets one at POST /v0/seller-name, and until then publishing a card is refused, so a dashboard that registers a person and takes them straight to a publish screen has built a dead end. Nothing about an account, an address or a password reaches this call either: those belong to whatever signs a person in, on the other side of it. Unlike every other call on this surface that writes something, a repeat of this one is not safe and there is nothing here that could make it so: two calls make two merchants, and the caller holds a key to only the second. A caller whose connection drops before the answer arrives cannot find out from here whether the first landed — it has no key with which to ask — and the merchant it may have made cannot be swept away afterwards, because a merchant is what every card, order and receipt is owned by.",
|
|
765
809
|
request: RegistrationRequestSchema,
|
|
766
810
|
response: { document: RegisteredMerchantSchema },
|
|
767
811
|
},
|
|
@@ -784,14 +828,14 @@ export const API_ROUTES = Object.freeze({
|
|
|
784
828
|
method: "GET",
|
|
785
829
|
path: "/v0/payout-wallet",
|
|
786
830
|
auth: "merchant_key",
|
|
787
|
-
description: "The address the sales of the merchant this call's own key belongs to are paid into now, and any change of it that is waiting. Payments here are not held by anybody on the way: a buyer's agent pays this address directly and no balance of the merchant's is ever held on our side, which is why the address has to be theirs and why this call exists. Any key of the merchant's reads it, the keys made for their own code included; only the merchant's
|
|
831
|
+
description: "The address the sales of the merchant this call's own key belongs to are paid into now, and any change of it that is waiting. Payments here are not held by anybody on the way: a buyer's agent pays this address directly and no balance of the merchant's is ever held on our side, which is why the address has to be theirs and why this call exists. Any key of the merchant's reads it, the keys made for their own code included; only the merchant's dashboard sets it. Null is the ordinary answer for a merchant who has set none, and it is an answer rather than a refusal — a merchant with no wallet exists and has a settings screen to draw. The address comes back in the mixed-case spelling a wallet shows, whichever of the two accepted spellings was sent to set it, so a screen showing it shows what the merchant copied out of their wallet character for character. On the live deployment a replacement for an address already set takes effect forty-eight hours after it was announced to the merchant, and until then this answers with the address still paid and names the waiting one, with the moment it takes effect, under pending. Null there means nothing is waiting, which is always the answer on the test channel and in a sandbox.",
|
|
788
832
|
response: { document: PayoutWalletSchema },
|
|
789
833
|
},
|
|
790
834
|
set_payout_wallet: {
|
|
791
835
|
method: "POST",
|
|
792
836
|
path: "/v0/payout-wallet",
|
|
793
837
|
auth: "merchant_key",
|
|
794
|
-
description: "Sets the address the sales of the merchant this call's own key belongs to are paid into. Only the merchant's
|
|
838
|
+
description: "Sets the address the sales of the merchant this call's own key belongs to are paid into. Only the merchant's dashboard makes this call, with the key made for it, from inside the stack, when a person sets the wallet on its Settings screen: keys operate the shop, and where the shop's money goes is set through the dashboard. The public origin does not route this call at all, so from outside it is a path the site does not have; at the gateway a key made for the merchant's own code is refused under not_a_dashboard_key, for the first address as for any other, and nothing is written or announced. The first address a merchant sets applies at once; on the live deployment every dashboard account of the merchant is then told of it, and a message that cannot be sent refuses nothing. On the live deployment a different address after that does not: before anything is written, every account that names the merchant is sent a message saying what changes, when, and that it was asked for in the dashboard, and the change takes effect forty-eight hours after those messages were handed to the mail provider. Until then every payment request names the address that applies now, and the answer carries the waiting one under pending with the moment it takes effect. Asking again for the address already waiting changes nothing, sends no second message and restarts no clock, and answers with the same pending change, so a retry after a dropped connection is safe; a different address replaces the waiting one and starts the forty-eight hours again; asking for the address that applies now cancels the waiting change. On the test channel and in a sandbox every change applies at once and no message is sent. The answer is the wallet as it now stands, read back from what was written rather than echoed, in the mixed-case spelling a wallet shows. Setting the address that already applies, with nothing waiting, changes nothing and answers the same way. An address whose capital letters do not agree with the rest of it is refused and nothing is written, because those capitals are a checksum and letters that disagree mean a character is wrong — and an address that is wrong is another perfectly good address belonging to somebody else. What this call will not do is take an address away: null is refused, because without an address there is nowhere to send the money and every published card would quietly come off sale — ending the selling under the name of editing a setting; somebody reaching for that wants either a different address, which is this same call, or an end to selling, which is the pause. On a deployment that settles on a real chain, a merchant with no wallet set here cannot publish a card, and the refusal at the publish says so.",
|
|
795
839
|
request: PayoutWalletRequestSchema,
|
|
796
840
|
response: { document: PayoutWalletSchema },
|
|
797
841
|
},
|
|
@@ -799,14 +843,14 @@ export const API_ROUTES = Object.freeze({
|
|
|
799
843
|
method: "GET",
|
|
800
844
|
path: "/v0/keys",
|
|
801
845
|
auth: "merchant_key",
|
|
802
|
-
description: "The keys the merchant this call's own key belongs to made for their own code, the revoked ones among them, and never the keys themselves. A merchant who has only ever signed into a
|
|
846
|
+
description: "The keys the merchant this call's own key belongs to made for their own code, the revoked ones among them, and never the keys themselves. A merchant who has only ever signed into a dashboard has none of these, and an empty list is that answer rather than a fault: the key a dashboard calls with is made for the dashboard and is in no list here, because the merchant did not issue it and cannot disable it. Whether the list is all of the rest is not something this document claims either: paging is not designed, and the absence of a field about it is not a promise that there is no more. The answer also names the key the call was made with, as this_call, and that field is the reason this is not a bare list: a merchant cannot disable the key their own call was made with, so a screen drawn without knowing which key that is would offer a button the gateway refuses. That identifier is not among the keys listed when the caller is a dashboard, and a client that looked it up among the rows has to be built for finding none. Every row also says when a call was last seen on that key, which is the field a screen offering to revoke one is worth drawing at all — and it is a recorded answer rather than an exact one, lagging by minutes, and blank both for a key nothing has called and for one made before this gateway began recording, which nothing here tells apart. This call is itself one of those calls, so the key it was made with reads as used at about this moment, whether or not it appears in the list.",
|
|
803
847
|
response: { document: MerchantKeyListSchema },
|
|
804
848
|
},
|
|
805
849
|
issue_key: {
|
|
806
850
|
method: "POST",
|
|
807
851
|
path: "/v0/keys",
|
|
808
852
|
auth: "merchant_key",
|
|
809
|
-
description: "Issues another key for the merchant's own code, to the merchant this call's own key belongs to. The key is generated here and never taken from the caller, and it comes back exactly once — what is kept afterwards is a digest, so nothing can show it again. A merchant with several keys can hand one to each worker and revoke one without touching the others, which is the whole reason a key is a row. This call cannot make the other kind of key: what a
|
|
853
|
+
description: "Issues another key for the merchant's own code, to the merchant this call's own key belongs to. The key is generated here and never taken from the caller, and it comes back exactly once — what is kept afterwards is a digest, so nothing can show it again. A merchant with several keys can hand one to each worker and revoke one without touching the others, which is the whole reason a key is a row. This call cannot make the other kind of key: what a dashboard calls with is asked for at POST /v0/keys/dashboard, and a key made here is one the merchant sees, names and revokes.",
|
|
810
854
|
request: IssueKeyRequestSchema,
|
|
811
855
|
response: { document: IssuedKeySchema },
|
|
812
856
|
},
|
|
@@ -814,22 +858,22 @@ export const API_ROUTES = Object.freeze({
|
|
|
814
858
|
method: "POST",
|
|
815
859
|
path: "/v0/keys/:key_id/disable",
|
|
816
860
|
auth: "merchant_key",
|
|
817
|
-
description: "Stops one of this merchant's keys working, from that instant, and touches no other key. Disabling a key that is already disabled changes nothing and answers the same way, keeping the instant it was first revoked at, so a retry after a dropped connection is safe. Three refusals are worth knowing before a screen is built on this. A key belonging to another merchant is answered exactly as a key that does not exist, so this call is not a way of counting somebody else's keys. A key made for a
|
|
861
|
+
description: "Stops one of this merchant's keys working, from that instant, and touches no other key. Disabling a key that is already disabled changes nothing and answers the same way, keeping the instant it was first revoked at, so a retry after a dropped connection is safe. Three refusals are worth knowing before a screen is built on this. A key belonging to another merchant is answered exactly as a key that does not exist, so this call is not a way of counting somebody else's keys. A key made for a dashboard is refused under key_made_for_a_dashboard, whoever asks and however they came by its identifier: this call reaches the keys a merchant issued for their own code and nothing else, and replacing the one a dashboard holds is POST /v0/keys/dashboard and the forgetting beside it. And the key this call was made with cannot be disabled by it — that one click and no more: the refusal is about the key in front of it, so a merchant holding two keys of their own can still disable either with the other, and two such calls at one moment can leave them with none of their own. That is not refused here or anywhere, and what it costs is their own code going quiet rather than the way back in, which is a key of the other kind.",
|
|
818
862
|
response: { document: DisabledKeySchema },
|
|
819
863
|
},
|
|
820
|
-
|
|
864
|
+
issue_dashboard_key: {
|
|
821
865
|
method: "POST",
|
|
822
|
-
path: "/v0/keys/
|
|
866
|
+
path: "/v0/keys/dashboard",
|
|
823
867
|
auth: "merchant_key",
|
|
824
|
-
description: "Makes a key for a
|
|
825
|
-
response: { document:
|
|
868
|
+
description: "Makes a key for a dashboard to call as this merchant with, and hands it back once. This call and the forgetting beside it are not on the public origin: only the dashboard makes them, from inside the stack, and from outside the path answers as one the site does not have. It is a key of a different kind from the ones at /v0/keys: the merchant did not ask for it, never sees it, and it appears in no list of theirs — so nothing comes back but the key itself. What this is for is a dashboard that replaces its own credential every time somebody signs in, which is what keeps a copy of a dashboard's database from being a set of working keys for long. The call is refused to a key made for the merchant's own code, under not_a_dashboard_key: these two calls are the dashboard's own, and a merchant asking for one would be asking for a credential to a dashboard they are not standing in.",
|
|
869
|
+
response: { document: DashboardKeySchema },
|
|
826
870
|
},
|
|
827
|
-
|
|
871
|
+
forget_dashboard_key: {
|
|
828
872
|
method: "DELETE",
|
|
829
|
-
path: "/v0/keys/
|
|
873
|
+
path: "/v0/keys/dashboard",
|
|
830
874
|
auth: "merchant_key",
|
|
831
|
-
description: "Removes the key this call was made with, and no other. Like the call that makes such a key, it is not on the public origin: only the
|
|
832
|
-
response: { document:
|
|
875
|
+
description: "Removes the key this call was made with, and no other. Like the call that makes such a key, it is not on the public origin: only the dashboard makes it, from inside the stack. It is removed rather than revoked: a merchant never issued one, never sees one and would never read a revoked one back, so a row kept for the history would be history for nobody. There are no parameters, and that is not a convenience — a key belonging to anybody else, this merchant included, is unreachable here by construction, because the only way to name a key is to be holding it. What a caller does with this is put a key of its own beyond use once it has stopped signing in with it: ask for a fresh key, write it down where the account is kept, then forget the one that was there. A caller that forgets first can be left naming a key that no longer exists, which is the one order that locks somebody out. Made a second time with the same key it is refused as a key that does not exist, which is safe and is the confirmation the first one landed: either way that key is gone and nothing else has moved. It is refused to a key made for the merchant's own code, under not_a_dashboard_key: those are revoked from the list they appear on, at an instant their owner can read back, and removing the row outright would take that history away.",
|
|
876
|
+
response: { document: ForgottenDashboardKeySchema },
|
|
833
877
|
},
|
|
834
878
|
get_order: {
|
|
835
879
|
method: "GET",
|
|
@@ -865,7 +909,7 @@ export const API_ROUTES = Object.freeze({
|
|
|
865
909
|
method: "POST",
|
|
866
910
|
path: "/v0/orders/:order_id/answer",
|
|
867
911
|
auth: "merchant_key",
|
|
868
|
-
description: "What the merchant's handler returned for an order it was given: the goods, a refusal, or an acceptance. The SDK sends this itself, in every mode, and it is the only way a synchronous answer reaches us at all — there the handler's return is the delivery and the refusal, and the explicit deliver and refuse calls do not apply. An acceptance is answered with the word accepted — the order is taken on, and the goods follow through the deliver call — or with already_delivered when the order closed before the acceptance reached us, which a redelivered order makes ordinary rather than a fault. A synchronous answer that arrives after its deadline is not an error: the work exists and a repeat purchase collects it, and the answer says so in the word purchase_already_closed.",
|
|
912
|
+
description: "What the merchant's handler returned for an order it was given: the goods, a refusal, or an acceptance. The SDK sends this itself, in every mode, and it is the only way a synchronous answer reaches us at all — there the handler's return is the delivery and the refusal, and the explicit deliver and refuse calls do not apply. An acceptance is answered with the word accepted — the order is taken on, and the goods follow through the deliver call — or with already_delivered when the order closed before the acceptance reached us, which a redelivered order makes ordinary rather than a fault. A synchronous order still waiting for its goods cannot be taken on, because the deliver call that would carry them later does not exist in that mode: the acceptance is refused with not_applicable_in_mode, the order is not sent to the handler again for it, and it closes on its deadline with nothing charged. A synchronous answer that arrives after its deadline is not an error: the work exists and a repeat purchase collects it, and the answer says so in the word purchase_already_closed.",
|
|
869
913
|
request: HandlerAnswerSchema,
|
|
870
914
|
response: { document: OrderCallResponseSchema },
|
|
871
915
|
},
|
|
@@ -889,7 +933,7 @@ export const API_ROUTES = Object.freeze({
|
|
|
889
933
|
method: "POST",
|
|
890
934
|
path: "/v0/orders/:order_id/accept",
|
|
891
935
|
auth: "merchant_key",
|
|
892
|
-
description: "Takes an order on: the merchant will deliver, and says how long they expect it to take when they know. An empty body is a complete answer. The same order is taken on again every time it is redelivered, and the success carries no word: taking on an order that is already delivered succeeds here too, and this route does not tell the two apart. The answer route does, because it carries whichever of the three things a handler returned.",
|
|
936
|
+
description: "Takes an order on: the merchant will deliver, and says how long they expect it to take when they know. An empty body is a complete answer. A synchronous order still waiting for its goods cannot be taken on, because its goods travel only in the handler's answer: the call is refused with not_applicable_in_mode. The same order is taken on again every time it is redelivered, and the success carries no word: taking on an order that is already delivered succeeds here too, and this route does not tell the two apart. The answer route does, because it carries whichever of the three things a handler returned.",
|
|
893
937
|
request: AcceptanceSchema,
|
|
894
938
|
response: { document: OrderAcceptResponseSchema },
|
|
895
939
|
},
|
package/dist/card.d.ts
CHANGED
|
@@ -66,6 +66,44 @@ export declare const PriceCheckSchema: z.ZodUnion<readonly [z.ZodLiteral<"handle
|
|
|
66
66
|
* wherever a merchant's listing name is written down.
|
|
67
67
|
*/
|
|
68
68
|
export declare const ServiceNameSchema: z.ZodString;
|
|
69
|
+
/**
|
|
70
|
+
* The address of a seller's own shop on the web, where an agent takes what an
|
|
71
|
+
* order cannot answer (ADR-0034).
|
|
72
|
+
*
|
|
73
|
+
* Only an https origin, because anything after the host — a path, a query, a
|
|
74
|
+
* fragment — would be text of the merchant's own reaching every agent that
|
|
75
|
+
* reads the card, which is the free text the decision refuses. The host is the
|
|
76
|
+
* one part the merchant writes, so it is held to a domain name too: labels of
|
|
77
|
+
* letters, digits and hyphens, at most 253 characters, ending in a zone of
|
|
78
|
+
* letters or a punycode one. Anything a URL parser keeps as written would
|
|
79
|
+
* otherwise pass — a sentence of instructions, a quote, a host of any length —
|
|
80
|
+
* and so would a single word or an IP address, which point other people's
|
|
81
|
+
* agents into somebody's own network. What the pattern cannot do is tell a
|
|
82
|
+
* public name from a private one: a name in a zone kept for local networks,
|
|
83
|
+
* or a public one whose address leads into one, passes, and so does a
|
|
84
|
+
* hyphenated sentence of up to 253 characters that is a valid name. That is
|
|
85
|
+
* why an agent is told, beside every site, that nobody checked it.
|
|
86
|
+
*
|
|
87
|
+
* The pattern says all that in a form the JSON Schema export keeps, and it
|
|
88
|
+
* stops the check where it fails, so an address copied with a slash at the end
|
|
89
|
+
* is told once. Asking the URL parser for the origin catches what the pattern
|
|
90
|
+
* leaves, such as a punycode label the parser would write differently.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SellerSiteSchema: z.ZodString;
|
|
93
|
+
/**
|
|
94
|
+
* Who sells, as an agent reads it beside a product and on an order: the name
|
|
95
|
+
* the merchant sells under and the site of their shop (ADR-0034).
|
|
96
|
+
*
|
|
97
|
+
* Both are the merchant's own word and nothing here checked either, which the
|
|
98
|
+
* description says to the reader who acts on it — a name and a site can be
|
|
99
|
+
* anybody's. Null is "none given": a merchant who gave no site has none here,
|
|
100
|
+
* and a name is missing only from an order whose merchant has since lost it.
|
|
101
|
+
*/
|
|
102
|
+
export declare const SellerSchema: z.ZodObject<{
|
|
103
|
+
name: z.ZodNullable<z.ZodString>;
|
|
104
|
+
site: z.ZodNullable<z.ZodString>;
|
|
105
|
+
}, z.core.$strict>;
|
|
106
|
+
export type Seller = z.infer<typeof SellerSchema>;
|
|
69
107
|
/**
|
|
70
108
|
* The words a merchant puts on one product so an agent searching a catalog can
|
|
71
109
|
* find it.
|
|
@@ -164,45 +202,64 @@ export type CardInput = Omit<Card, "price" | "params" | "result" | "fulfillment"
|
|
|
164
202
|
export declare const purchaseCheckFor: (card: Card) => z.ZodType;
|
|
165
203
|
/** The check this card's delivery is held to. */
|
|
166
204
|
export declare const deliveryCheckFor: (card: Card) => z.ZodType;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
/**
|
|
206
|
+
* The fields of a card as an agent reads it in a catalog.
|
|
207
|
+
*
|
|
208
|
+
* The published card is what a merchant writes; this is what we say about it
|
|
209
|
+
* to somebody who is about to spend money. Every difference between the two is
|
|
210
|
+
* a decision, so each one is written down.
|
|
211
|
+
*
|
|
212
|
+
* Our catalog identifier replaces the merchant's own key. `id` is what a
|
|
213
|
+
* purchase, a receipt and a status all use, while `merchant_item_id` is unique
|
|
214
|
+
* only inside one merchant's catalog and means nothing outside it. An agent
|
|
215
|
+
* handed both would use the wrong one some of the time, for no gain.
|
|
216
|
+
*
|
|
217
|
+
* `price_check` is gone and one fact out of it stays. The address of a
|
|
218
|
+
* merchant's pricing service is infrastructure of theirs, no agent ever calls
|
|
219
|
+
* it, and publishing it would put it in front of everyone. What an agent does
|
|
220
|
+
* act on is that the price will be asked again: the number in the catalog is
|
|
221
|
+
* what it compares when choosing, and the sale can go through at another. So
|
|
222
|
+
* the projection carries `price_checked_at_purchase` and nothing else about
|
|
223
|
+
* how the asking is done. The flag says we ask, not that we get an answer —
|
|
224
|
+
* what happens when a merchant is silent depends on the mode and is the
|
|
225
|
+
* gateway's, and this document does not claim to know it.
|
|
226
|
+
*
|
|
227
|
+
* Both deadlines stay, because they are the merchant's promise to the agent
|
|
228
|
+
* about how long it may wait, and the agent is told them before it pays. The
|
|
229
|
+
* projection writes each only on a mode that has it, and the document says so
|
|
230
|
+
* in words rather than as structure: it takes fields added later (ADR-0006
|
|
231
|
+
* §5), so a field an agent does not expect is one it ignores, not one it can
|
|
232
|
+
* refuse.
|
|
233
|
+
*
|
|
234
|
+
* The mode is a word whose known values are listed beside it, for the same
|
|
235
|
+
* reason. The storefront has no version, so a mode added later reaches agents
|
|
236
|
+
* that still hold this contract, and to them it has to be a card to pass over
|
|
237
|
+
* rather than a catalog they cannot read.
|
|
238
|
+
*
|
|
239
|
+
* `as_of` is added: the moment the price shown here was published. A price
|
|
240
|
+
* with no moment behind it cannot be judged stale, and this is the only
|
|
241
|
+
* freshness claim a catalog makes.
|
|
242
|
+
*
|
|
243
|
+
* `tags` are gone as well, and that one is a decision rather than an omission.
|
|
244
|
+
* They are words a merchant chose so that a search in a discovery catalog finds
|
|
245
|
+
* this product, held to that catalog's own rule about length and alphabet. Our
|
|
246
|
+
* own catalog is not searched that way — an agent reading it has the whole
|
|
247
|
+
* page — so carrying them here would put a foreign catalog's constraint in
|
|
248
|
+
* front of a reader who has no use for it.
|
|
249
|
+
*
|
|
250
|
+
* Who sells is here as the merchant gave it, a name and the site of their
|
|
251
|
+
* shop (ADR-0034), and as nothing more: no profile, no contact of ours, and no
|
|
252
|
+
* claim that either was checked. Who "we" are to the buyer is a question this
|
|
253
|
+
* document still does not answer.
|
|
254
|
+
*/
|
|
255
|
+
export declare const PublicCardSchema: z.ZodObject<{
|
|
199
256
|
id: z.ZodString;
|
|
200
257
|
title: z.ZodString;
|
|
201
258
|
description: z.ZodString;
|
|
202
259
|
price: z.ZodObject<{
|
|
203
260
|
amount: z.ZodString;
|
|
204
261
|
currency: z.ZodString;
|
|
205
|
-
}, z.core.$
|
|
262
|
+
}, z.core.$loose>;
|
|
206
263
|
as_of: z.ZodISODateTime;
|
|
207
264
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
208
265
|
type: z.ZodEnum<{
|
|
@@ -213,7 +270,7 @@ export declare const PublicCardSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
213
270
|
}>;
|
|
214
271
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
215
272
|
title: z.ZodOptional<z.ZodString>;
|
|
216
|
-
}, z.core.$
|
|
273
|
+
}, z.core.$loose>>>;
|
|
217
274
|
result: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
218
275
|
type: z.ZodEnum<{
|
|
219
276
|
string: "string";
|
|
@@ -223,45 +280,38 @@ export declare const PublicCardSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
223
280
|
}>;
|
|
224
281
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
225
282
|
title: z.ZodOptional<z.ZodString>;
|
|
226
|
-
}, z.core.$
|
|
283
|
+
}, z.core.$loose>>;
|
|
227
284
|
price_checked_at_purchase: z.ZodBoolean;
|
|
228
|
-
|
|
285
|
+
seller: z.ZodObject<{
|
|
286
|
+
name: z.ZodNullable<z.ZodString>;
|
|
287
|
+
site: z.ZodNullable<z.ZodString>;
|
|
288
|
+
}, z.core.$loose>;
|
|
289
|
+
fulfillment: z.ZodUnion<readonly [z.ZodEnum<{
|
|
290
|
+
sync: "sync";
|
|
291
|
+
async: "async";
|
|
292
|
+
confirm: "confirm";
|
|
293
|
+
}>, z.ZodString]>;
|
|
229
294
|
fulfill_deadline_seconds: z.ZodOptional<z.ZodInt>;
|
|
230
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
231
|
-
id: z.ZodString;
|
|
232
|
-
title: z.ZodString;
|
|
233
|
-
description: z.ZodString;
|
|
234
|
-
price: z.ZodObject<{
|
|
235
|
-
amount: z.ZodString;
|
|
236
|
-
currency: z.ZodString;
|
|
237
|
-
}, z.core.$strict>;
|
|
238
|
-
as_of: z.ZodISODateTime;
|
|
239
|
-
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
240
|
-
type: z.ZodEnum<{
|
|
241
|
-
string: "string";
|
|
242
|
-
number: "number";
|
|
243
|
-
boolean: "boolean";
|
|
244
|
-
integer: "integer";
|
|
245
|
-
}>;
|
|
246
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
247
|
-
title: z.ZodOptional<z.ZodString>;
|
|
248
|
-
}, z.core.$strict>>>;
|
|
249
|
-
result: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
250
|
-
type: z.ZodEnum<{
|
|
251
|
-
string: "string";
|
|
252
|
-
number: "number";
|
|
253
|
-
boolean: "boolean";
|
|
254
|
-
integer: "integer";
|
|
255
|
-
}>;
|
|
256
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
257
|
-
title: z.ZodOptional<z.ZodString>;
|
|
258
|
-
}, z.core.$strict>>;
|
|
259
|
-
price_checked_at_purchase: z.ZodBoolean;
|
|
260
|
-
fulfillment: z.ZodLiteral<"confirm">;
|
|
261
295
|
confirm_deadline_seconds: z.ZodOptional<z.ZodInt>;
|
|
262
|
-
|
|
263
|
-
}, z.core.$strict>], "fulfillment">;
|
|
296
|
+
}, z.core.$loose>;
|
|
264
297
|
export type PublicCard = z.infer<typeof PublicCardSchema>;
|
|
298
|
+
/**
|
|
299
|
+
* A card as `publicCardOf` builds it: the card's own fields without the index
|
|
300
|
+
* signature the open schema adds, and a mode this version knows.
|
|
301
|
+
*
|
|
302
|
+
* An agent reads a card with `PublicCardSchema`, which takes fields and modes
|
|
303
|
+
* added later (ADR-0006 §5); what is built is held to the opposite. The type
|
|
304
|
+
* catches part of that: an unknown field written straight into the returned
|
|
305
|
+
* object is a compile error. It does not reach inside the card's parts, which
|
|
306
|
+
* keep the open schema's index signatures, nor tie a deadline to its mode. The
|
|
307
|
+
* gateway's outbound check holds the rest, at every depth, before anything is
|
|
308
|
+
* sent (`checksBeforeSending`).
|
|
309
|
+
*/
|
|
310
|
+
export type ProjectedCard = {
|
|
311
|
+
[Field in keyof PublicCard as string extends Field ? never : Field]: PublicCard[Field];
|
|
312
|
+
} & {
|
|
313
|
+
readonly fulfillment: Fulfillment;
|
|
314
|
+
};
|
|
265
315
|
/**
|
|
266
316
|
* The card as an agent reads it, built from the card the merchant published.
|
|
267
317
|
*
|
|
@@ -272,13 +322,15 @@ export type PublicCard = z.infer<typeof PublicCardSchema>;
|
|
|
272
322
|
* every agent by default, and the first anybody heard of it would be a
|
|
273
323
|
* merchant's pricing address in a public catalog.
|
|
274
324
|
*
|
|
275
|
-
* The
|
|
276
|
-
* issued when it was published,
|
|
325
|
+
* The three things the card cannot know are passed in: the catalog identifier
|
|
326
|
+
* we issued when it was published, the moment its price was published, and
|
|
327
|
+
* who sells it as their merchant stands now.
|
|
277
328
|
*/
|
|
278
329
|
export declare const publicCardOf: (card: Card, issued: {
|
|
279
330
|
readonly id: string;
|
|
280
331
|
readonly as_of: string;
|
|
281
|
-
|
|
332
|
+
readonly seller: Seller;
|
|
333
|
+
}) => ProjectedCard;
|
|
282
334
|
/**
|
|
283
335
|
* One card as a discovery catalog reads it: what an agent that has never seen
|
|
284
336
|
* our own catalog finds when it searches somewhere else.
|