Skip to main content

gingr/endpoint/
commerce_retail.rs

1use super::{Date, Error, LocationId, Method, OwnerId, Request, Result};
2
3fn cutover_date() -> Date {
4    Date::parse("2019-08-01").expect("documented Gingr commerce cutover date is valid")
5}
6
7fn push_optional<T: core::fmt::Display>(
8    params: &mut Vec<(String, String)>,
9    key: &str,
10    value: Option<T>,
11) {
12    if let Some(value) = value {
13        params.push((key.to_owned(), value.to_string()));
14    }
15}
16
17/// Gingr get endpoint boundary with provider parameters kept explicit.
18pub mod get {
19    use super::*;
20
21    #[derive(Clone, Debug, Default, PartialEq, Eq)]
22    /// Request descriptor for the Gingr retail-item catalog used as source evidence for inventory and upsell workflows.
23    pub struct AllRetailItems;
24
25    impl Request for AllRetailItems {
26        fn method(&self) -> Method {
27            Method::Get
28        }
29
30        fn path(&self) -> &'static str {
31            "/api/v1/get_all_retail_items"
32        }
33
34        fn parameters(&self) -> Vec<(String, String)> {
35            Vec::new()
36        }
37    }
38
39    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
40    /// Provider subscription identifier used when requesting one Gingr package/subscription record.
41    pub struct SubscriptionId(u64);
42
43    impl SubscriptionId {
44        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
45        pub fn new(value: u64) -> Self {
46            Self(value)
47        }
48    }
49
50    impl core::fmt::Display for SubscriptionId {
51        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52            write!(formatter, "{}", self.0)
53        }
54    }
55
56    #[derive(Clone, Debug, PartialEq, Eq)]
57    /// Request descriptor for one Gingr subscription/package record by provider ID.
58    pub struct Subscription {
59        id: SubscriptionId,
60    }
61
62    impl Subscription {
63        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
64        pub fn new(id: SubscriptionId) -> Self {
65            Self { id }
66        }
67    }
68
69    impl Request for Subscription {
70        fn method(&self) -> Method {
71            Method::Get
72        }
73
74        fn path(&self) -> &'static str {
75            "/api/v1/get_subscription"
76        }
77
78        fn parameters(&self) -> Vec<(String, String)> {
79            vec![("id".to_owned(), self.id.to_string())]
80        }
81    }
82
83    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
84    /// Validated month-day filter accepted by Gingr subscription endpoints.
85    pub struct BillDayOfMonth(u8);
86
87    impl BillDayOfMonth {
88        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
89        pub fn new(value: u8) -> Result<Self> {
90            if (1..=31).contains(&value) {
91                Ok(Self(value))
92            } else {
93                Err(Error::InvalidBillDayOfMonth { value })
94            }
95        }
96    }
97
98    impl core::fmt::Display for BillDayOfMonth {
99        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100            write!(formatter, "{}", self.0)
101        }
102    }
103
104    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
105    /// Provider package identifier used to filter Gingr subscriptions.
106    pub struct PackageId(u64);
107
108    impl PackageId {
109        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
110        pub fn new(value: u64) -> Self {
111            Self(value)
112        }
113    }
114
115    impl core::fmt::Display for PackageId {
116        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
117            write!(formatter, "{}", self.0)
118        }
119    }
120
121    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
122    /// Provider pagination controls for subscription list requests.
123    pub struct SubscriptionPagination {
124        limit: u64,
125        offset: u64,
126    }
127
128    impl SubscriptionPagination {
129        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
130        pub fn new(limit: u64, offset: u64) -> Self {
131            Self { limit, offset }
132        }
133    }
134
135    #[derive(Clone, Debug, Default, PartialEq, Eq)]
136    /// Request descriptor for Gingr subscriptions/packages, including owner, bill-day, location, package, and deletion filters.
137    pub struct Subscriptions {
138        include_deleted: Option<bool>,
139        bill_day_of_month: Option<BillDayOfMonth>,
140        owner_id: Option<OwnerId>,
141        pagination: Option<SubscriptionPagination>,
142        location_id: Option<LocationId>,
143        package_id: Option<PackageId>,
144    }
145
146    impl Subscriptions {
147        /// Starts a builder that makes each provider parameter explicit before request capture.
148        pub fn builder() -> SubscriptionsBuilder {
149            SubscriptionsBuilder::default()
150        }
151    }
152
153    #[derive(Clone, Debug, Default)]
154    /// Builder for Gingr subscription filters without turning package data into NVA revenue facts.
155    pub struct SubscriptionsBuilder {
156        include_deleted: Option<bool>,
157        bill_day_of_month: Option<BillDayOfMonth>,
158        owner_id: Option<OwnerId>,
159        pagination: Option<SubscriptionPagination>,
160        location_id: Option<LocationId>,
161        package_id: Option<PackageId>,
162    }
163
164    impl SubscriptionsBuilder {
165        /// Includes deleted provider records when Gingr supports that filter.
166        pub fn include_deleted(mut self, include_deleted: bool) -> Self {
167            self.include_deleted = Some(include_deleted);
168            self
169        }
170
171        /// Filters subscriptions by provider bill day of month.
172        pub fn bill_day_of_month(mut self, bill_day_of_month: BillDayOfMonth) -> Self {
173            self.bill_day_of_month = Some(bill_day_of_month);
174            self
175        }
176
177        /// Narrows the provider request to one Gingr owner/customer identifier.
178        pub fn owner_id(mut self, owner_id: OwnerId) -> Self {
179            self.owner_id = Some(owner_id);
180            self
181        }
182
183        /// Applies provider pagination controls to the request.
184        pub fn pagination(mut self, pagination: SubscriptionPagination) -> Self {
185            self.pagination = Some(pagination);
186            self
187        }
188
189        /// Scopes the Gingr endpoint request to a location.
190        pub fn location_id(mut self, location_id: LocationId) -> Self {
191            self.location_id = Some(location_id);
192            self
193        }
194
195        /// Filters subscription requests to a package identifier.
196        pub fn package_id(mut self, package_id: PackageId) -> Self {
197            self.package_id = Some(package_id);
198            self
199        }
200
201        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
202        pub fn build(self) -> Subscriptions {
203            Subscriptions {
204                include_deleted: self.include_deleted,
205                bill_day_of_month: self.bill_day_of_month,
206                owner_id: self.owner_id,
207                pagination: self.pagination,
208                location_id: self.location_id,
209                package_id: self.package_id,
210            }
211        }
212    }
213
214    impl Request for Subscriptions {
215        fn method(&self) -> Method {
216            Method::Get
217        }
218
219        fn path(&self) -> &'static str {
220            "/api/v1/get_subscriptions"
221        }
222
223        fn parameters(&self) -> Vec<(String, String)> {
224            let mut params = Vec::new();
225            push_optional(&mut params, "include_deleted", self.include_deleted);
226            push_optional(&mut params, "bill_day_of_month", self.bill_day_of_month);
227            push_optional(&mut params, "owner_id", self.owner_id);
228            if let Some(pagination) = self.pagination {
229                params.push(("limit".to_owned(), pagination.limit.to_string()));
230                params.push(("offset".to_owned(), pagination.offset.to_string()));
231            }
232            push_optional(&mut params, "location_id", self.location_id);
233            push_optional(&mut params, "package_id", self.package_id);
234            params
235        }
236    }
237}
238
239/// Gingr list endpoint boundary with provider parameters kept explicit.
240pub mod list {
241    use super::*;
242
243    #[derive(Clone, Debug, PartialEq, Eq)]
244    /// Request descriptor for Gingr transaction lists over a validated provider date window.
245    pub struct Transactions {
246        from_date: Date,
247        to_date: Date,
248    }
249
250    impl Transactions {
251        /// Starts a builder that makes each provider parameter explicit before request capture.
252        pub fn builder() -> TransactionsBuilder {
253            TransactionsBuilder::default()
254        }
255    }
256
257    #[derive(Clone, Debug, Default)]
258    /// Builder for the transaction date window used by commerce reconciliation workflows.
259    pub struct TransactionsBuilder {
260        from_date: Option<Date>,
261        to_date: Option<Date>,
262    }
263
264    impl TransactionsBuilder {
265        /// Sets the inclusive provider start date sent to Gingr.
266        pub fn from_date(mut self, from_date: Date) -> Self {
267            self.from_date = Some(from_date);
268            self
269        }
270
271        /// Sets the inclusive provider end date sent to Gingr.
272        pub fn to_date(mut self, to_date: Date) -> Self {
273            self.to_date = Some(to_date);
274            self
275        }
276
277        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
278        pub fn build(self) -> Result<Transactions> {
279            let from_date = self.from_date.ok_or(Error::MissingRequiredParameter {
280                parameter: "from_date",
281            })?;
282            let to_date = self.to_date.ok_or(Error::MissingRequiredParameter {
283                parameter: "to_date",
284            })?;
285            let cutover = cutover_date();
286            if from_date >= cutover {
287                return Err(Error::LegacyDateBoundary {
288                    date: from_date.to_string(),
289                    boundary: "list_transactions only returns POS transactions before 2019-08-01",
290                });
291            }
292            if to_date >= cutover {
293                return Err(Error::LegacyDateBoundary {
294                    date: to_date.to_string(),
295                    boundary: "list_transactions only returns POS transactions before 2019-08-01",
296                });
297            }
298            Ok(Transactions { from_date, to_date })
299        }
300    }
301
302    impl Request for Transactions {
303        fn method(&self) -> Method {
304            Method::Get
305        }
306
307        fn path(&self) -> &'static str {
308            "/api/v1/list_transactions"
309        }
310
311        fn parameters(&self) -> Vec<(String, String)> {
312            vec![
313                ("from_date".to_owned(), self.from_date.to_string()),
314                ("to_date".to_owned(), self.to_date.to_string()),
315            ]
316        }
317    }
318
319    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
320    /// Provider pagination controls for invoice list requests after the documented Gingr cutover date.
321    pub struct InvoicePagination {
322        per_page: u64,
323        page: u64,
324    }
325
326    impl InvoicePagination {
327        /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
328        pub fn new(per_page: u64, page: u64) -> Result<Self> {
329            if per_page == 0 {
330                return Err(Error::InvalidPagination {
331                    reason: "list_invoices per_page must be greater than zero",
332                });
333            }
334            if page == 0 || !(page - 1).is_multiple_of(per_page) {
335                return Err(Error::InvalidPagination {
336                    reason: "list_invoices page is a one-based starting result number incremented by per_page",
337                });
338            }
339            Ok(Self { per_page, page })
340        }
341    }
342
343    #[derive(Clone, Debug, Default, PartialEq, Eq)]
344    /// Request descriptor for Gingr invoice lists used as raw billing evidence, not payment-policy authority.
345    pub struct Invoices {
346        pagination: Option<InvoicePagination>,
347        complete: Option<bool>,
348        closed_only: Option<bool>,
349        from_date: Option<Date>,
350        to_date: Option<Date>,
351    }
352
353    impl Invoices {
354        /// Starts a builder that makes each provider parameter explicit before request capture.
355        pub fn builder() -> InvoicesBuilder {
356            InvoicesBuilder::default()
357        }
358    }
359
360    #[derive(Clone, Debug, Default)]
361    /// Builder for invoice date, owner, location, and pagination filters.
362    pub struct InvoicesBuilder {
363        pagination: Option<InvoicePagination>,
364        complete: Option<bool>,
365        closed_only: Option<bool>,
366        from_date: Option<Date>,
367        to_date: Option<Date>,
368    }
369
370    impl InvoicesBuilder {
371        /// Applies provider pagination controls to the request.
372        pub fn pagination(mut self, pagination: InvoicePagination) -> Self {
373            self.pagination = Some(pagination);
374            self
375        }
376
377        /// Filters invoice results by completion state.
378        pub fn complete(mut self, complete: bool) -> Self {
379            self.complete = Some(complete);
380            self
381        }
382
383        /// Restricts invoice results to closed Gingr invoices.
384        pub fn closed_only(mut self, closed_only: bool) -> Self {
385            self.closed_only = Some(closed_only);
386            self
387        }
388
389        /// Sets the inclusive provider start date sent to Gingr.
390        pub fn from_date(mut self, from_date: Date) -> Self {
391            self.from_date = Some(from_date);
392            self
393        }
394
395        /// Sets the inclusive provider end date sent to Gingr.
396        pub fn to_date(mut self, to_date: Date) -> Self {
397            self.to_date = Some(to_date);
398            self
399        }
400
401        /// Finalizes the provider request descriptor after required fields are present and wrappers have validated local invariants.
402        pub fn build(self) -> Result<Invoices> {
403            let cutover = cutover_date();
404            for date in [&self.from_date, &self.to_date].into_iter().flatten() {
405                if date < &cutover {
406                    return Err(Error::LegacyDateBoundary {
407                        date: date.to_string(),
408                        boundary: "list_invoices only returns invoices created on or after 2019-08-01",
409                    });
410                }
411            }
412            Ok(Invoices {
413                pagination: self.pagination,
414                complete: self.complete,
415                closed_only: self.closed_only,
416                from_date: self.from_date,
417                to_date: self.to_date,
418            })
419        }
420    }
421
422    impl Request for Invoices {
423        fn method(&self) -> Method {
424            Method::Get
425        }
426
427        fn path(&self) -> &'static str {
428            "/api/v1/list_invoices"
429        }
430
431        fn parameters(&self) -> Vec<(String, String)> {
432            let mut params = Vec::new();
433            if let Some(pagination) = self.pagination {
434                params.push(("per_page".to_owned(), pagination.per_page.to_string()));
435                params.push(("page".to_owned(), pagination.page.to_string()));
436            }
437            push_optional(&mut params, "complete", self.complete);
438            push_optional(&mut params, "closed_only", self.closed_only);
439            push_optional(&mut params, "from_date", self.from_date);
440            push_optional(&mut params, "to_date", self.to_date);
441            params
442        }
443    }
444}
445
446#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447/// Provider transaction identifier used when requesting one Gingr transaction record.
448pub struct TransactionId(u64);
449
450impl TransactionId {
451    /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
452    pub fn new(value: u64) -> Self {
453        Self(value)
454    }
455}
456
457impl core::fmt::Display for TransactionId {
458    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
459        write!(formatter, "{}", self.0)
460    }
461}
462
463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464/// Classification for whether a Gingr response can be logged or must be quarantined.
465pub enum ResponseSensitivity {
466    /// Response may include payment-related details and must stay log-quarantined.
467    PaymentSensitive,
468}
469
470#[derive(Clone, Debug, PartialEq, Eq)]
471/// Request descriptor for one Gingr transaction record by provider ID.
472pub struct Transaction {
473    id: TransactionId,
474}
475
476impl Transaction {
477    /// Constructs this typed Gingr boundary value after the caller has chosen the provider input to trust.
478    pub fn new(id: TransactionId) -> Self {
479        Self { id }
480    }
481
482    /// Describes whether a response payload should be quarantined from normal logs.
483    pub fn sensitivity(&self) -> ResponseSensitivity {
484        ResponseSensitivity::PaymentSensitive
485    }
486}
487
488impl Request for Transaction {
489    fn method(&self) -> Method {
490        Method::Post
491    }
492
493    fn path(&self) -> &'static str {
494        "/api/v1/transaction"
495    }
496
497    fn parameters(&self) -> Vec<(String, String)> {
498        vec![("id".to_owned(), self.id.to_string())]
499    }
500}