From 0dd76b4e2b80d2b3ca581aeb4e91260aef0a499d Mon Sep 17 00:00:00 2001 From: "MAREK-SHIFT4\\mguzowski" Date: Mon, 10 Feb 2025 16:26:12 +0100 Subject: [PATCH] allow PayoutTransaction listing to have source expanded --- .github/workflows/build.yml | 34 ++++++----- Shift4/Converters/BaseObjectTypeConverter.cs | 57 +++++++++++++++++++ Shift4/Converters/EventDataConverter.cs | 50 +--------------- Shift4/Converters/ExpandConverter.cs | 27 +++++++++ Shift4/Converters/ExpandableConverter.cs | 33 +++++++++++ Shift4/Internal/ApiClient.cs | 2 +- Shift4/Request/Expand.cs | 18 ++++++ .../Request/PayoutTransactionListRequest.cs | 15 +++++ Shift4/Response/Event.cs | 2 +- Shift4/Response/Expandable.cs | 14 +++++ Shift4/Response/FraudWarning.cs | 2 +- Shift4/Response/PayoutTransaction.cs | 3 +- Shift4/Shift4.csproj | 7 ++- Shift4/Shift4Gateway.cs | 6 +- Shift4Tests/Integration/PayoutTests.cs | 40 +++++++++++-- 15 files changed, 239 insertions(+), 71 deletions(-) create mode 100644 Shift4/Converters/BaseObjectTypeConverter.cs create mode 100644 Shift4/Converters/ExpandConverter.cs create mode 100644 Shift4/Converters/ExpandableConverter.cs create mode 100644 Shift4/Request/Expand.cs create mode 100644 Shift4/Response/Expandable.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e5b801..27b9ec4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,16 +6,18 @@ jobs: build: name: Build lib - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-dotnet@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 with: dotnet-version: 3.1.x - name: Create NuGet package run: dotnet pack Shift4 -c Release + env: + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1 - name: Upload NuGet package as artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nuget-package path: Shift4/bin/Release/Shift4.*.nupkg @@ -24,34 +26,40 @@ jobs: test: needs: build + strategy: + max-parallel: 1 + matrix: + dotnetversion: [netcoreapp3.1, net6.0, netcoreapp7.0] env: SECRET_KEY: ${{ secrets.SECRET_KEY }} name: Run tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-dotnet@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 with: dotnet-version: | 3.1.x 6.0.x 7.0.x - name: Run tests - run: dotnet test + run: dotnet test -p:TargetFramework=${{ matrix.dotnetversion }} + env: + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1 nuget-publish: if: startsWith(github.event.ref, 'refs/tags/v') needs: [build, test] name: Publish package to nuget.org - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} steps: - - uses: actions/setup-dotnet@v3 + - uses: actions/setup-dotnet@v4 with: dotnet-version: 3.1.x - name: Download build artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: nuget-package - name: Publish package to nuget.org @@ -62,9 +70,9 @@ jobs: if: startsWith(github.event.ref, 'refs/tags/v') needs: [build, test] name: Create Release - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: nuget-package - name: Create Release diff --git a/Shift4/Converters/BaseObjectTypeConverter.cs b/Shift4/Converters/BaseObjectTypeConverter.cs new file mode 100644 index 0000000..d4011d1 --- /dev/null +++ b/Shift4/Converters/BaseObjectTypeConverter.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Shift4.Response; + +namespace Shift4.Converters +{ + public abstract class BaseObjectTypeConverter : JsonConverter + { + protected (string, BaseResponse) convertObjectType(JsonReader reader, JsonSerializer serializer) { + JObject jObject = JObject.Load(reader); + switch (jObject.GetValue("objectType").ToString()) + { + case "charge": + var charge = new Charge(); + serializer.Populate(jObject.CreateReader(), charge); + return (charge.Id, charge); + case "credit": + var credit = new Credit(); + serializer.Populate(jObject.CreateReader(), credit); + return (credit.Id, credit); + case "dispute": + var dispute = new Dispute(); + serializer.Populate(jObject.CreateReader(), dispute); + return (dispute.Id, dispute); + case "refund": + var refund = new Refund(); + serializer.Populate(jObject.CreateReader(), refund); + return (refund.Id, refund); + case "subscription": + var subscription = new Subscription(); + serializer.Populate(jObject.CreateReader(), subscription); + return (subscription.Id, subscription); + case "plan": + var plan = new Plan(); + serializer.Populate(jObject.CreateReader(), plan); + return (plan.Id, plan); + case "customer": + var customer = new Customer(); + serializer.Populate(jObject.CreateReader(), customer); + return (customer.Id, customer); + case "fraud_warning": + var warning = new FraudWarning(); + serializer.Populate(jObject.CreateReader(), warning); + return (warning.Id, warning); + case "card": + var card = new Card(); + serializer.Populate(jObject.CreateReader(), card); + return (card.Id, card); + case "payout": + var payout = new Payout(); + serializer.Populate(jObject.CreateReader(), payout); + return (payout.Id, payout); + } + return (null, null); + } + } +} \ No newline at end of file diff --git a/Shift4/Converters/EventDataConverter.cs b/Shift4/Converters/EventDataConverter.cs index 389e919..a55dcda 100644 --- a/Shift4/Converters/EventDataConverter.cs +++ b/Shift4/Converters/EventDataConverter.cs @@ -1,14 +1,9 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Shift4.Response; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Shift4.Converters { - public class EventDataConverter : JsonConverter + public class EventDataConverter : BaseObjectTypeConverter { public override bool CanConvert(Type objectType) @@ -18,48 +13,7 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - JObject jObject = JObject.Load(reader); - switch (jObject.GetValue("objectType").ToString()) - { - case "charge": - var charge = new Charge(); - serializer.Populate(jObject.CreateReader(), charge); - return charge; - case "credit": - var credit = new Credit(); - serializer.Populate(jObject.CreateReader(), credit); - return credit; - case "dispute": - var dispute = new Dispute(); - serializer.Populate(jObject.CreateReader(), dispute); - return dispute; - case "subscription": - var subscription = new Subscription(); - serializer.Populate(jObject.CreateReader(), subscription); - return subscription; - case "plan": - var plan = new Plan(); - serializer.Populate(jObject.CreateReader(), plan); - return plan; - case "customer": - var customer = new Customer(); - serializer.Populate(jObject.CreateReader(), customer); - return customer; - case "fraud_warning": - var warning = new FraudWarning(); - serializer.Populate(jObject.CreateReader(), warning); - return warning; - case "card": - var card = new Card(); - serializer.Populate(jObject.CreateReader(), card); - return card; - case "payout": - var payout = new Payout(); - serializer.Populate(jObject.CreateReader(), payout); - return payout; - } - - return null; + return convertObjectType(reader, serializer).Item2; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) diff --git a/Shift4/Converters/ExpandConverter.cs b/Shift4/Converters/ExpandConverter.cs new file mode 100644 index 0000000..8a7ae8b --- /dev/null +++ b/Shift4/Converters/ExpandConverter.cs @@ -0,0 +1,27 @@ + +using System; +using Newtonsoft.Json; +using Shift4.Request; + +namespace Shift4.Converters +{ + public class ExpandConverter : JsonConverter + { + public override Expand ReadJson(JsonReader reader, Type objectType, Expand existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return null; + } + + public override void WriteJson(JsonWriter writer, Expand value, JsonSerializer serializer) + { + if (value == null || value.Paths.Count == 0) { + return; + } + writer.WriteStartArray(); + value.Paths.ForEach(path => { + writer.WriteValue(path); + }); + writer.WriteEndArray(); + } + } +} \ No newline at end of file diff --git a/Shift4/Converters/ExpandableConverter.cs b/Shift4/Converters/ExpandableConverter.cs new file mode 100644 index 0000000..85a9e50 --- /dev/null +++ b/Shift4/Converters/ExpandableConverter.cs @@ -0,0 +1,33 @@ +using System; +using Newtonsoft.Json; +using Shift4.Response; + +namespace Shift4.Converters +{ + public class ExpandableConverter : BaseObjectTypeConverter where T: BaseResponse + { + public override bool CanConvert(Type objectType) => objectType.IsInstanceOfType(typeof(Expandable)); + + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + // Case: JSON contains a string + return new Expandable { Id = reader.Value.ToString() }; + } + else if (reader.TokenType == JsonToken.StartObject) + { + var (id, deserializedObject) = convertObjectType(reader, serializer); + return new Expandable { ExpandedObject = (T)deserializedObject, Id = id}; + } + + throw new JsonSerializationException("Unexpected token type"); + + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + } + } +} \ No newline at end of file diff --git a/Shift4/Internal/ApiClient.cs b/Shift4/Internal/ApiClient.cs index cba5c35..34b6c19 100644 --- a/Shift4/Internal/ApiClient.cs +++ b/Shift4/Internal/ApiClient.cs @@ -17,7 +17,7 @@ public class ApiClient : IApiClient private string _privateAuthToken; private IHttpClient _client; private IFileExtensionToMimeMapper _fileExtensionToMimeMapper; - private string _sdkVersion = "3.7.0"; + private string _sdkVersion = "3.8.0"; public ApiClient(IHttpClient httpClient, ISecretKeyProvider secretKeyProvider, IFileExtensionToMimeMapper fileExtensionToMimeMapper) { diff --git a/Shift4/Request/Expand.cs b/Shift4/Request/Expand.cs new file mode 100644 index 0000000..0123f00 --- /dev/null +++ b/Shift4/Request/Expand.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Org.BouncyCastle.Asn1.Mozilla; + +namespace Shift4.Request +{ + public class Expand { + public List Paths {get; set; } + + public Expand() { + Paths = new List(); + } + + public Expand add(string path){ + Paths.Add(path); + return this; + } + } +} \ No newline at end of file diff --git a/Shift4/Request/PayoutTransactionListRequest.cs b/Shift4/Request/PayoutTransactionListRequest.cs index bf35e5b..e363dc3 100644 --- a/Shift4/Request/PayoutTransactionListRequest.cs +++ b/Shift4/Request/PayoutTransactionListRequest.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Shift4.Converters; using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Text; @@ -14,5 +16,18 @@ public class PayoutTransactionListRequest : ListRequest [JsonProperty("source")] public String Source { get; set; } + + [JsonProperty("expand")] + [JsonConverter(typeof(ExpandConverter))] + public Expand Expand { get; } + + public PayoutTransactionListRequest() { + Expand = new Expand(); + } + + public PayoutTransactionListRequest expandSource() { + this.Expand.add("source"); + return this; + } } } diff --git a/Shift4/Response/Event.cs b/Shift4/Response/Event.cs index e9b57ac..49f4118 100644 --- a/Shift4/Response/Event.cs +++ b/Shift4/Response/Event.cs @@ -16,7 +16,7 @@ public class Event : BaseResponse [JsonProperty("data")] [JsonConverter(typeof(EventDataConverter))] - public object Data { get; set; } + public BaseResponse Data { get; set; } [JsonProperty("log")] public string Log { get; set; } diff --git a/Shift4/Response/Expandable.cs b/Shift4/Response/Expandable.cs new file mode 100644 index 0000000..49204fc --- /dev/null +++ b/Shift4/Response/Expandable.cs @@ -0,0 +1,14 @@ +namespace Shift4.Response +{ + public class Expandable + { + public string Id { get; set;} + public T ExpandedObject {get; set;} + + public bool Expanded { + get { + return ExpandedObject != null; + } + } + } +} \ No newline at end of file diff --git a/Shift4/Response/FraudWarning.cs b/Shift4/Response/FraudWarning.cs index dbd9ace..b4b2b28 100644 --- a/Shift4/Response/FraudWarning.cs +++ b/Shift4/Response/FraudWarning.cs @@ -11,7 +11,7 @@ namespace Shift4.Response { public class FraudWarning : BaseResponse { - + [JsonProperty("id")] public String Id { get; set; } diff --git a/Shift4/Response/PayoutTransaction.cs b/Shift4/Response/PayoutTransaction.cs index f97c739..f1e5210 100644 --- a/Shift4/Response/PayoutTransaction.cs +++ b/Shift4/Response/PayoutTransaction.cs @@ -29,7 +29,8 @@ public class PayoutTransaction : BaseResponse public string Currency { get; set; } [JsonProperty("source")] - public string Source { get; set; } + [JsonConverter(typeof(ExpandableConverter))] + public Expandable Source { get; set; } [JsonProperty("payout")] public string Payout { get; set; } diff --git a/Shift4/Shift4.csproj b/Shift4/Shift4.csproj index 9a4e5dd..df03071 100644 --- a/Shift4/Shift4.csproj +++ b/Shift4/Shift4.csproj @@ -8,7 +8,7 @@ Shift4 - 3.7.0 + 3.8.0 Shift4 ©2024 Shift4. All rights reserved. Shift4 @@ -33,4 +33,9 @@ + + true + + + diff --git a/Shift4/Shift4Gateway.cs b/Shift4/Shift4Gateway.cs index 5702bc7..13ba8d3 100644 --- a/Shift4/Shift4Gateway.cs +++ b/Shift4/Shift4Gateway.cs @@ -550,7 +550,7 @@ private string GenerateGetPath(object parameters, string parentName = null) if (value != null && !IsIgnored(property)) { - if (property.PropertyType.IsClass() && !(value is string)) + if (property.PropertyType.IsClass() && !(value is string) && !(value is Expand)) { path.Append(GenerateGetPath(value, GetPropertyName(property))); } @@ -577,6 +577,10 @@ private bool IsIgnored(PropertyInfo property) private string GenerateGetSection(object value, PropertyInfo property, string parentName) { var propertyName = GetPropertyName(property); + if (value is Expand expand) { + return String.Join("&", expand.Paths.Select(path => "expand[]="+path)); + + } if (parentName != null) { var converterValue = ConvertValue(value); diff --git a/Shift4Tests/Integration/PayoutTests.cs b/Shift4Tests/Integration/PayoutTests.cs index 781dc26..37c445f 100644 --- a/Shift4Tests/Integration/PayoutTests.cs +++ b/Shift4Tests/Integration/PayoutTests.cs @@ -53,7 +53,7 @@ public async Task RetrievePayout() Assert.NotNull(payout.PeriodEnd); Assert.NotNull(payout.Created); - var listedPayout = (await _gateway.ListPayouts()).List[0]; + var listedPayout = (await _gateway.ListPayouts()).List.First(p => p.Id == payout.Id); Assert.Equal(payout.Id, listedPayout.Id); Assert.Equal(payout.Created, listedPayout.Created); Assert.Equal(payout.PeriodStart, listedPayout.PeriodStart); @@ -122,20 +122,52 @@ public async Task RetrievePayoutTransactions(){ var payoutTransactions = await _gateway.ListPayoutTransactions(new PayoutTransactionListRequest() { Payout = payout.Id } ); // then Assert.NotNull(payoutTransactions); - Assert.True(payoutTransactions.List.Count > 3); + Assert.True(payoutTransactions.List.Count >= 3); var chargePayoutTransaction = await _gateway.ListPayoutTransactions(new PayoutTransactionListRequest() { Source = charge.Id }); Assert.NotNull(chargePayoutTransaction); - Assert.True(chargePayoutTransaction.List.Count == 1); + Assert.Equal(1, chargePayoutTransaction.List.Count); var chargeTransaction = chargePayoutTransaction.List[0]; Assert.False(chargePayoutTransaction.HasMore); Assert.Equal(chargeTransaction.Type, PayoutTransactionType.Charge); Assert.True(chargeTransaction.Amount > 0); Assert.Equal(chargeTransaction.Created, charge.Created); Assert.Equal(chargeTransaction.Description, charge.Description); - Assert.Equal(chargeTransaction.Source, charge.Id); + Assert.Equal(chargeTransaction.Source.Id, charge.Id); Assert.True(chargeTransaction.Fee > 0); Assert.NotNull(chargeTransaction.Id); } + + [Fact] + public async Task RetrievePayoutTransactionsWithExpandedSource(){ + // given + var payout = await _gateway.CreatePayout(); + var charge = await _gateway.CreateCharge(new ChargeRequest() + { + Amount = 100, + Currency = "EUR", + Card = new CardRequest() + { + Number = "4242424242424242", + ExpMonth = "12", + ExpYear = "2033" + } + }); + payout = await _gateway.CreatePayout(); + //when + var request = new PayoutTransactionListRequest() { Source = charge.Id } + .expandSource(); + var chargePayoutTransaction = await _gateway.ListPayoutTransactions(request); + + //then + Assert.NotNull(chargePayoutTransaction); + Assert.Equal(1, chargePayoutTransaction.List.Count); + var chargeTransaction = chargePayoutTransaction.List[0]; + Assert.True(chargeTransaction.Source.Expanded); + Assert.True(chargeTransaction.Source.ExpandedObject is Charge); + Assert.Equal(chargeTransaction.Source.Id, charge.Id); + var sourceCharge = (Charge)chargeTransaction.Source.ExpandedObject; + Assert.Equal(sourceCharge.Id, charge.Id); + } } }