Skip to content

Commit

Permalink
Fixes #15 - Adds new Invoice APIs. Minor fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
bchavez committed May 16, 2021
1 parent 89d74d9 commit 8b83e3f
Show file tree
Hide file tree
Showing 20 changed files with 1,510 additions and 9 deletions.
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v3.0.1
* Update `Newtonsoft.Json` to 13.0.1
* Add new Invoice APIs.
* Add `CancellationToken` parameter to `CreateCheckoutAsync`.

## v2.0.1
* Update `Flurl.Http` dependency to 3.0.1.
* Minimum .NET Framework requirement changed from 4.5 to 4.6.1.
Expand Down
2 changes: 1 addition & 1 deletion Source/Coinbase.Commerce/Coinbase.Commerce.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="3.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Roslynator.Analyzers" Version="1.8.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
Expand Down
92 changes: 89 additions & 3 deletions Source/Coinbase.Commerce/CommerceApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ public class CommerceApi
/// <summary>
/// API Endpoint
/// </summary>
protected internal Url InvoicesEndpoint => Endpoint.AppendPathSegment("invoices");
/// <summary>
/// API Endpoint
/// </summary>
protected internal Url EventsEndpoint => Endpoint.AppendPathSegment("events");


/// <summary>
/// User's API Key
/// </summary>
Expand Down Expand Up @@ -240,10 +243,10 @@ public virtual Task<Response<Checkout>> GetCheckoutAsync(string checkoutId, Canc
/// Create a new checkout.
/// </summary>
/// <param name="checkout">The checkout to create</param>
public virtual Task<Response<Checkout>> CreateCheckoutAsync(CreateCheckout checkout)
public virtual Task<Response<Checkout>> CreateCheckoutAsync(CreateCheckout checkout, CancellationToken cancellationToken = default)
{
return CheckoutEndpoint
.PostJsonAsync(checkout)
.PostJsonAsync(checkout, cancellationToken)
.ReceiveJson<Response<Checkout>>();
}

Expand Down Expand Up @@ -272,6 +275,89 @@ public virtual Task DeleteCheckoutAsync(string checkoutId, CancellationToken can
}





/// <summary>
/// Lists all the invoices
/// </summary>
/// <param name="listOrder">Order of the resources in the response. desc (default), asc</param>
/// <param name="limit">umber of results per call. Accepted values: 0 - 100. Default 25</param>
/// <param name="startingAfter">A cursor for use in pagination. starting_after is a resource ID that defines your place in the list.</param>
/// <param name="endingBefore">A cursor for use in pagination. ending_before is a resource ID that defines your place in the list.</param>
public virtual Task<PagedResponse<Invoice>> ListInvoicesAsync(ListOrder? listOrder = null, int? limit = null, string startingAfter = null, string endingBefore = null, CancellationToken cancellationToken = default)
{
return InvoicesEndpoint
.SetQueryParam("order", listOrder?.ToString().ToLower())
.SetQueryParam("limit", limit)
.SetQueryParam("starting_after", startingAfter)
.SetQueryParam("ending_before", endingBefore)
.GetJsonAsync<PagedResponse<Invoice>>(cancellationToken);
}

/// <summary>
/// Retrieves the details of an invoice that has been previously created. Supply the unique
/// short code that was returned when the invoice was created. This information is
/// also returned when an invoice is first created.
/// </summary>
/// <param name="codeOrId">Invoice code or ID</param>
public virtual Task<Response<Invoice>> GetInvoiceAsync(string codeOrId, CancellationToken cancellationToken = default)
{
return InvoicesEndpoint
.AppendPathSegment(codeOrId)
.GetJsonAsync<Response<Invoice>>(cancellationToken);
}

/// <summary>
/// To send an invoice in cryptocurrency, you need to create an invoice object and provide
/// the user with the hosted url where they will be able to pay. Once an invoice is
/// viewed at the hosted url, a charge will be generated on the invoice.
/// </summary>
/// <param name="invoice">The invoice to create</param>
/// <returns></returns>
public virtual Task<Response<Invoice>> CreateInvoiceAsync(CreateInvoice invoice, CancellationToken cancellationToken = default)
{
return InvoicesEndpoint
.PostJsonAsync(invoice, cancellationToken)
.ReceiveJson<Response<Invoice>>();
}

/// <summary>
/// Voids an invoice that has been previously created. Supply the unique invoice code that
/// was returned when the invoice was created.
/// Note: Only invoices with OPEN or VIEWED status can be voided. Once a payment is detected,
/// the invoice can no longer be voided.
/// </summary>
/// <param name="codeOrId">Invoice code or id</param>
public virtual Task<Response<Invoice>> VoidInvoiceAsync(string codeOrId, CancellationToken cancellationToken = default)
{
return InvoicesEndpoint
.AppendPathSegments(codeOrId, "void")
.PostAsync(null, cancellationToken)
.ReceiveJson<Response<Invoice>>();
}


/// <summary>
/// Resolve an invoice that has been previously marked as unresolved. Supply the unique
/// invoice code that was returned when the invoice was created.
/// Note: Only invoices with an unresolved charge can be successfully resolved.For more
/// on unresolved charges, check out at Charge timeline: https://commerce.coinbase.com/docs/api/#charge-timeline
/// </summary>
/// <param name="codeOrId">Invoice code or id</param>
public virtual Task<Response<Invoice>> ResolveInvoiceAsync(string codeOrId, CancellationToken cancellationToken = default)
{
return InvoicesEndpoint
.AppendPathSegments(codeOrId, "resolve")
.PostAsync(null, cancellationToken)
.ReceiveJson<Response<Invoice>>();
}






/// <summary>
/// List all events. All GET endpoints which return an object list
/// support cursor based pagination with pagination information
Expand Down
37 changes: 37 additions & 0 deletions Source/Coinbase.Commerce/Models/CreateInvoice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Newtonsoft.Json;

namespace Coinbase.Commerce.Models
{
public partial class CreateInvoice : Json
{
/// <summary>
/// Your business name
/// </summary>
[JsonProperty("business_name")]
public string BusinessName { get; set; }

/// <summary>
/// The email address of the customer
/// </summary>
[JsonProperty("customer_email")]
public string CustomerEmail { get; set; }

/// <summary>
/// The name of the customer
/// </summary>
[JsonProperty("customer_name")]
public string CustomerName { get; set; }

/// <summary>
/// Price in local fiat currency
/// </summary>
[JsonProperty("local_price")]
public Money LocalPrice { get; set; }

/// <summary>
/// A memo for the invoice
/// </summary>
[JsonProperty("memo")]
public string Memo { get; set; }
}
}
94 changes: 94 additions & 0 deletions Source/Coinbase.Commerce/Models/Invoice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Coinbase.Commerce.Models
{
/// <summary>
/// An invoice can be created and sent to customers for payment by constructing a url with the
/// generated 8 character short-code. Invoice url's have the format https://commerce.coinbase.com/invoices/:code.
/// Invoice urls can be sent to the payee to collect payment, and will associate a new charge on the invoice
/// object once it has been viewed. Charges associated with invoices will automatically refresh their 1-hour
/// payment window if they expire.
/// </summary>
public partial class Invoice : Json
{
/// <summary>
/// Charge UUID
/// </summary>
[JsonProperty("id")]
public Guid Id { get; set; }

/// <summary>
/// Resource name: "invoice"
/// </summary>
[JsonProperty("resource")]
public string Resource { get; set; }

/// <summary>
/// Charge user-friendly primary key
/// </summary>
[JsonProperty("code")]
public string Code { get; set; }

/// <summary>
/// Status of the invoice
/// </summary>
[JsonProperty("status")]
public string Status { get; set; }

/// <summary>
/// Your business name
/// </summary>
[JsonProperty("business_name")]
public string BusinessName { get; set; }

/// <summary>
/// Customer's name (optional)
/// </summary>
[JsonProperty("customer_name")]
public string CustomerName { get; set; }

/// <summary>
/// Customer's email
/// </summary>
[JsonProperty("customer_email")]
public string CustomerEmail { get; set; }

/// <summary>
/// Invoice memo
/// </summary>
[JsonProperty("memo")]
public string Memo { get; set; }

/// <summary>
/// Invoice price information object
/// </summary>
[JsonProperty("local_price")]
public Money LocalPrice { get; set; }

/// <summary>
/// Hosted invoice URL
/// </summary>
[JsonProperty("hosted_url")]
public Uri HostedUrl { get; set; }

/// <summary>
/// Invoice creation time
/// </summary>
[JsonProperty("created_at")]
public DateTimeOffset CreatedAt { get; set; }

/// <summary>
/// Invoice updated time
/// </summary>
[JsonProperty("updated_at")]
public DateTimeOffset UpdatedAt { get; set; }

/// <summary>
/// Associated charge resource (only exists if viewed)
/// </summary>
[JsonProperty("charge")]
public JObject Charge { get; set; }
}
}
2 changes: 2 additions & 0 deletions Source/Coinbase.Tests/ApiTests/CommerceApiTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Coinbase.Commerce;
using Flurl.Http.Testing;
using NUnit.Framework;
using VerifyTests;

namespace Coinbase.Tests.ApiTests
{
Expand All @@ -13,6 +14,7 @@ public class CommerceApiTests
[SetUp]
public void BeforeEachTest()
{
VerifierSettings.UseStrictJson();
server = new HttpTest();

api = new CommerceApi(apiKey);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"data": {
"id": "D50D53D3-03EF-4465-B4E1-E0400907BC98",
"resource": "invoice",
"code": "fff",
"status": "VIEWED",
"business_name": "Some Company, LLC",
"customer_name": "Sam User",
"customer_email": "user@email.com",
"memo": "Only a test memo",
"local_price": {
"amount": 10.00,
"currency": "USD"
},
"hosted_url": "https://commerce.coinbase.com/invoices/fff",
"created_at": "2021-05-15T16:56:26+00:00",
"updated_at": "2021-05-15T16:56:42+00:00",
"charge": {
"addresses": {
"ethereum": "fff",
"usdc": "fff",
"dai": "fff",
"bitcoincash": "fff",
"litecoin": "fff",
"bitcoin": "fff"
},
"code": "fff",
"created_at": "2021-05-15T16:56:42Z",
"exchange_rates": {
"BCH-USD": "1204.91",
"BTC-USD": "48026.44",
"DAI-USD": "1.0006375",
"ETH-USD": "3785.475",
"LTC-USD": "305.415",
"USDC-USD": "1.0"
},
"expires_at": "2021-05-15T17:56:42Z",
"hosted_url": "https://commerce.coinbase.com/charges/fff",
"id": "59E3EED7-6F3E-4F36-B821-EDBC1D8D1734",
"invoice": {
"id": "CD67E00B-1DE0-4F34-BE0F-747CBBB9ABD7",
"code": "fff"
},
"metadata": {
"name": "Some Customer",
"email": "user@email.com"
},
"organization_name": "Some Company Inc",
"payment_threshold": {
"overpayment_absolute_threshold": {
"amount": "5.00",
"currency": "USD"
},
"overpayment_relative_threshold": "0.005",
"underpayment_absolute_threshold": {
"amount": "5.00",
"currency": "USD"
},
"underpayment_relative_threshold": "0.005"
},
"payments": [],
"pricing": {
"local": {
"amount": "10.00",
"currency": "USD"
},
"ethereum": {
"amount": "0.002642000",
"currency": "ETH"
},
"usdc": {
"amount": "10.000000",
"currency": "USDC"
},
"dai": {
"amount": "9.993629061473310764",
"currency": "DAI"
},
"bitcoincash": {
"amount": "0.00829938",
"currency": "BCH"
},
"litecoin": {
"amount": "0.03274233",
"currency": "LTC"
},
"bitcoin": {
"amount": "0.00020822",
"currency": "BTC"
}
},
"pricing_type": "fixed_price",
"resource": "charge",
"support_email": "some@user.com",
"timeline": [
{
"status": "NEW",
"time": "2021-05-15T16:56:42Z"
},
{
"status": "EXPIRED",
"time": "2021-05-15T17:56:49Z"
}
]
}
},
"error": null,
"warnings": null
}
Loading

0 comments on commit 8b83e3f

Please sign in to comment.