Skip to main content

k_ruoka_mcp/browser/
offers.rs

1//! OmaPlussa-edut: the account's personalised Plussa offers.
2//!
3//! Read-only, and per-account like the cart, so `Session` reaches it with the same
4//! cookies `Cart` and `Catalog` already carry.
5
6use crate::browser::session::{ApiError, KrApi};
7use crate::types::PersonalOffersResponse;
8
9pub struct Offers<'a> {
10    api: &'a dyn KrApi,
11}
12
13impl<'a> Offers<'a> {
14    pub fn new(api: &'a dyn KrApi) -> Self {
15        Self { api }
16    }
17
18    /// The account's personalised offers, scoped to a store.
19    ///
20    /// Measured against two real stores: the set differs rather than being identical
21    /// everywhere, matching the restriction text K-Ruoka attaches to each offer ("valid
22    /// in the K-food stores where the product is available").
23    ///
24    /// Every offer observed so far was already loaded onto the Plussa card, applying
25    /// one is just buying a listed product -- not verified as a universal guarantee,
26    /// since nothing here has tried to find or call a separate activation endpoint.
27    /// Time-limited (each offer carries its own `validUntil`), so the caller must
28    /// never cache this. An anonymous session returns `{"offers": []}` rather than an
29    /// error, measured against a fresh, never-logged-in profile.
30    pub async fn personal_offers(
31        &self,
32        store_id: &str,
33    ) -> Result<PersonalOffersResponse, ApiError> {
34        let body = serde_json::json!({ "storeId": store_id });
35        let value = self
36            .api
37            .call("POST", "/kr-api/tos-offers", Some(&body))
38            .await?;
39        serde_json::from_value(value)
40            .map_err(|e| ApiError::Other(anyhow::anyhow!("unexpected tos-offers shape: {e}")))
41    }
42}