Skip to content

Commit

Permalink
added search api functionality for charge, customer, invoice, payment…
Browse files Browse the repository at this point in the history
…_intent, price, product, and subscription
  • Loading branch information
hzargar2 committed Sep 5, 2023
1 parent 3dd6fec commit c247bb0
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 6 deletions.
193 changes: 193 additions & 0 deletions src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,199 @@ pub struct ListPaginator<T, P> {
pub params: P,
}

#[derive(Debug)]
pub struct SearchListPaginator<T, P> {
pub page: SearchList<T>,
pub params: P,
}

/// A single page of a cursor-paginated list of a search object.
///
/// For more details, see <https://stripe.com/docs/api/pagination/search>
#[derive(Debug, Deserialize, Serialize)]
pub struct SearchList<T> {
pub object: String,
pub url: String,
pub has_more: bool,
pub data: Vec<T>,
pub next_page: Option<String>,
pub total_count: Option<u64>,
}

impl<T> Default for SearchList<T> {
fn default() -> Self {
SearchList {
object: String::new(),
data: Vec::new(),
has_more: false,
total_count: None,
url: String::new(),
next_page: None,
}
}
}

impl<T: Clone> Clone for SearchList<T> {
fn clone(&self) -> Self {
SearchList {
object: self.object.clone(),
data: self.data.clone(),
has_more: self.has_more,
total_count: self.total_count,
url: self.url.clone(),
next_page: self.next_page.clone(),
}
}
}

impl<T> SearchList<T> {
pub fn paginate<P>(self, params: P) -> SearchListPaginator<T, P> {
SearchListPaginator { page: self, params }
}
}

impl<
T: Paginate + DeserializeOwned + Send + Sync + 'static + Clone + std::fmt::Debug,
P: Clone + Serialize + Send + 'static + std::fmt::Debug,
> SearchListPaginator<T, P>
where
P: Paginable<O = T>,
{
/// Repeatedly queries Stripe for more data until all elements in list are fetched, using
/// Stripe's default page size.
///
/// Requires `feature = "blocking"`.
#[cfg(feature = "blocking")]
pub fn get_all(self, client: &Client) -> Response<Vec<T>> {
let mut data = Vec::with_capacity(self.page.total_count.unwrap_or(0) as usize);
let mut paginator = self;
loop {
if !paginator.page.has_more {
data.extend(paginator.page.data.into_iter());
break;
}
let next_paginator = paginator.next(client)?;
data.extend(paginator.page.data.into_iter());
paginator = next_paginator
}
Ok(data)
}

/// Get all values in this SearchList, consuming self and lazily paginating until all values are fetched.
///
/// This function repeatedly queries Stripe for more data until all elements in list are fetched, using
/// the page size specified in params, or Stripe's default page size if none is specified.
///
/// ```no_run
/// # use stripe::{Customer, SearchListCustomers, StripeError, Client};
/// # use futures_util::TryStreamExt;
/// # async fn run() -> Result<(), StripeError> {
/// # let client = Client::new("sk_test_123");
/// # let params = SearchListCustomers { ..Default::default() };
///
/// let list = Customer::list(&client, &params).await.unwrap().paginate(params);
/// let mut stream = list.stream(&client);
///
/// // take a value out from the stream
/// if let Some(val) = stream.try_next().await? {
/// println!("GOT = {:?}", val);
/// }
///
/// // alternatively, you can use stream combinators
/// let all_values = stream.try_collect::<Vec<_>>().await?;
///
/// # Ok(())
/// # }
/// ```
///
/// Requires `feature = ["async", "stream"]`.
#[cfg(all(feature = "async", feature = "stream"))]
pub fn stream(
mut self,
client: &Client,
) -> impl futures_util::Stream<Item = Result<T, StripeError>> + Unpin {
// We are going to be popping items off the end of the list, so we need to reverse it.
self.page.data.reverse();

Box::pin(futures_util::stream::unfold(Some((self, client.clone())), Self::unfold_stream))
}

/// unfold a single item from the stream
#[cfg(all(feature = "async", feature = "stream"))]
async fn unfold_stream(
state: Option<(Self, Client)>,
) -> Option<(Result<T, StripeError>, Option<(Self, Client)>)> {
let (mut paginator, client) = state?; // If none, we sent the last item in the last iteration

if paginator.page.data.len() > 1 {
return Some((Ok(paginator.page.data.pop()?), Some((paginator, client))));
// We have more data on this page
}

if !paginator.page.has_more {
return Some((Ok(paginator.page.data.pop()?), None)); // Final value of the stream, no errors
}

match paginator.next(&client).await {
Ok(mut next_paginator) => {
let data = paginator.page.data.pop()?;
next_paginator.page.data.reverse();

// Yield last value of thimuts page, the next page (and client) becomes the state
Some((Ok(data), Some((next_paginator, client))))
}
Err(e) => Some((Err(e), None)), // We ran into an error. The last value of the stream will be the error.
}
}

/// Fetch an additional page of data from stripe.
pub fn next(&self, client: &Client) -> Response<Self> {
if let Some(last) = self.page.data.last() {
if self.page.url.starts_with("/v1/") {
let path = self.page.url.trim_start_matches("/v1/").to_string(); // the url we get back is prefixed

// clone the params and set the cursor
let params_next = {
let mut p = self.params.clone();
p.set_last(last.clone());
p
};

let page = client.get_query(&path, &params_next);

SearchListPaginator::create_paginator(page, params_next)
} else {
err(StripeError::UnsupportedVersion)
}
} else {
ok(SearchListPaginator {
page: SearchList {
object: self.page.object.clone(),
data: Vec::new(),
has_more: false,
total_count: self.page.total_count,
url: self.page.url.clone(),
next_page: self.page.next_page.clone(),
},
params: self.params.clone(),
})
}
}

/// Pin a new future which maps the result inside the page future into
/// a SearchListPaginator
#[cfg(feature = "async")]
fn create_paginator(page: Response<SearchList<T>>, params: P) -> Response<Self> {
use futures_util::FutureExt;
Box::pin(page.map(|page| page.map(|page| SearchListPaginator { page, params })))
}

#[cfg(feature = "blocking")]
fn create_paginator(page: Response<SearchList<T>>, params: P) -> Response<Self> {
page.map(|page| SearchListPaginator { page, params })
}
}

