Skip to main content

kasapay_iyzico/
lib.rs

1//! iyzico, behind kasapay's [`Provider`](kasapay_core::Provider) trait.
2//!
3//! # Four APIs, not one
4//!
5//! iyzico runs four that barely resemble each other, and which one a merchant
6//! uses decides everything about how their code looks — including how it
7//! authenticates, because no two of them do it the same way.
8//!
9//! | | [`in_store`] | [`terminal`] | [`agent`] and [`softpos`] | the rest |
10//! |---|---|---|---|---|
11//! | What it is | the counter-side flow: a till starts a payment, the payer finishes it in iyzico's app | a cash register driving a physical POS device over the counter | a sale on the payer's own phone, over NFC | ordinary card payments, subscriptions, marketplace, card storage, pay-by-link, mass payout, reporting |
12//! | Where | [`in_store`] | [`terminal`] | [`agent`], [`softpos`] | [`classic`], with [`iyzilink`], [`subscription`], [`mass`] and [`reporting`] over the same client |
13//! | Authentication | three plain headers | an OAuth2 bearer token that expires | a dealer secret key, then a session key — **not iyzico's own scheme, and not iyzico's own host** | [`IYZWSv2`](Credentials) request signing |
14//! | Currency | Turkish lira only | lira, dollars, euro | Turkish lira only, by inference — see [`softpos`] | several |
15//! | Implemented here | seven of the twelve filed under In-Store | four of fourteen, and the three-call login filed under In-Store | all five | thirty-eight of seventy |
16//!
17//! A till that cannot hold a secret key safely cannot sign one, which is the
18//! likely reason [`in_store`] does not. Whether its plain headers are the
19//! current mechanism or a legacy one iyzico has not said, and this crate does
20//! not guess.
21//!
22//! [`agent`] and [`softpos`] are not iyzico's own API at all: every one of
23//! their five fragments titles itself `"PayPOS (Paynet) API"` and points at
24//! `api.paynet.com.tr`, a Paynet host, not `api.iyzipay.com`. iyzico
25//! documents it because it resells it. [`agent`]'s module documentation has
26//! the full evidence, including why `specs/iyzico/agent/latest.yaml` and
27//! `specs/iyzico/softpos/latest.yaml` show the wrong host at their top level.
28//!
29//! # Retrying a charge is not documented as safe
30//!
31//! iyzico offers no idempotency key — [`in_store`] refuses one outright with
32//! [`ErrorKind::Unsupported`](kasapay_core::ErrorKind::Unsupported) rather than
33//! accepting one it cannot honour — and does not document what a reused
34//! `orderId` or `conversationId` does.
35//!
36//! So [`Error::is_retryable`](kasapay_core::Error::is_retryable) can be true
37//! for a failure whose retry might take the money twice. A bank timeout is
38//! exactly that case: nobody knows whether the first attempt went through.
39//! Read the payment back before sending it again — and if the timeout means
40//! there is no payment id to read it back *by*,
41//! [`Provider::lookup`](kasapay_core::Provider::lookup) on [`classic`] asks
42//! iyzico by the `conversationId` the charge was sent with. `Ok(None)` there
43//! is iyzico having no record, which is the only honest licence to send it
44//! again.
45//!
46//! # Where the types come from
47//!
48//! iyzico publishes no OpenAPI document. The ones in `specs/iyzico/` are
49//! reassembled from the per-endpoint fragments embedded across their whole
50//! documentation site, in both languages, one file per part of the API. They
51//! record what was documented, not a contract iyzico offers — and they are
52//! incomplete in one direction worth knowing about: the authentication scheme
53//! is documented on a page carrying no fragment, so a spec that declares no
54//! security scheme means the fragment was silent, not that the endpoint is
55//! open.
56//!
57//! Ninety-six operations across eleven groups. Fifty-seven are implemented,
58//! which `python3 scripts/coverage.py` counts rather than anybody remembering.
59//!
60//! Grouping is by path, which is why three of [`terminal`]'s belong to a group
61//! named after another product: its login sits at `/in-store/oauth2/…` and is
62//! filed with In-Store's twelve. `specs/README.md` says why it is the Terminal
63//! API's all the same.
64//!
65//! # Not every response is signed
66//!
67//! iyzico signs the money-moving ones and this crate refuses a signature that
68//! does not match. It does not sign all of them: the classic cancel carries no
69//! signature, [`iyzilink`] documents none on any of its seven,
70//! [`subscription`] documents none on any of its twenty-four, [`mass`] none on
71//! any of its six — including the ones that report where money that has
72//! already left got to — [`onboarding`] none on any of its three,
73//! [`reporting`] none on either of its two, [`terminal`]
74//! none on any of its fourteen, and neither does [`agent`] nor [`softpos`] —
75//! Paynet's own [Response Signature Validation]-equivalent page, if one
76//! exists, was not found; PayPOS's pages name no signature field at all. Each
77//! module says which of its calls are checked and which are only as
78//! trustworthy as the connection they arrived over.
79//!
80//! # Nothing here implements [`Webhook`](kasapay_core::Webhook)
81//!
82//! Not an omission — the trait cannot express what iyzico's callback needs.
83//! [`Webhook::verify`](kasapay_core::Webhook::verify) takes the headers and
84//! the bytes of a delivery, and In-Store's callback cannot be opened with
85//! those alone: the body is an encrypted `data` blob, and the only thing that
86//! opens it is `/crypt/decrypt` **with the `paymentSessionToken` of the
87//! payment it belongs to** — a value the merchant stored when the payment was
88//! opened, not one the delivery carries.
89//!
90//! So it is [`in_store::Client::decrypt_callback`], which takes that token,
91//! and it stays that way until either iyzico posts something that names its
92//! own session or the shared trait grows somewhere to put the caller's own
93//! state. Signing a callback is not what iyzico does anywhere: what they sign
94//! is the *response* to a request, which is what
95//! [`Credentials::verify_response`] checks and what the classic API's own
96//! calls already do.
97//!
98//! [Response Signature Validation]: https://docs.iyzico.com/en/advanced/response-signature-validation
99
100pub mod agent;
101pub mod classic;
102mod errors;
103pub mod in_store;
104pub mod iyzilink;
105pub mod mass;
106pub mod onboarding;
107pub mod reporting;
108mod signing;
109pub mod softpos;
110pub mod subscription;
111pub mod terminal;
112
113#[doc(inline)]
114pub use crate::signing::Credentials;