From cdbe8200a0cd417f05df2020617cdb396ec6af0d Mon Sep 17 00:00:00 2001 From: Vincent Kok Date: Sun, 29 Sep 2024 10:31:49 +0200 Subject: [PATCH] #396: Fix issue where ApplicationFee was set on wrong level (#399) * #396: Fix issue where ApplicationFee was set on the OrderRequest instead of the OrderPaymentParameters --- .../Models/Order/Request/OrderRequest.cs | 9 +--- .../ApplePaySpecificParameters.cs | 2 +- .../BillieSpecificParameters.cs | 2 +- .../CreditCardSpecificParameters.cs | 2 +- .../GiftcardSpecificParameters.cs | 2 +- .../IDealSpecificParameters.cs | 2 +- .../KbcSpecificParameters.cs | 2 +- .../KlarnaSpecificParameters.cs | 4 +- ...arameters.cs => OrderPaymentParameters.cs} | 7 ++- .../PaySafeCardSpecificParameters.cs | 2 +- .../SepaDirectDebitSpecificParameters.cs | 2 +- .../Response/SubscriptionResponse.cs | 5 ++ .../Api/OrderTests.cs | 32 ++++++++++++- .../Api/SubscriptionTests.cs | 46 ++++++++++++------- 14 files changed, 83 insertions(+), 36 deletions(-) rename src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/{PaymentSpecificParameters.cs => OrderPaymentParameters.cs} (57%) diff --git a/src/Mollie.Api/Models/Order/Request/OrderRequest.cs b/src/Mollie.Api/Models/Order/Request/OrderRequest.cs index 160a62e9..75ce8bee 100644 --- a/src/Mollie.Api/Models/Order/Request/OrderRequest.cs +++ b/src/Mollie.Api/Models/Order/Request/OrderRequest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Mollie.Api.JsonConverters; +using Mollie.Api.Models.Order.Request.PaymentSpecificParameters; using Newtonsoft.Json; namespace Mollie.Api.Models.Order.Request { @@ -101,7 +102,7 @@ public string? Method { /// /// Optional - Any payment specific properties can be passed here. /// - public PaymentSpecificParameters.PaymentSpecificParameters? Payment { get; set; } + public OrderPaymentParameters? Payment { get; set; } /// /// Provide any data you like, and we will save the data alongside the subscription. Whenever you fetch the subscription @@ -122,12 +123,6 @@ public string? Method { /// public bool? ShopperCountryMustMatchBillingCountry { get; set; } - /// - /// Adding an application fee allows you to charge the merchant for the payment and transfer this to your own account. - /// - [JsonProperty("payment.applicationFee")] - public ApplicationFee? ApplicationFee { get; set; } - /// /// Oauth only - The payment profile's unique identifier, for example pfl_3RkSN1zuPE. /// diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/ApplePaySpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/ApplePaySpecificParameters.cs index a12f7f87..5980a559 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/ApplePaySpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/ApplePaySpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record ApplePaySpecificParameters : PaymentSpecificParameters { + public record ApplePaySpecificParameters : OrderPaymentParameters { /// /// Optional - The Apple Pay Payment Token object (encoded as JSON) that is part of the result of authorizing a payment /// request. The token contains the payment information needed to authorize the payment. diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/BillieSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/BillieSpecificParameters.cs index e96cd072..1e0e286e 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/BillieSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/BillieSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record BillieSpecificParameters : PaymentSpecificParameters { + public record BillieSpecificParameters : OrderPaymentParameters { /// /// Billie is a B2B payment method, thus it requires some extra information to identify the business /// that is creating the order. It is recommended to include these parameters as part of the create diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/CreditCardSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/CreditCardSpecificParameters.cs index 56a0b41e..9a843f35 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/CreditCardSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/CreditCardSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record CreditCardSpecificParameters : PaymentSpecificParameters { + public record CreditCardSpecificParameters : OrderPaymentParameters { /// /// The card token you get from Mollie Components. The token contains the card information /// (such as card holder, card number and expiry date) needed to complete the payment. diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/GiftcardSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/GiftcardSpecificParameters.cs index 81683ccc..9141f256 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/GiftcardSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/GiftcardSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record GiftcardSpecificParameters : PaymentSpecificParameters { + public record GiftcardSpecificParameters : OrderPaymentParameters { /// /// The gift card brand to use for the payment. These issuers are not dynamically available through the Issuers API, /// but can be retrieved by using the issuers include in the Methods API. If you need a brand not in the list, contact diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/IDealSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/IDealSpecificParameters.cs index 25426855..c1c83efa 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/IDealSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/IDealSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record IDealSpecificParameters : PaymentSpecificParameters { + public record IDealSpecificParameters : OrderPaymentParameters { /// /// Optional - iDEAL issuer id. The id could for example be ideal_INGBNL2A. The returned paymentUrl will then directly /// point to the ING web site. diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KbcSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KbcSpecificParameters.cs index cb354aa3..95588f8a 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KbcSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KbcSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record KbcSpecificParameters : PaymentSpecificParameters { + public record KbcSpecificParameters : OrderPaymentParameters { /// /// The issuer to use for the KBC/CBC payment. These issuers are not dynamically available through the Issuers API, /// but can be retrieved by using the issuers include in the Methods API. See the Mollie.Api.Models.Payment.Request.KbcIssuer diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KlarnaSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KlarnaSpecificParameters.cs index ad5e2595..2f9f56b4 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KlarnaSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/KlarnaSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record KlarnaSpecificParameters : PaymentSpecificParameters where T : class { + public record KlarnaSpecificParameters : OrderPaymentParameters where T : class { public T? ExtraMerchantData { get; set; } } -} \ No newline at end of file +} diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaymentSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/OrderPaymentParameters.cs similarity index 57% rename from src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaymentSpecificParameters.cs rename to src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/OrderPaymentParameters.cs index 3f8f59fb..47b1d769 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaymentSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/OrderPaymentParameters.cs @@ -1,10 +1,15 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record PaymentSpecificParameters { + public record OrderPaymentParameters { public string? CustomerId { get; set; } /// /// See the Mollie.Api.Models.Payment.SequenceType class for a full list of known values. /// public string? SequenceType { get; set; } public string? WebhookUrl { get; set; } + + /// + /// Adding an application fee allows you to charge the merchant for the payment and transfer this to your own account. + /// + public ApplicationFee? ApplicationFee { get; set; } } } diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaySafeCardSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaySafeCardSpecificParameters.cs index 6d0cebcb..9a8cf852 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaySafeCardSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/PaySafeCardSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record PaySafeCardSpecificParameters : PaymentSpecificParameters { + public record PaySafeCardSpecificParameters : OrderPaymentParameters { /// /// Used for consumer identification. For example, you could use the consumer’s IP address. /// diff --git a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/SepaDirectDebitSpecificParameters.cs b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/SepaDirectDebitSpecificParameters.cs index 0a05bb76..56c4aa81 100644 --- a/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/SepaDirectDebitSpecificParameters.cs +++ b/src/Mollie.Api/Models/Order/Request/PaymentSpecificParameters/SepaDirectDebitSpecificParameters.cs @@ -1,5 +1,5 @@ namespace Mollie.Api.Models.Order.Request.PaymentSpecificParameters { - public record SepaDirectDebitSpecificParameters : PaymentSpecificParameters { + public record SepaDirectDebitSpecificParameters : OrderPaymentParameters { /// /// Optional - IBAN of the account holder. /// diff --git a/src/Mollie.Api/Models/Subscription/Response/SubscriptionResponse.cs b/src/Mollie.Api/Models/Subscription/Response/SubscriptionResponse.cs index 1bde5720..9310c169 100644 --- a/src/Mollie.Api/Models/Subscription/Response/SubscriptionResponse.cs +++ b/src/Mollie.Api/Models/Subscription/Response/SubscriptionResponse.cs @@ -91,6 +91,11 @@ public record SubscriptionResponse { /// public string? WebhookUrl { get; set; } + /// + /// The customer this subscription belongs to. + /// + public required string CustomerId { get; init; } + /// /// The optional metadata you provided upon subscription creation. Metadata can for example be used to link a plan to a /// subscription. diff --git a/tests/Mollie.Tests.Integration/Api/OrderTests.cs b/tests/Mollie.Tests.Integration/Api/OrderTests.cs index b8be12ea..8470c787 100644 --- a/tests/Mollie.Tests.Integration/Api/OrderTests.cs +++ b/tests/Mollie.Tests.Integration/Api/OrderTests.cs @@ -87,6 +87,36 @@ public async Task CreateOrderAsync_OrderWithRequiredFields_OrderIsCreated() { orderResponseLine.Metadata.Should().Be(expectedMetadataString); } + [DefaultRetryFact] + public async Task CreateOrderAsync_OrderWithApplicationFee_OrderIsCreated() { + // If: we create a order request with only the required parameters + OrderRequest orderRequest = CreateOrder() with { + Payment = new OrderPaymentParameters { + ApplicationFee = new ApplicationFee { + Amount = new Amount(Currency.EUR, 0.25m), + Description = "Test" + } + } + }; + + // When: We send the order request to Mollie + OrderResponse result = await _orderClient.CreateOrderAsync(orderRequest); + + // Then: Make sure we get a valid response + result.Should().NotBeNull(); + result.Amount.Should().Be(orderRequest.Amount); + result.OrderNumber.Should().Be(orderRequest.OrderNumber); + result.Lines.Should().HaveCount(orderRequest.Lines.Count()); + result.Links.Should().NotBeNull(); + OrderLineRequest orderLineRequest = orderRequest.Lines.First(); + OrderLineResponse orderResponseLine = result.Lines.First(); + orderResponseLine.Type.Should().Be(orderLineRequest.Type); + orderResponseLine.Links.ImageUrl!.Href.Should().Be(orderLineRequest.ImageUrl); + orderResponseLine.Links.ProductUrl!.Href.Should().Be(orderLineRequest.ProductUrl); + var expectedMetadataString = result.Lines.First().Metadata; + orderResponseLine.Metadata.Should().Be(expectedMetadataString); + } + [DefaultRetryFact] public async Task CreateOrderAsync_WithMultiplePaymentMethods_OrderIsCreated() { // When: we create a order request and specify multiple payment methods @@ -179,7 +209,7 @@ public async Task CreateOrderAsync_WithSinglePaymentMethod_OrderIsCreated() { [DefaultRetryTheory] [MemberData(nameof(PaymentSpecificParameters))] public async Task CreateOrderAsync_WithPaymentSpecificParameters_OrderIsCreated( - PaymentSpecificParameters paymentSpecificParameters) { + OrderPaymentParameters paymentSpecificParameters) { // If: we create a order request with payment specific parameters OrderRequest orderRequest = CreateOrder(); diff --git a/tests/Mollie.Tests.Integration/Api/SubscriptionTests.cs b/tests/Mollie.Tests.Integration/Api/SubscriptionTests.cs index 42121da4..1b63d573 100644 --- a/tests/Mollie.Tests.Integration/Api/SubscriptionTests.cs +++ b/tests/Mollie.Tests.Integration/Api/SubscriptionTests.cs @@ -89,40 +89,38 @@ public async Task CanCreateSubscription() { } [DefaultRetryFact] - public async Task CanCancelSubscription() { + public async Task CanUpdateSubscription() { // Given - string customerId = await GetFirstCustomerWithValidMandate(); - ListResponse subscriptions = await _subscriptionClient.GetSubscriptionListAsync(customerId); + var activeSubscription = await GetActiveSubscription(); // When - SubscriptionResponse subscriptionToCancel = subscriptions.Items - .FirstOrDefault(s => s.Status != SubscriptionStatus.Canceled); - if (subscriptionToCancel != null) { - await _subscriptionClient.CancelSubscriptionAsync(customerId, subscriptionToCancel.Id); - SubscriptionResponse cancelledSubscription = await _subscriptionClient.GetSubscriptionAsync(customerId, subscriptionToCancel.Id); + if (activeSubscription != null) { + var customerId = activeSubscription.CustomerId; + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest() { + Description = $"Updated subscription {Guid.NewGuid()}" + }; + SubscriptionResponse response = await _subscriptionClient.UpdateSubscriptionAsync(customerId, activeSubscription.Id, request); // Then - cancelledSubscription.Status.Should().Be(SubscriptionStatus.Canceled); + response.Description.Should().Be(request.Description); } } [DefaultRetryFact] - public async Task CanUpdateSubscription() { + public async Task CanCancelSubscription() { // Given string customerId = await GetFirstCustomerWithValidMandate(); ListResponse subscriptions = await _subscriptionClient.GetSubscriptionListAsync(customerId); // When - SubscriptionResponse subscriptionToUpdate = subscriptions.Items + SubscriptionResponse subscriptionToCancel = subscriptions.Items .FirstOrDefault(s => s.Status != SubscriptionStatus.Canceled); - if (subscriptionToUpdate != null) { - SubscriptionUpdateRequest request = new SubscriptionUpdateRequest() { - Description = $"Updated subscription {Guid.NewGuid()}" - }; - SubscriptionResponse response = await _subscriptionClient.UpdateSubscriptionAsync(customerId, subscriptionToUpdate.Id, request); + if (subscriptionToCancel != null) { + await _subscriptionClient.CancelSubscriptionAsync(customerId, subscriptionToCancel.Id); + SubscriptionResponse cancelledSubscription = await _subscriptionClient.GetSubscriptionAsync(customerId, subscriptionToCancel.Id); // Then - response.Description.Should().Be(request.Description); + cancelledSubscription.Status.Should().Be(SubscriptionStatus.Canceled); } } @@ -163,6 +161,20 @@ private async Task GetFirstCustomerWithValidMandate() { return null; } + private async Task GetActiveSubscription() { + ListResponse customers = await _customerClient.GetCustomerListAsync(); + + foreach (CustomerResponse customer in customers.Items.OrderByDescending(x => x.CreatedAt)) { + ListResponse subscriptions = await _subscriptionClient.GetSubscriptionListAsync(customer.Id); + var activeSubscription = subscriptions.Items.FirstOrDefault(x => x.Status == SubscriptionStatus.Active); + if (activeSubscription != null) { + return activeSubscription; + } + } + + return null; + } + public void Dispose() { _subscriptionClient?.Dispose();