diff --git a/src/Stripe.net/Constants/Events.cs b/src/Stripe.net/Constants/Events.cs
index de90ce37f3..cf910c8fa6 100644
--- a/src/Stripe.net/Constants/Events.cs
+++ b/src/Stripe.net/Constants/Events.cs
@@ -149,6 +149,21 @@ public static class Events
///
public static string CouponUpdated => "coupon.updated";
+ ///
+ /// Occurs whenever a credit note is created.
+ ///
+ public static string CreditNoteCreated => "credit_note.created";
+
+ ///
+ /// Occurs whenever a credit note is updated.
+ ///
+ public static string CreditNoteUpdated => "credit_note.updated";
+
+ ///
+ /// Occurs whenever a credit note is voided.
+ ///
+ public static string CreditNoteVoided => "credit_note.voided";
+
///
/// Occurs whenever a new customer is created.
///
diff --git a/src/Stripe.net/Entities/CreditNote.cs b/src/Stripe.net/Entities/CreditNote.cs
new file mode 100644
index 0000000000..51bb157122
--- /dev/null
+++ b/src/Stripe.net/Entities/CreditNote.cs
@@ -0,0 +1,170 @@
+namespace Stripe
+{
+ using System;
+ using System.Collections.Generic;
+ using Newtonsoft.Json;
+ using Stripe.Infrastructure;
+
+ public class CreditNote : StripeEntity, IHasId, IHasMetadata, IHasObject
+ {
+ ///
+ /// Unique identifier for the object.
+ ///
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ ///
+ /// String representing the object’s type. Objects of the same type share the same value.
+ ///
+ [JsonProperty("object")]
+ public string Object { get; set; }
+
+ ///
+ /// Credit note amount.
+ ///
+ [JsonProperty("amount")]
+ public long Amount { get; set; }
+
+ ///
+ /// Time at which the object was created. Measured in seconds since the Unix epoch.
+ ///
+ [JsonProperty("created")]
+ [JsonConverter(typeof(DateTimeConverter))]
+ public DateTime Created { get; set; }
+
+ ///
+ /// Three-letter ISO currency code, in lowercase. Must be a supported currency.
+ ///
+ [JsonProperty("currency")]
+ public string Currency { get; set; }
+
+ #region Expandable Customer
+
+ ///
+ /// ID of the customer associated with that credit note.
+ ///
+ [JsonIgnore]
+ public string CustomerId { get; set; }
+
+ [JsonIgnore]
+ public Customer Customer { get; set; }
+
+ [JsonProperty("customer")]
+ internal object InternalCustomer
+ {
+ get
+ {
+ return this.Customer ?? (object)this.CustomerId;
+ }
+
+ set
+ {
+ StringOrObject.Map(value, s => this.CustomerId = s, o => this.Customer = o);
+ }
+ }
+ #endregion
+
+ #region Expandable Invoice
+
+ ///
+ /// ID of the invoice associated with that credit note.
+ ///
+ [JsonIgnore]
+ public string InvoiceId { get; set; }
+
+ [JsonIgnore]
+ public Invoice Invoice { get; set; }
+
+ [JsonProperty("invoice")]
+ internal object InternalInvoice
+ {
+ get
+ {
+ return this.Invoice ?? (object)this.InvoiceId;
+ }
+
+ set
+ {
+ StringOrObject.Map(value, s => this.InvoiceId = s, o => this.Invoice = o);
+ }
+ }
+ #endregion
+
+ ///
+ /// Has the value true if the object exists in live mode or the value false
+ /// if the object exists in test mode.
+ ///
+ [JsonProperty("livemode")]
+ public bool Livemode { get; set; }
+
+ ///
+ /// Credit note memo.
+ ///
+ [JsonProperty("memo")]
+ public string Memo { get; set; }
+
+ ///
+ /// A set of key/value pairs that you can attach to an order object. It can be useful for
+ /// storing additional information about the order in a structured format.
+ ///
+ [JsonProperty("metadata")]
+ public Dictionary Metadata { get; set; }
+
+ ///
+ /// Credit note number.
+ ///
+ [JsonProperty("number")]
+ public string Number { get; set; }
+
+ ///
+ /// The link to download the PDF of the credit note.
+ ///
+ [JsonProperty("pdf")]
+ public string Pdf { get; set; }
+
+ ///
+ /// Reason for issuing this credit note, one of duplicate, fraudulent,
+ /// order_change, or product_unsatisfactory.
+ ///
+ [JsonProperty("reason")]
+ public string Reason { get; set; }
+
+ #region Expandable Refund
+
+ ///
+ /// ID of the refund associated with that credit note.
+ ///
+ [JsonIgnore]
+ public string RefundId { get; set; }
+
+ [JsonIgnore]
+ public Refund Refund { get; set; }
+
+ [JsonProperty("refund")]
+ internal object InternalRefund
+ {
+ get
+ {
+ return this.Refund ?? (object)this.RefundId;
+ }
+
+ set
+ {
+ StringOrObject.Map(value, s => this.RefundId = s, o => this.Refund = o);
+ }
+ }
+ #endregion
+
+ ///
+ /// Status of this credit note, one of issued or void.
+ ///
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ ///
+ /// Type of this credit note, one of post_payment or pre_payment.
+ ///
+ [JsonProperty("type")]
+ public string Type { get; set; }
+ }
+}
diff --git a/src/Stripe.net/Entities/Invoices/Invoice.cs b/src/Stripe.net/Entities/Invoices/Invoice.cs
index b370edc7e1..039016b68f 100644
--- a/src/Stripe.net/Entities/Invoices/Invoice.cs
+++ b/src/Stripe.net/Entities/Invoices/Invoice.cs
@@ -243,6 +243,18 @@ internal object InternalPaymentIntent
[JsonConverter(typeof(DateTimeConverter))]
public DateTime PeriodStart { get; set; }
+ ///
+ /// Total amount of all post-payment credit notes issued for this invoice.
+ ///
+ [JsonProperty("post_payment_credit_notes_amount")]
+ public long? PostPaymentCreditNotesAmount { get; set; }
+
+ ///
+ /// Total amount of all pre-payment credit notes issued for this invoice.
+ ///
+ [JsonProperty("pre_payment_credit_notes_amount")]
+ public long? PrePaymentCreditNotesAmount { get; set; }
+
[JsonProperty("receipt_number")]
public string ReceiptNumber { get; set; }
diff --git a/src/Stripe.net/Infrastructure/StripeTypeRegistry.cs b/src/Stripe.net/Infrastructure/StripeTypeRegistry.cs
index 946197ef19..c4913802b4 100644
--- a/src/Stripe.net/Infrastructure/StripeTypeRegistry.cs
+++ b/src/Stripe.net/Infrastructure/StripeTypeRegistry.cs
@@ -26,6 +26,7 @@ internal static class StripeTypeRegistry
{ "checkout.session", typeof(Checkout.Session) },
{ "country_spec", typeof(CountrySpec) },
{ "coupon", typeof(Coupon) },
+ { "credit_note", typeof(CreditNote) },
{ "customer", typeof(Customer) },
{ "discount", typeof(Discount) },
{ "dispute", typeof(Dispute) },
diff --git a/src/Stripe.net/Services/CreditNotes/CreditNoteCreateOptions.cs b/src/Stripe.net/Services/CreditNotes/CreditNoteCreateOptions.cs
new file mode 100644
index 0000000000..5c30fe9609
--- /dev/null
+++ b/src/Stripe.net/Services/CreditNotes/CreditNoteCreateOptions.cs
@@ -0,0 +1,61 @@
+namespace Stripe
+{
+ using System;
+ using System.Collections.Generic;
+ using Newtonsoft.Json;
+ using Stripe.Infrastructure;
+
+ public class CreditNoteCreateOptions : BaseOptions
+ {
+ ///
+ /// Credit note amount.
+ ///
+ [JsonProperty("amount")]
+ public long? Amount { get; set; }
+
+ ///
+ /// Amount to credit the customer balance.
+ ///
+ [JsonProperty("credit_amount")]
+ public long? CreditAmount { get; set; }
+
+ ///
+ /// ID of the invoice.
+ ///
+ [JsonProperty("invoice")]
+ public string InvoiceId { get; set; }
+
+ ///
+ /// Credit note memo.
+ ///
+ [JsonProperty("memo")]
+ public string Memo { get; set; }
+
+ ///
+ /// Set of key-value pairs that you can attach to an object. This can be useful for storing
+ /// additional information about the object in a structured format.
+ ///
+ [JsonProperty("metadata")]
+ public Dictionary Metadata { get; set; }
+
+ ///
+ /// Reason for issuing this credit note, one of duplicate, fraudulent,
+ /// order_change, or product_unsatisfactory.
+ ///
+ [JsonProperty("reason")]
+ public string Reason { get; set; }
+
+ ///
+ /// ID of an existing refund to link this credit note to.
+ ///
+ [JsonProperty("refund")]
+ public string RefundId { get; set; }
+
+ ///
+ /// Amount to refund. If set, a refund will be created for the charge associated with the
+ /// invoice.
+ ///
+ [JsonProperty("refund_amount")]
+ public long? RefundAmount { get; set; }
+ }
+}
diff --git a/src/Stripe.net/Services/CreditNotes/CreditNoteListOptions.cs b/src/Stripe.net/Services/CreditNotes/CreditNoteListOptions.cs
new file mode 100644
index 0000000000..dede1506fa
--- /dev/null
+++ b/src/Stripe.net/Services/CreditNotes/CreditNoteListOptions.cs
@@ -0,0 +1,14 @@
+namespace Stripe
+{
+ using System;
+ using Newtonsoft.Json;
+
+ public class CreditNoteListOptions : ListOptions
+ {
+ ///
+ /// ID of the invoice.
+ ///
+ [JsonProperty("invoice")]
+ public string InvoiceId { get; set; }
+ }
+}
diff --git a/src/Stripe.net/Services/CreditNotes/CreditNoteService.cs b/src/Stripe.net/Services/CreditNotes/CreditNoteService.cs
new file mode 100644
index 0000000000..e7424e8fad
--- /dev/null
+++ b/src/Stripe.net/Services/CreditNotes/CreditNoteService.cs
@@ -0,0 +1,80 @@
+namespace Stripe
+{
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ public class CreditNoteService : Service,
+ ICreatable,
+ IListable,
+ IRetrievable,
+ IUpdatable
+ {
+ public CreditNoteService()
+ : base(null)
+ {
+ }
+
+ public CreditNoteService(string apiKey)
+ : base(apiKey)
+ {
+ }
+
+ public override string BasePath => "/credit_notes";
+
+ public virtual CreditNote Create(CreditNoteCreateOptions options, RequestOptions requestOptions = null)
+ {
+ return this.CreateEntity(options, requestOptions);
+ }
+
+ public virtual Task CreateAsync(CreditNoteCreateOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return this.CreateEntityAsync(options, requestOptions, cancellationToken);
+ }
+
+ public virtual CreditNote Get(string creditNoteId, RequestOptions requestOptions = null)
+ {
+ return this.GetEntity(creditNoteId, null, requestOptions);
+ }
+
+ public virtual Task GetAsync(string creditNoteId, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return this.GetEntityAsync(creditNoteId, null, requestOptions, cancellationToken);
+ }
+
+ public virtual StripeList List(CreditNoteListOptions options = null, RequestOptions requestOptions = null)
+ {
+ return this.ListEntities(options, requestOptions);
+ }
+
+ public virtual Task> ListAsync(CreditNoteListOptions options = null, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return this.ListEntitiesAsync(options, requestOptions, cancellationToken);
+ }
+
+ public virtual IEnumerable ListAutoPaging(CreditNoteListOptions options = null, RequestOptions requestOptions = null)
+ {
+ return this.ListEntitiesAutoPaging(options, requestOptions);
+ }
+
+ public virtual CreditNote Update(string creditNoteId, CreditNoteUpdateOptions options, RequestOptions requestOptions = null)
+ {
+ return this.UpdateEntity(creditNoteId, options, requestOptions);
+ }
+
+ public virtual Task UpdateAsync(string creditNoteId, CreditNoteUpdateOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return this.UpdateEntityAsync(creditNoteId, options, requestOptions, cancellationToken);
+ }
+
+ public virtual CreditNote VoidCreditNote(string creditNoteId, CreditNoteVoidOptions options, RequestOptions requestOptions = null)
+ {
+ return this.PostRequest($"{this.InstanceUrl(creditNoteId)}/void", options, requestOptions);
+ }
+
+ public virtual Task VoidCreditNoteAsync(string creditNoteId, CreditNoteVoidOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return this.PostRequestAsync($"{this.InstanceUrl(creditNoteId)}/void", options, requestOptions, cancellationToken);
+ }
+ }
+}
diff --git a/src/Stripe.net/Services/CreditNotes/CreditNoteUpdateOptions.cs b/src/Stripe.net/Services/CreditNotes/CreditNoteUpdateOptions.cs
new file mode 100644
index 0000000000..64357d1a85
--- /dev/null
+++ b/src/Stripe.net/Services/CreditNotes/CreditNoteUpdateOptions.cs
@@ -0,0 +1,23 @@
+namespace Stripe
+{
+ using System;
+ using System.Collections.Generic;
+ using Newtonsoft.Json;
+ using Stripe.Infrastructure;
+
+ public class CreditNoteUpdateOptions : BaseOptions
+ {
+ ///
+ /// Credit note memo.
+ ///
+ [JsonProperty("memo")]
+ public string Memo { get; set; }
+
+ ///
+ /// Set of key-value pairs that you can attach to an object. This can be useful for storing
+ /// additional information about the object in a structured format.
+ ///
+ [JsonProperty("metadata")]
+ public Dictionary Metadata { get; set; }
+ }
+}
diff --git a/src/Stripe.net/Services/CreditNotes/CreditNoteVoidOptions.cs b/src/Stripe.net/Services/CreditNotes/CreditNoteVoidOptions.cs
new file mode 100644
index 0000000000..f668c88fea
--- /dev/null
+++ b/src/Stripe.net/Services/CreditNotes/CreditNoteVoidOptions.cs
@@ -0,0 +1,11 @@
+namespace Stripe
+{
+ using System;
+ using System.Collections.Generic;
+ using Newtonsoft.Json;
+ using Stripe.Infrastructure;
+
+ public class CreditNoteVoidOptions : BaseOptions
+ {
+ }
+}
diff --git a/src/StripeTests/Entities/CreditNotes/CreditNoteTest.cs b/src/StripeTests/Entities/CreditNotes/CreditNoteTest.cs
new file mode 100644
index 0000000000..4b8b969605
--- /dev/null
+++ b/src/StripeTests/Entities/CreditNotes/CreditNoteTest.cs
@@ -0,0 +1,52 @@
+namespace StripeTests
+{
+ using Newtonsoft.Json;
+ using Stripe;
+ using Xunit;
+
+ public class CreditNoteTest : BaseStripeTest
+ {
+ public CreditNoteTest(StripeMockFixture stripeMockFixture)
+ : base(stripeMockFixture)
+ {
+ }
+
+ [Fact]
+ public void Deserialize()
+ {
+ string json = this.GetFixture("/v1/credit_notes/cn_123");
+ var creditNote = JsonConvert.DeserializeObject(json);
+ Assert.NotNull(creditNote);
+ Assert.IsType(creditNote);
+ Assert.NotNull(creditNote.Id);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public void DeserializeWithExpansions()
+ {
+ string[] expansions =
+ {
+ "customer",
+ "invoice",
+ "refund",
+ };
+
+ string json = this.GetFixture("/v1/credit_notes/cn_123", expansions);
+ var creditNote = JsonConvert.DeserializeObject(json);
+ Assert.NotNull(creditNote);
+ Assert.IsType(creditNote);
+ Assert.NotNull(creditNote.Id);
+ Assert.Equal("credit_note", creditNote.Object);
+
+ Assert.NotNull(creditNote.Customer);
+ Assert.Equal("customer", creditNote.Customer.Object);
+
+ Assert.NotNull(creditNote.Invoice);
+ Assert.Equal("invoice", creditNote.Invoice.Object);
+
+ Assert.NotNull(creditNote.Refund);
+ Assert.Equal("refund", creditNote.Refund.Object);
+ }
+ }
+}
diff --git a/src/StripeTests/Services/CreditNotes/CreditNoteServiceTest.cs b/src/StripeTests/Services/CreditNotes/CreditNoteServiceTest.cs
new file mode 100644
index 0000000000..66de8d585f
--- /dev/null
+++ b/src/StripeTests/Services/CreditNotes/CreditNoteServiceTest.cs
@@ -0,0 +1,154 @@
+namespace StripeTests
+{
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net.Http;
+ using System.Threading.Tasks;
+
+ using Stripe;
+ using Xunit;
+
+ public class CreditNoteServiceTest : BaseStripeTest
+ {
+ private const string CreditNoteId = "cn_123";
+
+ private readonly CreditNoteService service;
+ private readonly CreditNoteCreateOptions createOptions;
+ private readonly CreditNoteUpdateOptions updateOptions;
+ private readonly CreditNoteListOptions listOptions;
+ private readonly CreditNoteVoidOptions voidOptions;
+
+ public CreditNoteServiceTest(MockHttpClientFixture mockHttpClientFixture)
+ : base(mockHttpClientFixture)
+ {
+ this.service = new CreditNoteService();
+
+ this.createOptions = new CreditNoteCreateOptions
+ {
+ Amount = 100,
+ InvoiceId = "in_123",
+ Reason = "duplicate",
+ };
+
+ this.updateOptions = new CreditNoteUpdateOptions
+ {
+ Metadata = new Dictionary
+ {
+ { "key", "value" },
+ },
+ };
+
+ this.listOptions = new CreditNoteListOptions
+ {
+ Limit = 1,
+ InvoiceId = "in_123",
+ };
+
+ this.voidOptions = new CreditNoteVoidOptions
+ {
+ };
+ }
+
+ [Fact]
+ public void Create()
+ {
+ var creditNote = this.service.Create(this.createOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public async Task CreateAsync()
+ {
+ var creditNote = await this.service.CreateAsync(this.createOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public void Get()
+ {
+ var creditNote = this.service.Get(CreditNoteId);
+ this.AssertRequest(HttpMethod.Get, "/v1/credit_notes/cn_123");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public async Task GetAsync()
+ {
+ var creditNote = await this.service.GetAsync(CreditNoteId);
+ this.AssertRequest(HttpMethod.Get, "/v1/credit_notes/cn_123");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public void List()
+ {
+ var creditNotes = this.service.List(this.listOptions);
+ this.AssertRequest(HttpMethod.Get, "/v1/credit_notes");
+ Assert.NotNull(creditNotes);
+ Assert.Equal("list", creditNotes.Object);
+ Assert.Single(creditNotes.Data);
+ Assert.Equal("credit_note", creditNotes.Data[0].Object);
+ }
+
+ [Fact]
+ public async Task ListAsync()
+ {
+ var creditNotes = await this.service.ListAsync(this.listOptions);
+ this.AssertRequest(HttpMethod.Get, "/v1/credit_notes");
+ Assert.NotNull(creditNotes);
+ Assert.Equal("list", creditNotes.Object);
+ Assert.Single(creditNotes.Data);
+ Assert.Equal("credit_note", creditNotes.Data[0].Object);
+ }
+
+ [Fact]
+ public void ListAutoPaging()
+ {
+ var creditNotes = this.service.ListAutoPaging(this.listOptions).ToList();
+ Assert.NotNull(creditNotes);
+ Assert.Equal("credit_note", creditNotes[0].Object);
+ }
+
+ [Fact]
+ public void Update()
+ {
+ var creditNote = this.service.Update(CreditNoteId, this.updateOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes/cn_123");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public async Task UpdateAsync()
+ {
+ var creditNote = await this.service.UpdateAsync(CreditNoteId, this.updateOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes/cn_123");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public void VoidCreditNote()
+ {
+ var creditNote = this.service.VoidCreditNote(CreditNoteId, this.voidOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes/cn_123/void");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+
+ [Fact]
+ public async Task VoidCreditNoteAsync()
+ {
+ var creditNote = await this.service.VoidCreditNoteAsync(CreditNoteId, this.voidOptions);
+ this.AssertRequest(HttpMethod.Post, "/v1/credit_notes/cn_123/void");
+ Assert.NotNull(creditNote);
+ Assert.Equal("credit_note", creditNote.Object);
+ }
+ }
+}