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}