Skip to main content

kasapay_core/
lib.rs

1//! Provider-neutral payment types and the trait every kasapay adapter implements.
2//!
3//! Nothing here talks to a network. It defines what a charge is, what an
4//! amount is, and what a failure is, so that [`kasapay-stripe`] and
5//! [`kasapay-iyzico`] can disagree about everything else.
6//!
7//! # The shape of a payment
8//!
9//! [`Provider::charge`] does not return a completed payment. It returns a
10//! [`Charge`] with a [`Status`], and — where the payer still has work to do —
11//! a [`NextAction`]. Stripe answers a `client_secret` to confirm in the
12//! browser; iyzico answers a deep link into its own app. Both are the same
13//! shape here, and neither is a success yet.
14//!
15//! # Authorising and capturing
16//!
17//! [`Provider::capture`] takes funds an authorisation is only holding, and
18//! [`Provider::cancel`] releases one that will never be taken. Not every
19//! provider separates the two — iyzico's In-Store flow takes the money at
20//! authorisation — so [`Provider::capabilities`] says which, and it says so
21//! before there is a payment to ask about.
22//!
23//! # Giving money back
24//!
25//! [`Provider::refund`] is the only way money goes back: a capture has no
26//! inverse. A [`Refund`] is its own object with its own life — its own
27//! identifier where the provider issues one, its own [`RefundStatus`], and,
28//! at one provider, its own [`NextAction`] for the payer to approve.
29//!
30//! [`Status`] has no `Refunded` and will not grow one. No provider reports a
31//! refund as a payment's status — Stripe leaves a refunded PaymentIntent
32//! `succeeded` — so the variant would be a branch that never runs for most of
33//! them. "Has all of this gone back" is the refunds summed against
34//! [`Charge::amount`].
35//!
36//! # The call whose answer never arrived
37//!
38//! A charge that times out is the one failure where nobody knows whether the
39//! money moved. [`Provider::lookup`] is the question to ask: keyed by
40//! [`OrderRef`] — the caller's own reference, which they had before they sent
41//! anything — rather than by an identifier the lost reply never delivered.
42//!
43//! `Ok(None)` says the provider has no record and the charge can be sent
44//! again. Two of the five can answer it, [`Capabilities::lookup_by_order`]
45//! says which, and the ones that cannot say what to do instead on
46//! [`Provider::lookup`] itself. Guessing is not on the list: an answer of "no
47//! record" that is merely late is how a caller authorises twice.
48//!
49//! # What arrives without being asked for
50//!
51//! A payment that finishes out of band is not observable through
52//! [`Provider`] at all: the payer goes away and comes back somewhere else.
53//! [`Webhook::verify`] is the other half — it takes the bytes a provider
54//! posted, shows they are the provider's, and says what they mean as an
55//! [`Event`].
56//!
57//! It is a separate trait because it is a separate thing to hold: verifying
58//! needs a webhook secret the API credentials do not carry. It is `async`
59//! because verification is not one mechanism — Stripe signs the bytes, PayTR
60//! signs three fields of them, and Mollie signs nothing at all and posts an
61//! identifier to read back.
62//!
63//! An [`EventKind`] this crate does not model is [`EventKind::Other`] and
64//! never an error. A provider retries a delivery until it is acknowledged, so
65//! refusing an unknown type is how a shop earns a week of redeliveries for an
66//! event nobody wanted.
67//!
68//! # Identifiers
69//!
70//! [`OrderRef`] is the caller's own reference for an order, and [`PaymentId`]
71//! is how the provider names the payment that came of it. They are not the same
72//! string even where they carry the same characters.
73//!
74//! Two questions are asked of every identifier the provider issues, and both
75//! are answered by [`Id`], of which `PaymentId` is one kind.
76//!
77//! **What does it name?** The type says, and the compiler holds it: `PaymentId`
78//! is [`Id<kind::Payment>`](Id), and an adapter that hands back a handle to
79//! something else — iyzico's classic API names a hosted checkout form by a
80//! token that is not a payment id — declares a kind of its own with [`IdKind`]
81//! rather than lending this one out. Two identifiers the same provider issued
82//! are alike enough to confuse, and the kind is what separates them.
83//!
84//! **Whose uniqueness does it rest on?** [`PaymentId::source`] says. PayTR
85//! issues no identifier at all and names a payment by the `merchant_oid` it was
86//! sent, so its source is [`IdSource::Derived`] and names that field. A caller
87//! relying on an identifier being unique — writing it into a unique index,
88//! keying a retry on it — is relying on the provider's guarantee or on their
89//! own, and this is what tells the two apart.
90//!
91//! [`Charge::id`] is an `Option` for the provider that has not named the
92//! payment yet, and never an empty string.
93//!
94//! # No type here holds a card number
95//!
96//! There is no field on [`ChargeRequest`] for one and there will not be. A
97//! server that touches a card number is in PCI DSS scope on the merchant's
98//! longest self-assessment rather than its shortest, and a library that makes
99//! it easy to put one in a struct makes it easy to end up there without
100//! noticing. Every provider kasapay ships has a way of taking a payment that
101//! never sends a number through the caller's process — a page the provider
102//! hosts, a token the payer's browser makes, a redirect — and those are the
103//! ways kasapay implements.
104//!
105//! What a returning customer needs instead is [`InstrumentId`]: the provider
106//! keeps the card and hands back a handle to it, and the handle is what a
107//! payment carries. Charging one is not the same act as taking a card number,
108//! and only the first of the two is here.
109//!
110//! [`Provider::instruments`] lists what a customer has on file — every
111//! adapter answers the same shape, an [`Instrument`] carrying the identity and
112//! something to show a person choosing between them. Charging one is not:
113//! iyzico wants a buyer and a basket beside the token, Stripe an
114//! `off_session` flag, Mollie a `sequenceType`, so that call stays each
115//! adapter's own.
116//!
117//! # Amounts
118//!
119//! [`Money`] counts minor units. There is no `f64` anywhere in this crate,
120//! and [`Money::parse`] refuses precision a currency does not have rather than
121//! rounding it away.
122//!
123//! [`kasapay-stripe`]: https://docs.rs/kasapay-stripe
124//! [`kasapay-iyzico`]: https://docs.rs/kasapay-iyzico
125
126mod charge;
127mod error;
128mod id;
129mod instrument;
130mod money;
131mod party;
132mod provider;
133mod raw;
134mod refund;
135mod secret;
136mod webhook;
137
138#[doc(inline)]
139pub use crate::charge::{
140    Charge, ChargeRequest, ChargeRequestBuilder, ChargeRequestError, IdempotencyKey, NextAction,
141    OrderRef, Status,
142};
143#[doc(inline)]
144pub use crate::error::{Error, ErrorKind};
145#[doc(inline)]
146pub use crate::id::{EventId, Id, IdKind, IdSource, InstrumentId, PaymentId, RefundId, kind};
147#[doc(inline)]
148pub use crate::instrument::Instrument;
149#[doc(inline)]
150pub use crate::money::{Currency, Money, MoneyError, UnknownCurrency};
151#[doc(inline)]
152pub use crate::party::{Address, BasketItem, Buyer, ItemKind};
153#[doc(inline)]
154pub use crate::provider::{Capabilities, Provider, ProviderId, async_trait};
155#[doc(inline)]
156pub use crate::raw::Raw;
157#[doc(inline)]
158pub use crate::refund::{
159    Refund, RefundReason, RefundRequest, RefundRequestBuilder, RefundRequestError, RefundStatus,
160};
161#[doc(inline)]
162pub use crate::secret::Secret;
163#[doc(inline)]
164pub use crate::webhook::{Delivery, Event, EventKind, RepeatedHeader, Webhook};