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(¶ms).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*/