/// A single page of a cursor-paginated list of an object.
///
/// For more details, see <https://stripe.com/docs/api/pagination>
Expand Down
7 changes: 7 additions & 0 deletions src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ mod billing {
pub mod usage_record_ext;
}

#[path = "resources"]
#[cfg(feature = "products")]
mod products {
pub mod price_ext;
pub mod product_ext;
}

#[path = "resources"]
#[cfg(feature = "checkout")]
mod checkout {
Expand Down
25 changes: 24 additions & 1 deletion src/resources/charge_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};

use crate::client::{Client, Response};
use crate::ids::{AccountId, BankAccountId, CardId, ChargeId, SourceId, TokenId};
use crate::params::Object;
use crate::params::{Object, SearchList};
use crate::resources::{Charge, Rule};

/// The set of PaymentSource parameters that can be used to create a charge.
Expand Down Expand Up @@ -44,6 +44,13 @@ impl Charge {
) -> Response<Charge> {
client.post_form(&format!("/charges/{}/capture", charge_id), params)
}

/// Searches for a charge.
///
/// For more details see <https://stripe.com/docs/api/charges/search>.
pub fn search(client: &Client, params: ChargeSearchParams) -> Response<SearchList<Charge>> {
client.get_query("/charges/search", params)
}
}

impl Object for Rule {
Expand All @@ -55,3 +62,19 @@ impl Object for Rule {
""
}
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct ChargeSearchParams<'a> {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u64>,
pub expand: &'a [&'a str],
}

impl<'a> ChargeSearchParams<'a> {
pub fn new() -> ChargeSearchParams<'a> {
ChargeSearchParams { query: String::new(), limit: None, page: None, expand: &[] }
}
}
25 changes: 24 additions & 1 deletion src/resources/customer_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};

use crate::client::{Client, Response};
use crate::ids::{BankAccountId, CardId, CustomerId, PaymentSourceId};
use crate::params::{Deleted, Expand, List};
use crate::params::{Deleted, Expand, List, SearchList};
use crate::resources::{
BankAccount, Customer, PaymentMethod, PaymentSource, PaymentSourceParams, Source,
};
Expand Down Expand Up @@ -70,6 +70,22 @@ pub enum CustomerPaymentMethodRetrievalType {
WechatPay,
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct CustomerSearchParams<'a> {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u64>,
pub expand: &'a [&'a str],
}

