From 87835d7591527351e91edde7c9eeedd4a833f5ea Mon Sep 17 00:00:00 2001 From: anthonyvscode Date: Thu, 19 Oct 2023 15:47:18 +1000 Subject: [PATCH] Merchant Statements (#36) * Removed VerificationSession * SDK to version 0.20.0 * added Statement API endpoints * Update BaseClient.cs * Update StatementsClient.cs * Changed responses on statement file streams * Added Github Action workflows * updated property description --- .github/workflows/PR_Build_Check.yml | 25 ++++ .github/workflows/Release_Package.yml | 30 +++++ .../Pinch.SDK.WebSample.csproj | 1 - src/Pinch.SDK/Agreements/AgreementClient.cs | 4 +- src/Pinch.SDK/BaseClient.cs | 8 +- src/Pinch.SDK/Helpers/FileDto.cs | 17 +++ src/Pinch.SDK/Helpers/HttpClientHelpers.cs | 32 +++-- src/Pinch.SDK/Merchants/MerchantClient.cs | 12 -- .../Merchants/VerificationSession.cs | 25 ---- src/Pinch.SDK/Pinch.SDK.csproj | 8 +- src/Pinch.SDK/PinchApi.cs | 3 + src/Pinch.SDK/Statements/DailyStatement.cs | 23 ++++ src/Pinch.SDK/Statements/MerchantInvoice.cs | 29 +++++ src/Pinch.SDK/Statements/StatementsClient.cs | 113 ++++++++++++++++++ 14 files changed, 272 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/PR_Build_Check.yml create mode 100644 .github/workflows/Release_Package.yml create mode 100644 src/Pinch.SDK/Helpers/FileDto.cs delete mode 100644 src/Pinch.SDK/Merchants/VerificationSession.cs create mode 100644 src/Pinch.SDK/Statements/DailyStatement.cs create mode 100644 src/Pinch.SDK/Statements/MerchantInvoice.cs create mode 100644 src/Pinch.SDK/Statements/StatementsClient.cs diff --git a/.github/workflows/PR_Build_Check.yml b/.github/workflows/PR_Build_Check.yml new file mode 100644 index 0000000..f109e2c --- /dev/null +++ b/.github/workflows/PR_Build_Check.yml @@ -0,0 +1,25 @@ +name: PR_Build_Check + +on: + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + env: + CI: "" # Prevent node build warnings emitting as run errors. + + steps: + - uses: actions/checkout@v3 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3.2.0 + with: + dotnet-version: '6.0.x' + + - name: Install dependencies + run: dotnet restore + + - name: Build + run: dotnet build -c Release --no-restore diff --git a/.github/workflows/Release_Package.yml b/.github/workflows/Release_Package.yml new file mode 100644 index 0000000..3e80fa9 --- /dev/null +++ b/.github/workflows/Release_Package.yml @@ -0,0 +1,30 @@ +name: Upload dotnet package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v3 + with: + dotnet-version: '6.0.x' # SDK Version to use. + source-url: https://nuget.pkg.github.com/PinchPayments/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Build + run: dotnet build --configuration Release + + - name: Create the package + run: dotnet pack --configuration Release + + - name: Publish the package to GPR + working-directory: ./src/Pinch.SDK + run: dotnet nuget push ./bin/Release/*.nupkg \ No newline at end of file diff --git a/src/Pinch.SDK.WebSample/Pinch.SDK.WebSample.csproj b/src/Pinch.SDK.WebSample/Pinch.SDK.WebSample.csproj index 7242644..1c27cef 100644 --- a/src/Pinch.SDK.WebSample/Pinch.SDK.WebSample.csproj +++ b/src/Pinch.SDK.WebSample/Pinch.SDK.WebSample.csproj @@ -3,7 +3,6 @@ netcoreapp2.0 aspnet5-Pinch.SDK.WebSample-035714b9-1391-452f-876c-de29839a80c8 - $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; diff --git a/src/Pinch.SDK/Agreements/AgreementClient.cs b/src/Pinch.SDK/Agreements/AgreementClient.cs index ce32f50..3c52a2c 100644 --- a/src/Pinch.SDK/Agreements/AgreementClient.cs +++ b/src/Pinch.SDK/Agreements/AgreementClient.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Pinch.SDK.Helpers; namespace Pinch.SDK.Agreements { @@ -46,10 +47,9 @@ public async Task> GetByAnonymousToken(string tok /// /// Agreement ID /// - public async Task> GetDdr(string agreementId) + public async Task> GetDdr(string agreementId) { var response = await GetFile($"agreements/ddr/{agreementId}"); - return response.ToApiResponse(); } diff --git a/src/Pinch.SDK/BaseClient.cs b/src/Pinch.SDK/BaseClient.cs index 49a8e45..bbc4642 100644 --- a/src/Pinch.SDK/BaseClient.cs +++ b/src/Pinch.SDK/BaseClient.cs @@ -28,7 +28,7 @@ protected async Task> GetHttp(string url) return await SendHttp(() => new HttpRequestMessage(HttpMethod.Get, Options.BaseUri + url)); } - protected async Task GetFile(string url) + protected async Task GetFile(string url) { return await SendHttpFile(() => new HttpRequestMessage(HttpMethod.Get, Options.BaseUri + url)); } @@ -160,7 +160,7 @@ private async Task> SendHttp(Func SendHttpFile(Func requestFunc) + private async Task SendHttpFile(Func requestFunc) { try { @@ -176,11 +176,11 @@ private async Task SendHttpFile(Func requestFunc) response = await _httpClientFactory().SendAsync(request); } - return await QuickFile.FromMessage(response); + return await QuickFileResponse.FromMessage(response); } catch (Exception ex) { - return new QuickFile() + return new QuickFileResponse() { Errors = new List() { diff --git a/src/Pinch.SDK/Helpers/FileDto.cs b/src/Pinch.SDK/Helpers/FileDto.cs new file mode 100644 index 0000000..673bee6 --- /dev/null +++ b/src/Pinch.SDK/Helpers/FileDto.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Mime; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Pinch.SDK.Helpers +{ + public class FileDto + { + public Stream Stream { get; set; } + public string Filename { get; set; } + public string ContentType { get; set; } + } +} \ No newline at end of file diff --git a/src/Pinch.SDK/Helpers/HttpClientHelpers.cs b/src/Pinch.SDK/Helpers/HttpClientHelpers.cs index abb8aab..98e4590 100644 --- a/src/Pinch.SDK/Helpers/HttpClientHelpers.cs +++ b/src/Pinch.SDK/Helpers/HttpClientHelpers.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; @@ -59,10 +60,10 @@ public static async Task> Get(this HttpClient client, string u return qr.ToApiResponse(); } - public static async Task GetFile(this HttpClient client, string url) + public static async Task> GetFile(this HttpClient client, string url) { var response = await client.GetAsync(url); - var qr = await QuickFile.FromMessage(response); + var qr = await QuickFileResponse.FromMessage(response); return qr.ToApiResponse(); } @@ -294,17 +295,32 @@ public class QuickResponse : QuickResponse } } - public class QuickFile : QuickResponse + public class QuickFileResponse : QuickResponse { - public new static async Task FromMessage(HttpResponseMessage message) + public new static async Task FromMessage(HttpResponseMessage message) { - var response = new QuickFile(); - response.Message = message; - response.ResponseBody = await message.Content.ReadAsStringAsync(); + var response = new QuickFileResponse + { + Message = message + }; if (message.IsSuccessStatusCode) { - response.Data = await message.Content.ReadAsStreamAsync(); + response.Data = new FileDto() + { + Stream = await message.Content.ReadAsStreamAsync() + }; + + var header = message.Content.Headers.GetValues("content-disposition")?.ToList(); + if (header != null && header.Any()) + { + response.Data.Filename = new ContentDisposition(header.First()).FileName; + } + var contentTypeHeader = message.Content.Headers.GetValues("content-type")?.ToList(); + if (contentTypeHeader != null && contentTypeHeader.Any()) + { + response.Data.ContentType = contentTypeHeader.First(); + } } else { diff --git a/src/Pinch.SDK/Merchants/MerchantClient.cs b/src/Pinch.SDK/Merchants/MerchantClient.cs index 82cfe6f..c8b0e7e 100644 --- a/src/Pinch.SDK/Merchants/MerchantClient.cs +++ b/src/Pinch.SDK/Merchants/MerchantClient.cs @@ -73,18 +73,6 @@ public async Task> CreateManagedMerchant(ManagedMer return response.ToApiResponse(); } - /// - /// Create a new verification session for the specified user. - /// - /// The contactID in `con_XXXXXXXX` format. - /// - public async Task> CreateVerificationSession(string contactId) - { - var response = await PostHttp("merchants/verification-session", new { contactId }); - - return response.ToApiResponse(); - } - /// /// Upload documents for the merchant /// diff --git a/src/Pinch.SDK/Merchants/VerificationSession.cs b/src/Pinch.SDK/Merchants/VerificationSession.cs deleted file mode 100644 index c22fe34..0000000 --- a/src/Pinch.SDK/Merchants/VerificationSession.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Pinch.SDK.Merchants -{ - /// - /// Create Verification Session Response - /// - public class VerificationSession - { - /// - /// The fully qualified URL to verify the contact - /// - public string Url { get; set; } - - /// - /// Expiry time of the token in Utc - /// - public DateTime ExpiryUtc { get; set; } - - /// - /// Token used to verify the user - /// - public Guid Token { get; set; } - } -} \ No newline at end of file diff --git a/src/Pinch.SDK/Pinch.SDK.csproj b/src/Pinch.SDK/Pinch.SDK.csproj index 181e8c3..6ca0fa5 100644 --- a/src/Pinch.SDK/Pinch.SDK.csproj +++ b/src/Pinch.SDK/Pinch.SDK.csproj @@ -4,7 +4,7 @@ The official Pinch Payments SDK The easiest way to integrate your .net application with Pinch Payments. Create payers, add payments, receive webhooks, fetch payments, get paid. Really all the good stuff. It's for Australian businesses if you didn't already know. I'll assume you did if you got this far, if not, Welcome! but you should probably check out getpinch.com.au to get up to speed. Pinch Payments - Ben Cull + Pinch Payments net45;netstandard2.0 true Pinch.SDK @@ -13,7 +13,7 @@ https://raw.githubusercontent.com/PinchPayments/Pinch.SDK/master/assets/img/circle_logo_64x64.png https://github.com/PinchPayments/Pinch.SDK https://github.com/PinchPayments/Pinch.SDK - 0.19.3 + 0.20.0 @@ -30,8 +30,4 @@ - - - - diff --git a/src/Pinch.SDK/PinchApi.cs b/src/Pinch.SDK/PinchApi.cs index e9d7fd4..ccaad95 100644 --- a/src/Pinch.SDK/PinchApi.cs +++ b/src/Pinch.SDK/PinchApi.cs @@ -13,6 +13,7 @@ using Pinch.SDK.Payments; using Pinch.SDK.Plans; using Pinch.SDK.Refunds; +using Pinch.SDK.Statements; using Pinch.SDK.Subscriptions; using Pinch.SDK.Transfers; using Pinch.SDK.Webhooks; @@ -51,6 +52,7 @@ public class PinchApi private FeesClient _fees; private CustomerClient _customer; private RefundsClient _refunds; + private StatementsClient _statements; public AuthClient Auth => _auth ?? (_auth = new AuthClient(_secretKey, _options.AuthUri, _options.BaseUri, _options.AdditionalScopes, HttpClientFactoryOrStaticInstance())); public MerchantClient Merchant => _merchant ?? (_merchant = new MerchantClient(_options, GetAccessToken, HttpClientFactoryOrStaticInstance())); @@ -66,6 +68,7 @@ public class PinchApi public FeesClient Fees => _fees ?? (_fees = new FeesClient(_options, GetAccessToken, HttpClientFactoryOrStaticInstance())); public CustomerClient Customers => _customer ?? (_customer = new CustomerClient(_options, GetAccessToken, HttpClientFactoryOrStaticInstance())); public RefundsClient Refunds => _refunds ?? (_refunds = new RefundsClient(_options, GetAccessToken, HttpClientFactoryOrStaticInstance())); + public StatementsClient Statements => _statements ?? (_statements = new StatementsClient(_options, GetAccessToken, HttpClientFactoryOrStaticInstance())); /// /// Supply your Merchant ID and Secret Key. These can be found in the API Keys menu item in the Pinch Portal. diff --git a/src/Pinch.SDK/Statements/DailyStatement.cs b/src/Pinch.SDK/Statements/DailyStatement.cs new file mode 100644 index 0000000..343a2ce --- /dev/null +++ b/src/Pinch.SDK/Statements/DailyStatement.cs @@ -0,0 +1,23 @@ +using System; +using Pinch.SDK.Payers; + +namespace Pinch.SDK.Statements +{ + public class DailyStatement + { + /// + /// The Daily Statement ID + /// + public string Id { get; set; } + + /// + /// Status of the PDF generation. + /// + public string PdfGenerationStatus { get; set; } + + /// + /// Date of the statement + /// + public string StatementDateLocal { get; set; } + } +} diff --git a/src/Pinch.SDK/Statements/MerchantInvoice.cs b/src/Pinch.SDK/Statements/MerchantInvoice.cs new file mode 100644 index 0000000..762feb9 --- /dev/null +++ b/src/Pinch.SDK/Statements/MerchantInvoice.cs @@ -0,0 +1,29 @@ +using System; +using Pinch.SDK.Payers; + +namespace Pinch.SDK.Statements +{ + public class MerchantInvoice + { + /// + /// The Merchant Invoice Id + /// + public string Id { get; set; } + + /// + /// Invoice generation date. + /// + public string StatementDateLocal { get; set; } + + /// + /// Start Date of the merchant invoice + /// + public string PeriodStartDateLocal { get; set; } + + + /// + /// End Date of the merchant invoice + /// + public string PeriodEndDateLocal { get; set; } + } +} diff --git a/src/Pinch.SDK/Statements/StatementsClient.cs b/src/Pinch.SDK/Statements/StatementsClient.cs new file mode 100644 index 0000000..0462fc7 --- /dev/null +++ b/src/Pinch.SDK/Statements/StatementsClient.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Pinch.SDK.Helpers; +using Pinch.SDK.Merchants; + +namespace Pinch.SDK.Statements +{ + public class StatementsClient : BaseClient + { + public StatementsClient(PinchApiOptions options, Func> getAccessToken, Func httpClientFactory) + : base(options, getAccessToken, httpClientFactory) + { + } + + /// + /// Gets the Daily statements (paged) + /// + /// + /// + /// + /// + public async Task> GetDailyStatements(int page = 1, int pageSize = 50, DateTime? startDate = null, DateTime? endDate = null) + { + var url = $"statements/daily?page={page}&pagesize={pageSize}"; + + if (startDate.HasValue) + { + url += $"&startDate={startDate.Value:yyyy-MM-dd}"; + } + + if (endDate.HasValue) + { + url += $"&endDate={endDate.Value:yyyy-MM-dd}"; + } + + var response = await GetHttp>(url); + + return response.Data; + } + + /// + /// Re-sends the daily statement email to the merchant + /// + /// + public async Task ResendDailyStatement(string dailyStatementId) + { + var url = $"statements/daily/{dailyStatementId}/resend-email"; + var response = await PostHttp(url, null); + return response.ToApiResponse(); + } + + /// + /// Gets the daily statement PDF for the merchant + /// + /// + public async Task> GetDailyStatementPdf(string dailyStatementId) + { + var response = await GetFile($"statements/daily/{dailyStatementId}"); + return response.ToApiResponse(); + } + + /// + /// Gets the merchant invoices (paged) + /// + /// + /// + /// + /// + public async Task> GetMonthlyInvoices(int page = 1, int pageSize = 50, DateTime? startDate = null, DateTime? endDate = null) + { + var url = $"statements/merchant-invoice?page={page}&pagesize={pageSize}"; + + if (startDate.HasValue) + { + url += $"&startDate={startDate.Value:yyyy-MM-dd}"; + } + + if (endDate.HasValue) + { + url += $"&endDate={endDate.Value:yyyy-MM-dd}"; + } + + var response = await GetHttp>(url); + + return response.Data; + } + + /// + /// Re-sends the merchant invoice email to the merchant + /// + /// + public async Task ResendMerchantInvoice(string merchantInvoiceId) + { + var url = $"statements/merchant-invoice/{merchantInvoiceId}/resend-email"; + var response = await PostHttp(url, null); + return response.ToApiResponse(); + } + + /// + /// Gets the merchant invoice PDF + /// + /// + public async Task> GetMerchantInvoicePdf(string merchantInvoiceId) + { + var response = await GetFile($"statements/merchant-invoice/{merchantInvoiceId}"); + return response.ToApiResponse(); + } + } +} +