Skip to content

Commit

Permalink
Implement billing charges API
Browse files Browse the repository at this point in the history
* adds billing service with list charges endpoint
  • Loading branch information
AGS4NO authored Dec 7, 2023
1 parent 62a14bc commit 8a237fa
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This project uses [Semantic Versioning 2.0.0](http://semver.org/).

## main

FEATURES:

- NEW: Added `Dnsimple.Billing.ListCharges` to list billing charges for an account. (dnsimple/dnsimple-csharp#133)

## 0.15.0

FEATURES:
Expand Down
15 changes: 6 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,7 @@ cd dnsimple-csharp

### 3. Build and test

[Run the test suite](#testing) to check everything is working as expected and to install the project specific
dependencies (the first time you'll run the script it will install all the dependencies for you).

To run the test suite:

```shell
dotnet test
```
[Run the test suite](#testing) to check everything is working as expected and to install the project specific dependencies (the first time you'll run the script it will install all the dependencies for you).

## Releasing

Expand All @@ -59,6 +52,10 @@ The following instructions uses $VERSION as a placeholder, where $VERSION is a M

## Testing

Submit unit tests for your changes. You can test your changes on your machine by [running the test suite](#testing).
Submit unit tests for your changes. You can test your changes on your machine by running:

```shell
dotnet test
```

When you submit a PR, tests will also be run on the [continuous integration environment via GitHub Actions](https://github.com/dnsimple/dnsimple-csharp/actions).
4 changes: 3 additions & 1 deletion src/dnsimple-test/MockDnsimpleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class MockDnsimpleClient : IClient
private string Fixture { get; }

public AccountsService Accounts { get; }
public BillingService Billing { get; }
public CertificatesService Certificates { get; }
public ContactsService Contacts { get; }
public DomainsService Domains { get; }
Expand All @@ -38,6 +39,7 @@ public MockDnsimpleClient(string fixture)
UserAgent = "Testing user agent";

Accounts = new AccountsService(this);
Billing = new BillingService(this);
Certificates = new CertificatesService(this);
Contacts = new ContactsService(this);
Domains = new DomainsService(this);
Expand Down Expand Up @@ -165,4 +167,4 @@ public void SetHeaders(List<Parameter> headers)
Headers = headers;
}
}
}
}
134 changes: 134 additions & 0 deletions src/dnsimple-test/Services/BillingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using dnsimple;
using dnsimple.Services;
using dnsimple.Services.ListOptions;
using NUnit.Framework;
using RestSharp;
using Pagination = dnsimple.Services.ListOptions.Pagination;

namespace dnsimple_test.Services
{
[TestFixture]
public class BillingTest
{

private MockResponse _response;

private const string ListChargesFixture =
"listCharges/success.http";

private const string ListChargesBadFilterFixture =
"listCharges/fail-400-bad-filter.http";

private const string ListChargesUnauthorizedFixture =
"listCharges/fail-403.http";

[SetUp]
public void Initialize()
{
var loader = new FixtureLoader("v2", ListChargesFixture);
_response = new MockResponse(loader);
}

[Test]
public void ListCharges()
{
var charges =
new PaginatedResponse<Charge>(_response).Data;

Assert.Multiple(() =>
{
Assert.AreEqual(3, charges.Count);
Assert.AreEqual(14.50m, charges.First().TotalAmount);
Assert.AreEqual(0.00m, charges.First().BalanceAmount);
Assert.AreEqual("1-2", charges.First().Reference);
Assert.AreEqual("collected", charges.First().State);
Assert.AreEqual("Register bubble-registered.com", charges.First().Items.First().Description);
Assert.AreEqual(14.50m, charges.First().Items.First().Amount);
Assert.AreEqual(1, charges.First().Items.First().ProductId);
Assert.AreEqual("domain-registration", charges.First().Items.First().ProductType);
});
}

[Test]
[TestCase(
"https://api.sandbox.dnsimple.com/v2/1010/domains?sort=invoiced:asc&start_date=2023-01-01&end_date=2023-08-31")]
public void ListChargesWithOptions(string expectedUrl)
{
var client = new MockDnsimpleClient(ListChargesFixture);
var listOptions = new ListChargesOptions();
listOptions.FilterByStartDate("2023-01-01");
listOptions.FilterByEndDate("2023-08-31");
listOptions.SortByInvoiceDate(Order.asc);

client.Domains.ListDomains(1010, listOptions);

Assert.AreEqual(expectedUrl, client.RequestSentTo());
}

[Test]
public void ListChargesOptions()
{
var filters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("start_date", "2023-01-01"),
new KeyValuePair<string, string>("end_date", "2023-08-31")
};
var sorting = new KeyValuePair<string, string>("sort",
"invoiced:asc");
var pagination = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("per_page", "30"),
new KeyValuePair<string, string>("page", "1")
};

var options = new ListChargesOptions
{
Pagination = new Pagination
{
PerPage = 30,
Page = 1
}
}.FilterByStartDate("2023-01-01")
.FilterByEndDate("2023-08-31")
.SortByInvoiceDate(Order.asc);


Assert.Multiple(() =>
{
Assert.AreEqual(filters, options.UnpackFilters());
Assert.AreEqual(pagination, options.UnpackPagination());
Assert.AreEqual(sorting, options.UnpackSorting());
});
}

[Test]
[TestCase(1010)]
public void ListChargesBadFilter(long accountId)
{
var client = new MockDnsimpleClient(ListChargesBadFilterFixture);
client.StatusCode(HttpStatusCode.BadRequest);

Assert.Throws(
Is.TypeOf<DnsimpleValidationException>().And.Message
.EqualTo("Invalid date format must be ISO8601 (YYYY-MM-DD)"),
delegate { client.Billing.ListCharges(accountId); });
}

[Test]
[TestCase(1010)]
public void ListChargesUnauthorized(long accountId)
{
var client = new MockDnsimpleClient(ListChargesUnauthorizedFixture);
client.StatusCode(HttpStatusCode.BadRequest);

Assert.Throws(
Is.TypeOf<DnsimpleValidationException>().And.Message
.EqualTo("Permission Denied. Required Scope: billing:*:read"),
delegate { client.Billing.ListCharges(accountId); });
}
}
}
9 changes: 9 additions & 0 deletions src/dnsimple-test/dnsimple-test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,15 @@
<Content Include="fixtures\v2\api\listCertificates\success.http">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="fixtures\v2\api\listCharges\success.http">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="fixtures\v2\api\listCharges\fail-400-bad-filter.http">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="fixtures\v2\api\listCharges\fail-403.http">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="fixtures\v2\api\listCollaborators\success.http">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
HTTP/1.1 400 Bad Request
Date: Tue, 24 Oct 2023 08:13:01 GMT
Connection: close
X-RateLimit-Limit: 2400
X-RateLimit-Remaining: 2392
X-RateLimit-Reset: 1698136677
Content-Type: application/json; charset=utf-8
X-WORK-WITH-US: Love automation? So do we! https://dnsimple.com/jobs
Cache-Control: no-cache
X-Request-Id: bdfbf3a7-d9dc-4018-9732-61502be989a3
X-Runtime: 0.455303
Transfer-Encoding: chunked

{"message":"Invalid date format must be ISO8601 (YYYY-MM-DD)"}
14 changes: 14 additions & 0 deletions src/dnsimple-test/fixtures/v2/api/listCharges/fail-403.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
HTTP/1.1 403 Forbidden
Date: Tue, 24 Oct 2023 09:49:29 GMT
Connection: close
X-RateLimit-Limit: 2400
X-RateLimit-Remaining: 2398
X-RateLimit-Reset: 1698143967
Content-Type: application/json; charset=utf-8
X-WORK-WITH-US: Love automation? So do we! https://dnsimple.com/jobs
Cache-Control: no-cache
X-Request-Id: 5554e2d3-2652-4ca7-8c5e-92b4c35f28d6
X-Runtime: 0.035309
Transfer-Encoding: chunked

{"message":"Permission Denied. Required Scope: billing:*:read"}
14 changes: 14 additions & 0 deletions src/dnsimple-test/fixtures/v2/api/listCharges/success.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
HTTP/1.1 200 OK
Date: Tue, 24 Oct 2023 09:52:55 GMT
Connection: close
X-RateLimit-Limit: 2400
X-RateLimit-Remaining: 2397
X-RateLimit-Reset: 1698143967
Content-Type: application/json; charset=utf-8
X-WORK-WITH-US: Love automation? So do we! https://dnsimple.com/jobs
Cache-Control: no-store, must-revalidate, private, max-age=0
X-Request-Id: a57a87c8-626a-4361-9fb8-b55ca9be8e5d
X-Runtime: 0.060526
Transfer-Encoding: chunked

{"data":[{"invoiced_at":"2023-08-17T05:53:36Z","total_amount":"14.50","balance_amount":"0.00","reference":"1-2","state":"collected","items":[{"description":"Register bubble-registered.com","amount":"14.50","product_id":1,"product_type":"domain-registration","product_reference":"bubble-registered.com"}]},{"invoiced_at":"2023-08-17T05:57:53Z","total_amount":"14.50","balance_amount":"0.00","reference":"2-2","state":"refunded","items":[{"description":"Register example.com","amount":"14.50","product_id":2,"product_type":"domain-registration","product_reference":"example.com"}]},{"invoiced_at":"2023-10-24T07:49:05Z","total_amount":"1099999.99","balance_amount":"0.00","reference":"4-2","state":"collected","items":[{"description":"Test Line Item 1","amount":"99999.99","product_id":null,"product_type":"manual","product_reference":null},{"description":"Test Line Item 2","amount":"1000000.00","product_id":null,"product_type":"manual","product_reference":null}]}],"pagination":{"current_page":1,"per_page":30,"total_entries":3,"total_pages":1}}
12 changes: 11 additions & 1 deletion src/dnsimple/DNSimple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public interface IClient
/// <see>https://developer.dnsimple.com/v2/accounts/</see>
AccountsService Accounts { get; }

/// <summary>
/// Instance of the <c>BillingService</c>
/// </summary>
/// <see cref="BillingService"/>
/// <see>https://developer.dnsimple.com/v2/billing/</see>
BillingService Billing { get; }

/// <summary>
/// Instance of the <c>ContactsService</c>
/// </summary>
Expand Down Expand Up @@ -260,6 +267,9 @@ public class Client : IClient
/// <inheritdoc />
public AccountsService Accounts { get; private set; }

/// <inheritdoc />
public BillingService Billing { get; private set; }

/// <inheritdoc />
public CertificatesService Certificates { get; private set; }

Expand Down Expand Up @@ -404,4 +414,4 @@ private void InitializeServices()
Zones = new ZonesService(this);
}
}
}
}
64 changes: 64 additions & 0 deletions src/dnsimple/Services/Billing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using dnsimple.Services.ListOptions;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using static dnsimple.Services.Paths;

