Build it
What to do, in the order to do it — the page an app developer works from.
The catalogue
What is for sale, from every seller.
Messaging
Your threads as a buyer, or your shops’ with
filter[side]=seller; attachments, unread counts.Devices
Registering an install so it can be pushed to.
Why it does not use the authorization code flow
The code flow exists so a third party can act for somebody without ever seeing their password. Our own app is not a third party: it is the place people sign in. Sending them out to a browser to approve Wristler to Wristler would be a consent screen about ourselves — more steps, and a worse answer to “is this really Wristler asking”. So the app has three grants of its own:
All three are refused outright for any client that is not first-party. The lock is the
client type in our database, not a scope, so it cannot be widened by asking for
more.
There is no client secret: a secret shipped in a binary is out within the
hour. The app is a public client and the lock is on our side.
The scopes only we may ask for
account:read, account:write
account:read, account:write
Somebody’s own account and everything under
/v1/me: what they put aside, the
searches they want to hear about, their address book, what they bought, the
devices they read us on. A partner acts for a business, and a business has none
of those.account-security:write
account-security:write
The second factor and the recovery code. Its own scope because a token that may
add a favourite should not thereby be able to take somebody’s second factor
away — and every one of these asks for the password again on top.
checkout:write
checkout:write
Placing an order and paying for it. Not part of
account:write: adding a
favourite and paying twelve thousand euros are not the same permission. It moves
money between two of our customers, through our escrow account and our fees,
and there is no version of it a third party performs.catalogue:read
catalogue:read
What is for sale, from every seller. Its own scope because it describes
something a holder may do: it is the one thing here we could reasonably open up
later, to a shop window or a comparison site, and that should be a decision
about this scope alone.
content:read
content:read
The pages people read rather than buy from: filter pages, Insights articles, the
help centre, the personal shoppers. A guest token carries it together with
catalogue:read, and nothing else.business:write
business:write
Changing a shop’s own details: its settings, its company details, whether it takes
offers. Not inventory and not orders, so a client that syncs stock has no use for it.
messages:read, messages:write
messages:read, messages:write
Somebody’s correspondence with their buyers is a different thing to be trusted
with than their stock list.
uploads:write
uploads:write
Sending us a file. Its own scope because uploading is its own thing — and a
partner syncing stock gives a listing
imageSources instead, which is the
better trade for both of us.The app may look at any seller’s page
GET /v1/sellers/{id} and the listings at GET /v1/sellers/{id}/watches are open
to it for any seller, not only the sellers the person acts for, because the app shows a
seller’s shop the way the website does. What comes back is narrowed: somebody else’s
business gives you what is for sale and nothing more — no drafts, no sold listings, no
prices that were never published. Everything else under /v1/sellers/{id}/… — orders,
invoices, quick replies, blocked buyers, the transaction fee — and every write answers
404 for a business the person does not act for, as if it did not exist.
Checkout, and the two things not to build
Do not draw the payment sheet the SDK offers, and do not add anything up. Every method carries its own fee, and on a watch that is the difference between nothing and a few hundred euros — so the choice is a real one, made on numbers only the server knows.POST /v1/me/orders answers with every method its seller offers, in this
language, each with its fee and the total it makes — and available: false with an
unavailableReason for one that cannot be used for where the order ships. Show that
list; post back the code of an available one. A sheet that picks the method itself cannot show those numbers, and refuses
to confirm a payment whose amount is not the one it displayed.
A price never goes in a request. What somebody pays comes from the watch, or from
the offer a seller sent them — relationships.offer, a message id. The offer has to
still be standing and still be the newest; expired is expired, and no amount in a body
would be something we could check.
Three of the methods need nothing native at all. A bank transfer is confirmed by
the server and answers with an IBAN and a reference, which is a screen of text — and it
is the method most of this money arrives through. iDEAL, Bancontact, EPS and
Przelewy24 are confirmed by the server too if you name a returnUrl on the app’s own
scheme: the answer carries a URL to open, and the buyer comes back to the app. A
card is never confirmed by us, because the card details are not ours to receive: with
a returnUrl the answer carries the URL of Stripe’s hosted card page, and without one the
client confirms it with Stripe’s SDK and the clientSecret.
Apple Pay and Google Pay come with the card, at the card’s service costs: Stripe’s hosted
card page shows them where the buyer’s device has one, as long as the environment has not
switched them off (STRIPE_CARD_PAYMENT_METHOD_CONFIGURATION). Nothing in the API changes for
them — paymentMethod: "card" either way.