alat/config.rs
1//! Client configuration: gateway host, credentials, and transport tuning.
2//!
3//! # The ALAT gateway topology (important)
4//!
5//! ALAT by Wema publishes its APIs through **Azure API Management (APIM)**. A
6//! single APIM gateway fronts many *products*, each mounted under a path prefix
7//! (e.g. `/funds-transfer-open`, `/bills-payment`, `/wallet-creation`). Every
8//! request is therefore `https://<gateway-host>/<product-prefix>/...`.
9//!
10//! Wema exposes **two public developer sandboxes**, and — crucially — they are
11//! *separate* APIM instances with *separate* subscription keys and *different*
12//! sets of products:
13//!
14//! | Sandbox | Sign-up portal | API gateway (calls go here) |
15//! |---------------|---------------------------------------------------|---------------------------------------------|
16//! | **Playground**| `https://playground.alat.ng` | `https://playground.azure-api.net` |
17//! | **APIM Dev** | `https://wema-alatdev-apimgt.developer.azure-api.net` | `https://wema-alatdev-apimgt.azure-api.net` |
18//!
19//! > The portal host (where you sign up / read keys) is **not** the gateway host
20//! > (where API calls go). The named constructors below target the **gateway**.
21//!
22//! Because a [`Client`](crate::Client) is bound to exactly one gateway + key,
23//! exercising endpoints that live on *both* sandboxes requires two clients (see
24//! the crate-level docs). In **production**, Wema typically fronts all of your
25//! subscribed products behind a single gateway host that they issue to you —
26//! pass that host to [`Config::new`].
27//!
28//! > The previous revision of this SDK defaulted to `https://sandbox.alat.ng`,
29//! > which does not resolve. There is intentionally **no hidden default host**
30//! > here: you must name the gateway explicitly (or use a named sandbox
31//! > constructor), so a typo can never silently point at the wrong bank.
32
33use std::time::Duration;
34
35/// The default per-request timeout applied if the caller does not override it.
36///
37/// Banking calls should never hang indefinitely; 30s is generous for the
38/// synchronous "accepted/pending" responses these endpoints return.
39pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
40
41/// Connection details and credentials for a single ALAT gateway.
42///
43/// A `Config` is consumed by [`Client::new`](crate::Client::new). It is cheap to
44/// clone and holds no live connections.
45///
46/// # Authentication
47///
48/// APIM gates every product with a **subscription key**
49/// (`Ocp-Apim-Subscription-Key`). On top of that, ALAT identifies the calling
50/// software *channel* with an **API key** (`x-api-key`). A subset of products
51/// (bills & airtime) additionally require an **access key** sent in an `access`
52/// header — supply it via [`Config::with_access_key`] when you use those
53/// endpoints.
54#[derive(Debug, Clone)]
55pub struct Config {
56 /// Absolute base URL of the APIM **gateway** (where API calls go, not the
57 /// sign-up portal), e.g. `https://playground.azure-api.net`. Stored without a
58 /// trailing slash.
59 pub base_url: String,
60
61 /// APIM subscription key, sent as the `Ocp-Apim-Subscription-Key` header.
62 /// Obtained from your subscription on the ALAT developer portal.
63 pub subscription_key: String,
64
65 /// Channel API key, sent as the `x-api-key` header. Identifies your
66 /// registered application/partner to ALAT's core systems.
67 pub api_key: String,
68
69 /// Optional "access" credential required by the bills & airtime products,
70 /// sent as the `access` header. `None` if you do not use those endpoints.
71 pub access_key: Option<String>,
72
73 /// Maximum duration to wait for each request before failing with a
74 /// [`Network`](crate::Error::Network) timeout error.
75 pub timeout: Duration,
76}
77
78impl Config {
79 /// Creates a configuration pointing at an explicit gateway host.
80 ///
81 /// This is the honest, production-oriented constructor: you name the gateway
82 /// Wema issued to you. Trailing slashes are trimmed.
83 ///
84 /// ```
85 /// use alat::Config;
86 /// let config = Config::new("https://your-gateway.wemabank.com", "sub_key", "api_key");
87 /// assert_eq!(config.base_url, "https://your-gateway.wemabank.com");
88 /// ```
89 pub fn new(
90 base_url: impl Into<String>,
91 subscription_key: impl Into<String>,
92 api_key: impl Into<String>,
93 ) -> Self {
94 let base_url = base_url.into().trim_end_matches('/').to_string();
95 Self {
96 base_url,
97 subscription_key: subscription_key.into(),
98 api_key: api_key.into(),
99 access_key: None,
100 timeout: DEFAULT_TIMEOUT,
101 }
102 }
103
104 /// Configuration for the **Playground** sandbox gateway
105 /// (`https://playground.azure-api.net`; sign up at `https://playground.alat.ng`).
106 ///
107 /// Use this for wallet creation, account maintenance, statements, bills, and
108 /// airtime — the products published on that portal.
109 pub fn playground(
110 subscription_key: impl Into<String>,
111 api_key: impl Into<String>,
112 ) -> Self {
113 Self::new("https://playground.azure-api.net", subscription_key, api_key)
114 }
115
116 /// Configuration for the **APIM Dev** sandbox
117 /// (`https://wema-alatdev-apimgt.azure-api.net`).
118 ///
119 /// Use this for the funds-transfer / name-enquiry product
120 /// (`/funds-transfer-open`), which is published on that portal rather than
121 /// on Playground.
122 pub fn apim_dev(
123 subscription_key: impl Into<String>,
124 api_key: impl Into<String>,
125 ) -> Self {
126 Self::new(
127 "https://wema-alatdev-apimgt.azure-api.net",
128 subscription_key,
129 api_key,
130 )
131 }
132
133 /// Attaches the `access` credential required by bills & airtime endpoints
134 /// (builder style).
135 ///
136 /// ```
137 /// use alat::Config;
138 /// let config = Config::playground("sub", "api").with_access_key("bills_access_token");
139 /// assert!(config.access_key.is_some());
140 /// ```
141 pub fn with_access_key(mut self, access_key: impl Into<String>) -> Self {
142 self.access_key = Some(access_key.into());
143 self
144 }
145
146 /// Overrides the default per-request [`timeout`](Self::timeout) (builder style).
147 pub fn with_timeout(mut self, timeout: Duration) -> Self {
148 self.timeout = timeout;
149 self
150 }
151}