namespace dnsimple.Services
{
/// <inheritdoc cref="ServiceBase"/>
/// <see>https://developer.dnsimple.com/v2/certificates/</see>
public class BillingService : ServiceBase
{
/// <inheritdoc cref="ServiceBase"/>
public BillingService(IClient client) : base(client)
{
}

/// <summary>
/// Lists the billing charges for an account.
/// </summary>
/// <param name="accountId">The account ID</param>
/// <param name="options">Options passed to the list (filtering, pagination, and sorting)</param>
/// <returns>A <c>ChargesResponse</c> containing a list of charges for the
/// account.</returns>
public PaginatedResponse<Charge> ListCharges(long accountId, ListChargesOptions options = null)
{
var builder = BuildRequestForPath(ChargesPath(accountId));
AddListOptionsToRequest(options, ref builder);

return new PaginatedResponse<Charge>(Execute(builder.Request));
}
}

/// <summary>
/// Represents the charge data.
/// </summary>
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public struct Charge
{
public DateTime InvoicedAt { get; set; }
public decimal TotalAmount { get; set; }
public decimal BalanceAmount { get; set; }
public string Reference { get; set; }
public string State { get; set; }
public List<ChargeItem> Items { get; set; }
}

/// <summary>
/// Represents the charge item data.
/// </summary>
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)]

