paypal_rs/api/
invoice.rs

1//! Use the Invoicing API to create, send, and manage invoices.
2//! You can also use the API or webhooks to track invoice payments. When you send an invoice to a customer,
3//! the invoice moves from draft to payable state. PayPal then emails the customer a link to the invoice on the PayPal website.
4//! Customers with a PayPal account can log in and pay the invoice with PayPal. Alternatively,
5//! customers can pay as a guest with a debit card or credit card. For more information, see the Invoicing Overview and the Invoicing Integration Guide.
6//!
7//! Reference: <https://developer.paypal.com/docs/api/invoicing/v2/>
8
9use std::borrow::Cow;
10
11use derive_builder::Builder;
12use serde::Serialize;
13
14use crate::{
15    data::{
16        invoice::{CancelReason, Invoice, InvoiceList, InvoicePayload, SendInvoicePayload},
17        orders::InvoiceNumber,
18    },
19    endpoint::Endpoint,
20    Query,
21};
22
23/// Generates the next invoice number that is available to the merchant.
24///
25/// The next invoice number uses the prefix and suffix from the last invoice number and increments the number by one.
26///
27/// For example, the next invoice number after `INVOICE-1234` is `INVOICE-1235`.
28#[derive(Debug, Default, Clone)]
29pub struct GenerateInvoiceNumber {
30    /// The invoice number. If you omit this value, the default is the auto-incremented number from the last number.
31    pub invoice_number: Option<InvoiceNumber>,
32}
33
34impl GenerateInvoiceNumber {
35    /// New constructor.
36    pub fn new(invoice_number: Option<InvoiceNumber>) -> Self {
37        Self { invoice_number }
38    }
39}
40
41impl Endpoint for GenerateInvoiceNumber {
42    type Query = ();
43
44    type Body = Option<InvoiceNumber>;
45
46    type Response = InvoiceNumber;
47
48    fn relative_path(&self) -> Cow<str> {
49        Cow::Borrowed("/v2/invoicing/generate-next-invoice-number")
50    }
51
52    fn method(&self) -> reqwest::Method {
53        reqwest::Method::POST
54    }
55
56    fn body(&self) -> Option<Self::Body> {
57        Some(self.invoice_number.clone())
58    }
59}
60
61/// Creates a draft invoice. To move the invoice from a draft to payable state, you must send the invoice.
62/// Include invoice details including merchant information. The invoice object must include an items array.
63#[derive(Debug, Clone)]
64pub struct CreateDraftInvoice {
65    /// The invoice details.
66    pub invoice: InvoicePayload,
67}
68
69impl CreateDraftInvoice {
70    /// New constructor.
71    pub fn new(invoice: InvoicePayload) -> Self {
72        Self { invoice }
73    }
74}
75
76impl Endpoint for CreateDraftInvoice {
77    type Query = ();
78
79    type Body = InvoicePayload;
80
81    type Response = Invoice;
82
83    fn relative_path(&self) -> Cow<str> {
84        Cow::Borrowed("/v2/invoicing/invoices")
85    }
86
87    fn method(&self) -> reqwest::Method {
88        reqwest::Method::POST
89    }
90
91    fn body(&self) -> Option<Self::Body> {
92        Some(self.invoice.clone())
93    }
94}
95
96/// Shows details for an invoice, by ID.
97#[derive(Debug, Clone)]
98pub struct GetInvoice {
99    /// The invoice id.
100    pub invoice_id: String,
101}
102
103impl GetInvoice {
104    /// New constructor.
105    pub fn new(invoice_id: impl ToString) -> Self {
106        Self {
107            invoice_id: invoice_id.to_string(),
108        }
109    }
110}
111
112impl Endpoint for GetInvoice {
113    type Query = ();
114
115    type Body = ();
116
117    type Response = Invoice;
118
119    fn relative_path(&self) -> Cow<str> {
120        Cow::Owned(format!("/v2/invoicing/invoices/{}", self.invoice_id))
121    }
122
123    fn method(&self) -> reqwest::Method {
124        reqwest::Method::GET
125    }
126}
127
128/// Lists invoices. To filter the invoices that appear in the response, you can specify one or more optional query parameters.
129/// Page size has the following limits: [1, 100].
130#[derive(Debug, Clone)]
131pub struct ListInvoices {
132    /// The endpoint query.
133    pub query: Query,
134}
135
136impl ListInvoices {
137    /// New constructor.
138    pub fn new(query: Query) -> Self {
139        Self { query }
140    }
141}
142
143impl Endpoint for ListInvoices {
144    type Query = Query;
145
146    type Body = ();
147
148    type Response = InvoiceList;
149
150    fn relative_path(&self) -> Cow<str> {
151        Cow::Borrowed("/v2/invoicing/invoices")
152    }
153
154    fn method(&self) -> reqwest::Method {
155        reqwest::Method::GET
156    }
157
158    fn query(&self) -> Option<Self::Query> {
159        Some(self.query.clone())
160    }
161}
162
163/// Deletes a draft or scheduled invoice, by ID. Deletes invoices in the draft or scheduled state only.
164///
165/// For invoices that have already been sent, you can cancel the invoice.
166///
167/// After you delete a draft or scheduled invoice, you can no longer use it or show its details. However, you can reuse its invoice number.
168#[derive(Debug, Clone)]
169pub struct DeleteInvoice {
170    /// The invocie id.
171    pub invoice_id: String,
172}
173
174impl DeleteInvoice {
175    /// New constructor.
176    pub fn new(invoice_id: impl ToString) -> Self {
177        Self {
178            invoice_id: invoice_id.to_string(),
179        }
180    }
181}
182
183impl Endpoint for DeleteInvoice {
184    type Query = Query;
185
186    type Body = ();
187
188    type Response = ();
189
190    fn relative_path(&self) -> Cow<str> {
191        Cow::Owned(format!("/v2/invoicing/invoices/{}", self.invoice_id))
192    }
193
194    fn method(&self) -> reqwest::Method {
195        reqwest::Method::DELETE
196    }
197}
198
199/// The update invoice query.
200#[derive(Debug, Clone, Serialize, Builder)]
201pub struct UpdateInvoiceQuery {
202    /// Indicates whether to send the invoice update notification to the recipient.
203    pub send_to_recipient: bool,
204    /// Indicates whether to send the invoice update notification to the merchant.
205    pub send_to_invoicer: bool,
206}
207
208/// Update an invoice.
209///
210/// Fully updates an invoice, by ID. In the JSON request body, include a complete invoice object. This call does not support partial updates.
211#[derive(Debug, Clone)]
212pub struct UpdateInvoice {
213    /// The updated invoice object.
214    pub invoice: Invoice,
215    /// The update invoice query.
216    pub query: UpdateInvoiceQuery,
217}
218
219impl UpdateInvoice {
220    /// New constructor.
221    pub fn new(invoice: Invoice, query: UpdateInvoiceQuery) -> Self {
222        Self { invoice, query }
223    }
224}
225
226impl Endpoint for UpdateInvoice {
227    type Query = UpdateInvoiceQuery;
228
229    type Body = Invoice;
230
231    type Response = Invoice;
232
233    fn relative_path(&self) -> Cow<str> {
234        Cow::Owned(format!("/v2/invoicing/invoices/{}", self.invoice.id))
235    }
236
237    fn method(&self) -> reqwest::Method {
238        reqwest::Method::PUT
239    }
240
241    fn body(&self) -> Option<Self::Body> {
242        Some(self.invoice.clone())
243    }
244
245    fn query(&self) -> Option<Self::Query> {
246        Some(self.query.clone())
247    }
248}
249
250/// Cancels a sent invoice, by ID, and, optionally, sends a notification about the cancellation to the payer, merchant, and CC: emails.
251#[derive(Debug, Clone)]
252pub struct CancelInvoice {
253    /// The invoice id.
254    pub invoice_id: String,
255    /// The reason of the cancelation.
256    pub reason: CancelReason,
257}
258
259impl CancelInvoice {
260    /// New constructor.
261    pub fn new(invoice_id: impl ToString, reason: CancelReason) -> Self {
262        Self {
263            invoice_id: invoice_id.to_string(),
264            reason,
265        }
266    }
267}
268
269impl Endpoint for CancelInvoice {
270    type Query = ();
271
272    type Body = CancelReason;
273
274    type Response = ();
275
276    fn relative_path(&self) -> Cow<str> {
277        Cow::Owned(format!("/v2/invoicing/invoices/{}/cancel", self.invoice_id))
278    }
279
280    fn method(&self) -> reqwest::Method {
281        reqwest::Method::POST
282    }
283
284    fn body(&self) -> Option<Self::Body> {
285        Some(self.reason.clone())
286    }
287}
288
289/// Sends or schedules an invoice, by ID, to be sent to a customer.
290#[derive(Debug, Clone)]
291pub struct SendInvoice {
292    /// The invoice id.
293    pub invoice_id: String,
294    /// The payload.
295    pub payload: SendInvoicePayload,
296}
297
298impl SendInvoice {
299    /// New constructor.
300    pub fn new(invoice_id: impl ToString, payload: SendInvoicePayload) -> Self {
301        Self {
302            invoice_id: invoice_id.to_string(),
303            payload,
304        }
305    }
306}
307
308impl Endpoint for SendInvoice {
309    type Query = ();
310
311    type Body = SendInvoicePayload;
312
313    type Response = ();
314
315    fn relative_path(&self) -> Cow<str> {
316        Cow::Owned(format!("/v2/invoicing/invoices/{}/send", self.invoice_id))
317    }
318
319    fn method(&self) -> reqwest::Method {
320        reqwest::Method::POST
321    }
322
323    fn body(&self) -> Option<Self::Body> {
324        Some(self.payload.clone())
325    }
326}
327
328/*
329
330impl super::Client {
331
332    /// Generate a QR code
333    pub async fn generate_qr_code(
334        &mut self,
335        invoice_id: &str,
336        params: QRCodeParams,
337        header_params: HeaderParams,
338    ) -> Result<Bytes, ResponseError> {
339        let build = self
340            .setup_headers(
341                self.client.post(
342                    format!(
343                        "{}/v2/invoicing/invoices/{}/generate-qr-code",
344                        self.endpoint(),
345                        invoice_id
346                    )
347                    .as_str(),
348                ),
349                header_params,
350            )
351            .await;
352
353        let res = build.json(&params).send().await?;
354
355        if res.status().is_success() {
356            let b = res.bytes().await?;
357            Ok(b)
358        } else {
359            Err(res.json::<PaypalError>().await?.into())
360        }
361    }
362
363    /// Records a payment for the invoice. If no payment is due, the invoice is marked as PAID. Otherwise, the invoice is marked as PARTIALLY PAID.
364    pub async fn record_invoice_payment(
365        &mut self,
366        invoice_id: &str,
367        payload: RecordPaymentPayload,
368        header_params: crate::HeaderParams,
369    ) -> Result<String, ResponseError> {
370        let build = self
371            .setup_headers(
372                self.client
373                    .post(format!("{}/v2/invoicing/invoices/{}/payments", self.endpoint(), invoice_id).as_str()),
374                header_params,
375            )
376            .await;
377
378        let res = build.json(&payload).send().await?;
379
380        if res.status().is_success() {
381            let x = res.json::<HashMap<String, String>>().await?;
382            Ok(x.get("payment_id").unwrap().to_owned())
383        } else {
384            Err(res.json::<PaypalError>().await?.into())
385        }
386    }
387
388    // TODO: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_payments-delete
389}
390*/
391
392/*
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::data::common::*;
397    use crate::data::invoice::*;
398    use crate::Client;
399
400    async fn create_client() -> Client {
401        dotenvy::dotenv().ok();
402        let clientid = std::env::var("PAYPAL_CLIENTID").unwrap();
403        let secret = std::env::var("PAYPAL_SECRET").unwrap();
404
405        let mut client = Client::new(clientid, secret, crate::PaypalEnv::Sandbox);
406        client.get_access_token().await.unwrap();
407        client
408    }
409
410    #[tokio::test]
411    async fn test_invoice_create_cancel() -> color_eyre::Result<()> {
412        let client = create_client().await;
413
414        let payload = InvoicePayloadBuilder::default()
415            .detail(InvoiceDetailBuilder::default().currency_code(Currency::EUR).build()?)
416            .invoicer(
417                InvoicerInfoBuilder::default()
418                    .name(NameBuilder::default().full_name("Test Person").build()?)
419                    .build()?,
420            )
421            .items(vec![ItemBuilder::default()
422                .name("Some name")
423                .unit_amount(Money {
424                    currency_code: Currency::EUR,
425                    value: "10.0".to_string(),
426                })
427                .quantity("1")
428                .build()?])
429            .build()?;
430
431        let invoice = CreateDraftInvoice::new(payload);
432
433        let _res = client.execute(&invoice).await?;
434        Ok(())
435    }
436}
437*/