impl<'a> CustomerSearchParams<'a> {
pub fn new() -> CustomerSearchParams<'a> {
CustomerSearchParams { query: String::new(), limit: None, page: None, expand: &[] }
}
}

impl Customer {
/// Attaches a source to a customer, does not change default Source for the Customer
///
Expand Down Expand Up @@ -132,6 +148,13 @@ impl Customer {
) -> Response<List<PaymentMethod>> {
client.get_query(&format!("/customers/{}/payment_methods", customer_id), &params)
}

/// Searches for a customer.
///
/// For more details see <https://stripe.com/docs/api/customers/search>.
pub fn search(client: &Client, params: CustomerSearchParams) -> Response<SearchList<Customer>> {
client.get_query("/customers/search", params)
}
}

/// The set of parameters that can be used when verifying a Bank Account.
Expand Down
25 changes: 24 additions & 1 deletion src/resources/invoice_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::Serialize;

use crate::client::{Client, Response};
use crate::ids::{CouponId, CustomerId, InvoiceId, PlanId, SubscriptionId, SubscriptionItemId};
use crate::params::{Metadata, Timestamp};
use crate::params::{Metadata, SearchList, Timestamp};
use crate::resources::{CollectionMethod, Invoice};

#[deprecated(since = "0.12.0")]
Expand All @@ -22,6 +22,13 @@ impl Invoice {
pub fn pay(client: &Client, invoice_id: &InvoiceId) -> Response<Invoice> {
client.post(&format!("/invoices/{}/pay", invoice_id))
}

/// Searches for an invoice.
///
/// For more details see <https://stripe.com/docs/api/invoices/search>.
pub fn search(client: &Client, params: InvoiceSearchParams) -> Response<SearchList<Invoice>> {
client.get_query("/invoices/search", params)
}
}

#[derive(Clone, Debug, Serialize)]
Expand Down Expand Up @@ -71,3 +78,19 @@ pub struct SubscriptionItemFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub quantity: Option<u64>,
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct InvoiceSearchParams<'a> {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u64>,
pub expand: &'a [&'a str],
}

impl<'a> InvoiceSearchParams<'a> {
pub fn new() -> InvoiceSearchParams<'a> {
InvoiceSearchParams { query: String::new(), limit: None, page: None, expand: &[] }
}
}
32 changes: 29 additions & 3 deletions src/resources/payment_intent_ext.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use serde::{Deserialize, Serialize};

use crate::{PaymentIntent, PaymentIntentCancellationReason};
use crate::client::{Client, Response};
use crate::params::{Expandable, Metadata};
use crate::resources::{Currency, PaymentIntent, PaymentSource, Shipping};
use crate::PaymentIntentCancellationReason;
use crate::params::{Expandable, Metadata, SearchList};
use crate::resources::{Currency, PaymentSource, Shipping};

impl PaymentIntent {
/// Confirm that customer intends to pay with current or provided source. Upon confirmation, the PaymentIntent will attempt to initiate a payment.
Expand Down Expand Up @@ -38,6 +38,16 @@ impl PaymentIntent {
) -> Response<PaymentIntent> {
client.post_form(&format!("/payment_intents/{}/cancel", payment_intent_id), params)
}

/// Searches for a payment intent.
///
/// For more details see <https://stripe.com/docs/api/payment_intents/search>.
pub fn search(
client: &Client,
params: PaymentIntentSearchParams,
) -> Response<SearchList<PaymentIntent>> {
client.get_query("/payment_intents/search", params)
}
}
/// The resource representing a Stripe PaymentError object.
///
Expand Down Expand Up @@ -205,3 +215,19 @@ pub struct CancelPaymentIntent {
#[serde(skip_serializing_if = "Option::is_none")]
pub cancellation_reason: Option<PaymentIntentCancellationReason>,
}

#[derive(Clone, Debug, Default, Serialize)]
pub struct PaymentIntentSearchParams<'a> {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u64>,
pub expand: &'a [&'a str],
}

impl<'a> PaymentIntentSearchParams<'a> {
pub fn new() -> PaymentIntentSearchParams<'a> {
PaymentIntentSearchParams { query: String::new(), limit: None, page: None, expand: &[] }
}
}
Loading

0 comments on commit c247bb0

Please sign in to comment.