public struct ChargeItem
{
public string Description { get; set; }
public decimal Amount { get; set; }
public long ProductId { get; set; }
public string ProductType { get; set; }
public string ProductReference { get; set; }
}

}
43 changes: 43 additions & 0 deletions src/dnsimple/Services/ListOptions/ListChargesOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace dnsimple.Services.ListOptions
{
/// <summary>
/// Defines the options you may want to send to list charges,
/// such as pagination and sorting
/// </summary>
/// <see cref="ListOptionsWithFiltering"/>
public class ListChargesOptions : ListOptionsWithFiltering
{
/// <summary>
/// Sets the start date to be filtered by.
/// </summary>
/// <param date="date">The start date we want to filter by.</param>
/// <returns>The instance of the <c>ChargesListOptions</c></returns>
public ListChargesOptions FilterByStartDate(string date)
{
AddFilter(new Filter { Field = "start_date", Value = date });
return this;
}

/// <summary>
/// Sets the end date to be filtered by.
/// </summary>
/// <param date="date">The end date we want to filter by.</param>
/// <returns>The instance of the <c>ChargesListOptions</c></returns>
public ListChargesOptions FilterByEndDate(string date)
{
AddFilter(new Filter { Field = "end_date", Value = date });
return this;
}

/// <summary>
/// Sets the order by which to sort by invoice date.
/// </summary>
/// <param name="order">The order in which we want to sort (asc or desc)</param>
/// <returns>The instance of <c>ChargesListOptions</c></returns>
public ListChargesOptions SortByInvoiceDate(Order order)
{
AddSortCriteria(new Sort { Field = "invoiced", Order = order });
return this;
}
}
}
Loading

0 comments on commit 8a237fa

Please sign in to comment.