orb_billing/client/
customers.rs

1// Copyright Materialize, Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use codes_iso_3166::part_1::CountryCode;
17use codes_iso_4217::CurrencyCode;
18use futures_core::Stream;
19use futures_util::stream::TryStreamExt;
20use reqwest::{Method, RequestBuilder};
21use serde::{Deserialize, Serialize};
22use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
23use time::format_description::well_known::Rfc3339;
24use time::{OffsetDateTime, UtcOffset};
25
26use crate::client::taxes::{TaxId, TaxIdRequest};
27use crate::client::Client;
28use crate::config::ListParams;
29use crate::error::Error;
30use crate::serde::Empty;
31use crate::util::StrIteratorExt;
32
33const CUSTOMERS_PATH: [&str; 1] = ["customers"];
34
35#[derive(Deserialize)]
36struct ArrayResponse<T> {
37    data: Vec<T>,
38}
39
40/// A customer ID.
41#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
42pub enum CustomerId<'a> {
43    /// An Orb customer ID.
44    #[serde(rename = "customer_id")]
45    Orb(&'a str),
46    /// A external customer ID.
47    #[serde(rename = "external_customer_id")]
48    External(&'a str),
49}
50
51impl<'a> Default for CustomerId<'a> {
52    fn default() -> CustomerId<'a> {
53        CustomerId::Orb("")
54    }
55}
56
57/// The subset of [`Customer`] used in create requests.
58#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
59pub struct CreateCustomerRequest<'a> {
60    /// An optional user-defined ID for this customer resource, used throughout
61    /// the system as an alias for this customer.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[serde(rename = "external_customer_id")]
64    pub external_id: Option<&'a str>,
65    /// The full name of the customer.
66    pub name: &'a str,
67    /// A valid email for the customer, to be used for notifications.
68    pub email: &'a str,
69    /// Additional email addresses for this customer.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub additional_emails: Option<Vec<&'a str>>,
72    /// The customer's timezone as an identifier from the IANA timezone
73    /// database.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub timezone: Option<&'a str>,
76    /// The external payments or invoicing solution connected to the customer.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    #[serde(flatten)]
79    pub payment_provider: Option<CustomerPaymentProviderRequest<'a>>,
80    /// The customer's shipping address.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub shipping_address: Option<AddressRequest<'a>>,
83    /// The customer's billing address.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub billing_address: Option<AddressRequest<'a>>,
86    /// The currency used for the customer's invoices and balance.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub currency: Option<CurrencyCode>,
89    /// The tax ID details to display on the customer's invoice.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub tax_id: Option<TaxIdRequest<'a>>,
92    /// An idempotency key can ensure that if the same request comes in
93    /// multiple times in a 48-hour period, only one makes changes.
94    // NOTE: this is passed in a request header, not the body
95    #[serde(skip_serializing)]
96    pub idempotency_key: Option<&'a str>,
97}
98
99/// The subset of [`Customer`] used in update requests.
100#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
101pub struct UpdateCustomerRequest<'a> {
102    /// The full name of the customer.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub name: Option<&'a str>,
105    /// A valid email for the customer, to be used for notifications.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub email: Option<&'a str>,
108    /// Additional email addresses for this customer.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub additional_emails: Option<Vec<&'a str>>,
111    /// The external payments or invoicing solution connected to the customer.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    #[serde(flatten)]
114    pub payment_provider: Option<CustomerPaymentProviderRequest<'a>>,
115    /// The customer's shipping address.
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub shipping_address: Option<AddressRequest<'a>>,
118    /// The customer's billing address.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub billing_address: Option<AddressRequest<'a>>,
121    /// The tax ID details to display on the customer's invoice.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub tax_id: Option<TaxIdRequest<'a>>,
124}
125
126/// Configures an external payment or invoicing solution for a customer.
127#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
128pub struct CustomerPaymentProviderRequest<'a> {
129    /// The external payments or invoicing solution type.
130    #[serde(rename = "payment_provider")]
131    pub kind: PaymentProvider,
132    /// The ID of this customer in an external payments solution, such as
133    /// Stripe.
134    #[serde(rename = "payment_provider_id")]
135    pub id: &'a str,
136}
137
138// Deleted variants are immediately filtered out, so boxing the larger
139// `Normal` variant would result in an unnecessary heap allocation.
140#[allow(clippy::large_enum_variant)]
141#[derive(Deserialize)]
142#[serde(untagged)]
143pub(crate) enum CustomerResponse {
144    Normal(Customer),
145    Deleted { id: String, deleted: bool },
146}
147
148/// An Orb customer.
149#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
150pub struct Customer {
151    /// The Orb-assigned unique identifier for the customer.
152    pub id: String,
153    /// An optional user-defined ID for this customer resource, used throughout
154    /// the system as an alias for this customer.
155    #[serde(rename = "external_customer_id")]
156    pub external_id: Option<String>,
157    /// The full name of the customer.
158    pub name: String,
159    /// A valid email for the customer, to be used for notifications.
160    pub email: String,
161    /// Additional email addresses for this customer.
162    pub additional_emails: Vec<String>,
163    /// The customer's timezone as an identifier from the IANA timezone
164    /// database.
165    pub timezone: String,
166    /// The ID of this customer in an external payments solution, such as
167    /// Stripe.
168    pub payment_provider_id: Option<String>,
169    /// The external payments or invoicing solution connected to the customer.
170    pub payment_provider: Option<PaymentProvider>,
171    /// The customer's shipping address.
172    pub shipping_address: Option<Address>,
173    /// The customer's billing address.
174    pub billing_address: Option<Address>,
175    /// The currency used for the customer's invoices and balance.
176    pub currency: Option<CurrencyCode>,
177    /// The tax ID details to display on the customer's invoice.
178    pub tax_id: Option<TaxId>,
179    /// Undocumented upstream.
180    pub auto_collection: bool,
181    /// The customer's current balance in their currency.
182    pub balance: String,
183    /// The time at which the customer was created.
184    #[serde(with = "time::serde::rfc3339")]
185    pub created_at: OffsetDateTime,
186    /// An authenticated URL link to the customer's private Orb dashboard portal.
187    pub portal_url: Option<String>,
188}
189
190/// A payment provider.
191#[non_exhaustive]
192#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize_enum_str, Serialize_enum_str)]
193pub enum PaymentProvider {
194    /// Quickbooks.
195    #[serde(rename = "quickbooks")]
196    Quickbooks,
197    /// Bill.com.
198    #[serde(rename = "bill.com")]
199    BillDotCom,
200    /// Stripe.
201    #[serde(rename = "stripe")]
202    Stripe,
203    /// Stripe charge.
204    #[serde(rename = "stripe_charge")]
205    StripeCharge,
206    /// Stripe invoice.
207    #[serde(rename = "stripe_invoice")]
208    StripeInvoice,
209    /// Other.
210    #[serde(other)]
211    Other(String),
212}
213
214/// The subset of [`Address`] used in create and update requests.
215#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
216pub struct AddressRequest<'a> {
217    /// The city.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub city: Option<&'a str>,
220    /// The country code.
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub country: Option<CountryCode>,
223    /// The first line of the street address.
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub line1: Option<&'a str>,
226    /// The second line of the street address.
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub line2: Option<&'a str>,
229    /// The postal code.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub postal_code: Option<&'a str>,
232    /// The state.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub state: Option<&'a str>,
235}
236
237/// A customer's address.
238#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
239pub struct Address {
240    /// The city.
241    pub city: Option<String>,
242    /// The country code.
243    pub country: Option<CountryCode>,
244    /// The first line of the street address.
245    pub line1: Option<String>,
246    /// The second line of the street address.
247    pub line2: Option<String>,
248    /// The postal code.
249    pub postal_code: Option<String>,
250    /// The state.
251    pub state: Option<String>,
252}
253
254/// The types of ledger entries that can be created.
255#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
256#[serde(tag = "entry_type")]
257pub enum LedgerEntryRequest<'a> {
258    /// Increment a credit balance
259    #[serde(rename = "increment")]
260    Increment(AddIncrementCreditLedgerEntryRequestParams<'a>),
261    /// Void an existing ledger entry
262    #[serde(rename = "void")]
263    Void(AddVoidCreditLedgerEntryRequestParams<'a>),
264    // TODO: additional ledger entry types
265}
266
267/// Optional invoicing settings for a credit purchase.
268#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
269pub struct CreditLedgerInvoiceSettingsRequestParams<'a> {
270    /// Whether the credits purchase invoice should auto collect with the customer's saved payment
271    /// method.
272    pub auto_collection: bool,
273    /// The difference between the invoice date and the issue date for the invoice. If due on issue,
274    /// set this to `0`.
275    pub net_terms: u64,
276    /// An optional memo to display on the invoice
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub memo: Option<&'a str>,
279    /// Whether the credits should be withheld from the customer account until the invoice is paid.
280    /// This applies primarily to stripe invoicing.
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub require_successful_payment: Option<bool>,
283}
284
285/// The parameters used to create a customer credit ledger entry.
286#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
287pub struct AddIncrementCreditLedgerEntryRequestParams<'a> {
288    /// The amount to credit the customer for.
289    pub amount: serde_json::Number,
290    /// An optional description for the credit operation.
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub description: Option<&'a str>,
293    /// The date on which the block's balance will expire.
294    #[serde(with = "time::serde::rfc3339::option")]
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub expiry_date: Option<OffsetDateTime>,
297    /// The date on which the block's balance will become available for use.
298    #[serde(with = "time::serde::rfc3339::option")]
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub effective_date: Option<OffsetDateTime>,
301    /// The price per credit.
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub per_unit_cost_basis: Option<&'a str>,
304    /// Invoicing settings for the credit increment request.
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub invoice_settings: Option<CreditLedgerInvoiceSettingsRequestParams<'a>>,
307}
308
309/// The reason for a void operation.
310#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize_enum_str, Serialize_enum_str)]
311pub enum VoidReason {
312    /// The credits are being returned to the originator.
313    #[serde(rename = "refund")]
314    Refund,
315}
316
317/// The parameters used to void a customer credit ledger entry.
318#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
319pub struct AddVoidCreditLedgerEntryRequestParams<'a> {
320    /// The number of credits to void.
321    pub amount: serde_json::Number,
322    /// The ID of the credit ledger block to void.
323    pub block_id: &'a str,
324    /// An optional reason for the void.
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub void_reason: Option<VoidReason>,
327    /// An optional description for the void operation.
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub description: Option<&'a str>,
330}
331
332/// A block of credit held by a customer.
333#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
334pub struct CustomerCreditBlock {
335    /// The Orb-assigned unique identifier for the credit block.
336    pub id: String,
337    /// The remaining credit balance for the block.
338    pub balance: serde_json::Number,
339    /// The date on which the block's balance will expire.
340    #[serde(with = "time::serde::rfc3339::option")]
341    pub expiry_date: Option<OffsetDateTime>,
342    /// The price per credit.
343    pub per_unit_cost_basis: Option<String>,
344}
345
346/// The type of ledger entry
347#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
348#[serde(tag = "entry_type")]
349pub enum LedgerEntry {
350    /// Incrementing a credit balance
351    #[serde(rename = "increment")]
352    Increment(IncrementLedgerEntry),
353    /// Voiding of an existing ledger entry
354    #[serde(rename = "void")]
355    Void(VoidLedgerEntry),
356    /// Voiding of an existing ledger entry has been initiated
357    #[serde(rename = "void_initiated")]
358    VoidInitiated(VoidInitiatedLedgerEntry),
359    // TODO: additional ledger entry types
360}
361
362/// The state of a ledger entry
363#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize_enum_str)]
364pub enum EntryStatus {
365    /// The entry has been committed to the ledger
366    #[serde(rename = "committed")]
367    Committed,
368    /// The entry hasn't yet been committed to the ledger
369    #[serde(rename = "pending")]
370    Pending,
371}
372
373/// A collection of identifiers for a customer
374#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
375pub struct CustomerIdentifier {
376    /// The Orb-assigned unique identifier for the customer.
377    pub id: String,
378    /// An optional user-defined ID for this customer resource, used throughout
379    /// the system as an alias for this customer.
380    pub external_customer_id: Option<String>,
381}
382
383/// Credit block data associated with entries in a ledger.
384#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
385pub struct LedgerEntryCreditBlock {
386    //// The Orb-assigned unique identifier for the credit block.
387    pub id: String,
388    /// The date on which the block's balance will expire.
389    #[serde(with = "time::serde::rfc3339::option")]
390    pub expiry_date: Option<OffsetDateTime>,
391    /// The price per credit.
392    pub per_unit_cost_basis: Option<String>,
393}
394
395/// Core ledger entry fields.
396#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
397pub struct BaseLedgerEntry {
398    /// The Orb-assigned unique identifier for the ledger entry.
399    pub id: String,
400    /// An incrementing identifier ordering the ledger entry relative to others.
401    pub ledger_sequence_number: u64,
402    /// The state of the ledger entry.
403    pub entry_status: EntryStatus,
404    /// The customer identifiers associated with the ledger entry.
405    pub customer: CustomerIdentifier,
406    /// The customer's credit balance before application of the ledger operation.
407    pub starting_balance: serde_json::Number,
408    /// The customer's credit balance after application of the ledger operation.
409    pub ending_balance: serde_json::Number,
410    /// The amount granted to the ledger.
411    pub amount: serde_json::Number,
412    /// The date the ledger entry was created.
413    #[serde(with = "time::serde::rfc3339")]
414    pub created_at: OffsetDateTime,
415    /// An optional description to associate with the entry.
416    pub description: Option<String>,
417    /// The credit block the ledger entry is modifying.
418    pub credit_block: LedgerEntryCreditBlock,
419}
420
421/// A record of an ledger increment operation.
422#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
423pub struct IncrementLedgerEntry {
424    /// The core ledger entry.
425    #[serde(flatten)]
426    pub ledger: BaseLedgerEntry,
427}
428
429/// A record of a ledger void operation.
430#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
431pub struct VoidLedgerEntry {
432    /// The core ledger entry.
433    #[serde(flatten)]
434    pub ledger: BaseLedgerEntry,
435    /// The reason the ledger entry was voided.
436    pub void_reason: Option<String>,
437    /// The amount voided from the ledger.
438    pub void_amount: serde_json::Number,
439}
440
441/// A record of a ledger void initialization operation.
442#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
443pub struct VoidInitiatedLedgerEntry {
444    /// The core ledger entry.
445    #[serde(flatten)]
446    pub ledger: BaseLedgerEntry,
447    /// The date on which the voided ledger's block will now expire.
448    #[serde(with = "time::serde::rfc3339")]
449    pub new_block_expiry_date: OffsetDateTime,
450    /// The reason the ledger entry was voided.
451    pub void_reason: Option<String>,
452    /// The amount voided from the ledger.
453    pub void_amount: serde_json::Number,
454}
455
456/// The view mode for a cost breakdown.
457#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize_enum_str, Serialize_enum_str)]
458pub enum CostViewMode {
459    /// Provide results as an incremental day-by-day view.
460    #[serde(rename = "periodic")]
461    Periodic,
462    /// Provide results as cumulative totals since the start of the billing period.
463    #[serde(rename = "cumulative")]
464    Cumulative,
465}
466
467#[derive(Debug, Default, Clone)]
468struct CustomerCostParamsFilter<'a> {
469    timeframe_start: Option<&'a OffsetDateTime>,
470    timeframe_end: Option<&'a OffsetDateTime>,
471    view_mode: Option<CostViewMode>,
472    group_by: Option<&'a str>,
473}
474
475trait Filterable<T> {
476    /// Apply the filter to a request.
477    fn apply(self, filter: &T) -> Self;
478}
479
480impl Filterable<CustomerCostParamsFilter<'_>> for RequestBuilder {
481    /// Apply the filter to a request.
482    fn apply(mut self, filter: &CustomerCostParamsFilter) -> Self {
483        if let Some(view_mode) = &filter.view_mode {
484            self = self.query(&[("view_mode", view_mode.to_string())]);
485        }
486        if let Some(group_by) = &filter.group_by {
487            self = self.query(&[("group_by", group_by)]);
488        }
489        if let Some(timeframe_start) = &filter.timeframe_start {
490            self = self.query(&[(
491                "timeframe_start",
492                timeframe_start
493                    // Orb requires supplied datetimes be in UTC
494                    .to_offset(UtcOffset::UTC)
495                    .format(&Rfc3339)
496                    .unwrap(),
497            )]);
498        }
499        if let Some(timeframe_end) = &filter.timeframe_end {
500            self = self.query(&[(
501                "timeframe_end",
502                timeframe_end
503                    // Orb requires supplied datetimes be in UTC
504                    .to_offset(UtcOffset::UTC)
505                    .format(&Rfc3339)
506                    .unwrap(),
507            )]);
508        }
509        self
510    }
511}
512
513/// Parameters for a Customer Costs query.
514#[derive(Debug, Default, Clone)]
515pub struct CustomerCostParams<'a> {
516    filter: CustomerCostParamsFilter<'a>,
517}
518
519impl<'a> CustomerCostParams<'a> {
520    /// The start of the returned range. If not specified this defaults to the billing period start
521    /// date.
522    pub const fn timeframe_start(mut self, timeframe_start: &'a OffsetDateTime) -> Self {
523        self.filter.timeframe_start = Some(timeframe_start);
524        self
525    }
526
527    /// The end of the returned range. If unspecified will default to the billing period end date.
528    pub const fn timeframe_end(mut self, timeframe_end: &'a OffsetDateTime) -> Self {
529        self.filter.timeframe_end = Some(timeframe_end);
530        self
531    }
532
533    /// How costs should be broken down in the resultant day-by-day view.
534    pub const fn view_mode(mut self, view_mode: CostViewMode) -> Self {
535        self.filter.view_mode = Some(view_mode);
536        self
537    }
538
539    /// The custom attribute to group costs by.
540    pub const fn group_by(mut self, group_by: &'a str) -> Self {
541        self.filter.group_by = Some(group_by);
542        self
543    }
544}
545
546/// A group of costs for a given timeframe.
547#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
548pub struct CustomerCostBucket {
549    /// Total costs for the timeframe, excluding any minimums and discounts.
550    pub subtotal: String,
551    /// Total costs for the timeframe, including any minimums and discounts.
552    pub total: String,
553    /// The starting point for the timeframe.
554    #[serde(with = "time::serde::rfc3339")]
555    pub timeframe_start: OffsetDateTime,
556    /// The ending point for the timeframe.
557    #[serde(with = "time::serde::rfc3339")]
558    pub timeframe_end: OffsetDateTime,
559    /// The costs for each price.
560    pub per_price_costs: Vec<CustomerCostPriceBlock>,
561}
562
563/// The cost for a given Price within a timeframe.
564#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
565pub struct CustomerCostPriceBlock {
566    /// The price's quantity for the timeframe.
567    pub quantity: Option<serde_json::Number>,
568    /// The price's contributions for the timeframe, excluding any minimums and discounts.
569    pub subtotal: String,
570    /// The price's contributions for the timeframe, including any minimums and discounts.
571    pub total: String,
572    /// The price that can be billed on a subscription.
573    pub price: CustomerCostPriceBlockPrice,
574    /// The price costs per grouping key.
575    pub price_groups: Option<Vec<CustomerCostPriceBlockPriceGroup>>,
576}
577
578/// A price cost for a given set of grouping keys.
579#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
580pub struct CustomerCostPriceBlockPriceGroup {
581    /// The key breaking down a single price's costs.
582    pub grouping_key: String,
583    /// An optional value for the key.
584    pub grouping_value: Option<String>,
585    /// The second dimension for the matrix price, if applicable.
586    pub secondary_grouping_key: Option<String>,
587    /// An optional value for the `secondary_grouping_key`, if applicable.
588    pub secondary_grouping_value: Option<String>,
589    /// Total costs for this group for the timeframe, excluding any minimums and discounts.
590    // this should be thought of as a "subtotal" to align with the rest of the API, but we're
591    // keeping the existing Orb terminology.
592    pub total: String,
593}
594
595/// The type of pricing
596#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
597#[serde(tag = "model_type")]
598pub enum CustomerCostPriceBlockPrice {
599    /// Sets of unit prices in a one or two-dimensional matrix.
600    #[serde(rename = "matrix")]
601    Matrix(CustomerCostPriceBlockMatrixPrice),
602    /// A fixed amount per unit of usage.
603    #[serde(rename = "unit")]
604    Unit(CustomerCostPriceBlockUnitPrice),
605    // TODO: Add support for additional pricing models
606}
607
608/// Matrix pricing details.
609#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
610pub struct CustomerCostPriceBlockMatrixPrice {
611    /// The Orb-assigned unique identifier for the price block.
612    pub id: String,
613    /// An optional user-defined ID for this price resource.
614    #[serde(rename = "external_price_id")]
615    pub external_id: Option<String>,
616    /// Information about the item being priced.
617    pub item: CustomerCostItem,
618    /// The configuration for this matrix price.
619    pub matrix_config: CustomerCostPriceBlockMatrixPriceConfig,
620}
621
622/// An item being priced.
623#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
624pub struct CustomerCostItem {
625    /// Orb's unique identifier for the item.
626    pub id: String,
627    /// The item's name.
628    pub name: String,
629}
630
631/// Configuration for a pricing matrix.
632#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
633pub struct CustomerCostPriceBlockMatrixPriceConfig {
634    /// The fallback unit amount.
635    pub default_unit_amount: String,
636    /// A collection of dimensions modeled by the matrix.
637    pub dimensions: Vec<Option<String>>,
638    /// All pricing values configured for the matrix.
639    pub matrix_values: Vec<CustomerCostPriceBlockMatrixPriceValue>,
640}
641
642/// A pricing value for a cell within the pricing matrix.
643#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
644pub struct CustomerCostPriceBlockMatrixPriceValue {
645    /// The dimensions corresponding to this cell.
646    pub dimension_values: Vec<Option<String>>,
647    /// The per-unit amount usage within this cell bills.
648    pub unit_amount: String,
649}
650
651/// Unit pricing details.
652#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
653pub struct CustomerCostPriceBlockUnitPrice {
654    /// The Orb-assigned unique identifier for the price block.
655    pub id: String,
656    /// An optional user-defined ID for this price resource.
657    #[serde(rename = "external_price_id")]
658    pub external_id: Option<String>,
659    /// Information about the item being priced.
660    pub item: CustomerCostItem,
661    /// The configuration for this unit price.
662    pub unit_config: CustomerCostPriceBlockUnitPriceConfig,
663}
664
665/// Configuration for a unit price.
666#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
667pub struct CustomerCostPriceBlockUnitPriceConfig {
668    /// Per-unit pricing.
669    pub unit_amount: String,
670}
671
672impl Client {
673    /// Lists all customers.
674    ///
675    /// The underlying API call is paginated. The returned stream will fetch
676    /// additional pages as it is consumed.
677    pub fn list_customers(
678        &self,
679        params: &ListParams,
680    ) -> impl Stream<Item = Result<Customer, Error>> + '_ {
681        let req = self.build_request(Method::GET, CUSTOMERS_PATH);
682        self.stream_paginated_request(params, req)
683            .try_filter_map(|res| async {
684                match res {
685                    CustomerResponse::Normal(c) => Ok(Some(c)),
686                    CustomerResponse::Deleted {
687                        id: _,
688                        deleted: true,
689                    } => Ok(None),
690                    CustomerResponse::Deleted { id, deleted: false } => {
691                        Err(Error::UnexpectedResponse {
692                            detail: format!(
693                                "customer {id} used deleted response shape \
694                                but deleted field was `false`"
695                            ),
696                        })
697                    }
698                }
699            })
700    }
701
702    /// Creates a new customer.
703    pub async fn create_customer(
704        &self,
705        customer: &CreateCustomerRequest<'_>,
706    ) -> Result<Customer, Error> {
707        let mut req = self.build_request(Method::POST, CUSTOMERS_PATH);
708        if let Some(key) = customer.idempotency_key {
709            req = req.header("Idempotency-Key", key);
710        }
711        let req = req.json(customer);
712        let res = self.send_request(req).await?;
713        Ok(res)
714    }
715
716    /// Gets a customer by ID.
717    pub async fn get_customer(&self, id: &str) -> Result<Customer, Error> {
718        let req = self.build_request(Method::GET, CUSTOMERS_PATH.chain_one(id));
719        let res = self.send_request(req).await?;
720        Ok(res)
721    }
722
723    /// Gets a customer by external ID.
724    pub async fn get_customer_by_external_id(&self, external_id: &str) -> Result<Customer, Error> {
725        let req = self.build_request(
726            Method::GET,
727            CUSTOMERS_PATH
728                .chain_one("external_customer_id")
729                .chain_one(external_id),
730        );
731        let res = self.send_request(req).await?;
732        Ok(res)
733    }
734
735    /// Updates a customer by ID.
736    pub async fn update_customer(
737        &self,
738        id: &str,
739        customer: &UpdateCustomerRequest<'_>,
740    ) -> Result<Customer, Error> {
741        let req = self.build_request(Method::PUT, CUSTOMERS_PATH.chain_one(id));
742        let req = req.json(customer);
743        let res = self.send_request(req).await?;
744        Ok(res)
745    }
746
747    /// Updates a customer by external ID.
748    pub async fn update_customer_by_external_id(
749        &self,
750        external_id: &str,
751        customer: &UpdateCustomerRequest<'_>,
752    ) -> Result<Customer, Error> {
753        let req = self.build_request(
754            Method::PUT,
755            CUSTOMERS_PATH
756                .chain_one("external_customer_id")
757                .chain_one(external_id),
758        );
759        let req = req.json(customer);
760        let res = self.send_request(req).await?;
761        Ok(res)
762    }
763
764    /// Deletes a customer by ID.
765    pub async fn delete_customer(&self, id: &str) -> Result<(), Error> {
766        let req = self.build_request(Method::DELETE, CUSTOMERS_PATH.chain_one(id));
767        let _: Empty = self.send_request(req).await?;
768        Ok(())
769    }
770
771    /// Fetch all unexpired, non-zero credit blocks for a customer.
772    ///
773    /// The underlying API call is paginated. The returned stream will fetch
774    /// additional pages as it is consumed.
775    pub fn get_customer_credit_balance(
776        &self,
777        id: &str,
778        params: &ListParams,
779    ) -> impl Stream<Item = Result<CustomerCreditBlock, Error>> + '_ {
780        let req = self.build_request(
781            Method::GET,
782            CUSTOMERS_PATH.chain_one(id).chain_one("credits"),
783        );
784        self.stream_paginated_request(params, req)
785    }
786
787    /// Fetch all unexpired, non-zero credit blocks for a customer by external ID.
788    ///
789    /// The underlying API call is paginated. The returned stream will fetch
790    /// additional pages as it is consumed.
791    pub fn get_customer_credit_balance_by_external_id(
792        &self,
793        external_id: &str,
794        params: &ListParams,
795    ) -> impl Stream<Item = Result<CustomerCreditBlock, Error>> + '_ {
796        let req = self.build_request(
797            Method::GET,
798            CUSTOMERS_PATH
799                .chain_one("external_customer_id")
800                .chain_one(external_id)
801                .chain_one("credits"),
802        );
803        self.stream_paginated_request(params, req)
804    }
805
806    /// Create a new ledger entry for the specified customer's balance.
807    pub async fn create_ledger_entry(
808        &self,
809        id: &str,
810        entry: &LedgerEntryRequest<'_>,
811    ) -> Result<LedgerEntry, Error> {
812        let req = self.build_request(
813            Method::POST,
814            CUSTOMERS_PATH
815                .chain_one(id)
816                .chain_one("credits")
817                .chain_one("ledger_entry"),
818        );
819        let req = req.json(entry);
820        self.send_request(req).await
821    }
822
823    /// Fetch a day-by-day snapshot of a customer's costs.
824    pub async fn get_customer_costs(
825        &self,
826        id: &str,
827        params: &CustomerCostParams<'_>,
828    ) -> Result<Vec<CustomerCostBucket>, Error> {
829        let req = self.build_request(Method::GET, CUSTOMERS_PATH.chain_one(id).chain_one("costs"));
830        let req = req.apply(&params.filter);
831        let res: ArrayResponse<CustomerCostBucket> = self.send_request(req).await?;
832        Ok(res.data)
833    }
834
835    /// Fetch a day-by-day snapshot of a customer's costs by their external ID.
836    pub async fn get_customer_costs_by_external_id(
837        &self,
838        external_id: &str,
839        params: &CustomerCostParams<'_>,
840    ) -> Result<Vec<CustomerCostBucket>, Error> {
841        let req = self.build_request(
842            Method::GET,
843            CUSTOMERS_PATH
844                .chain_one("external_customer_id")
845                .chain_one(external_id)
846                .chain_one("costs"),
847        );
848        let req = req.apply(&params.filter);
849        let res: ArrayResponse<CustomerCostBucket> = self.send_request(req).await?;
850        Ok(res.data)
851    }
852}