diff --git a/src/Libraries/OrchardCore.Commerce.MoneyDataType/Amount.cs b/src/Libraries/OrchardCore.Commerce.MoneyDataType/Amount.cs index 7aeccad36..534a09930 100644 --- a/src/Libraries/OrchardCore.Commerce.MoneyDataType/Amount.cs +++ b/src/Libraries/OrchardCore.Commerce.MoneyDataType/Amount.cs @@ -3,8 +3,10 @@ using OrchardCore.Commerce.MoneyDataType.Abstractions; using OrchardCore.Commerce.MoneyDataType.Serialization; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Text.Json.Serialization; namespace OrchardCore.Commerce.MoneyDataType; @@ -82,6 +84,18 @@ public int CompareTo(Amount other) public Amount GetRounded() => new(Math.Round(Value, Currency.DecimalPlaces), Currency); + public long GetPaymentAmount(IEnumerable zeroDecimalCurrencies, IEnumerable specialCases) + { + if (zeroDecimalCurrencies.Contains(Currency.CurrencyIsoCode)) + { + return (long)Math.Round(Value); + } + + return specialCases.Contains(Currency.CurrencyIsoCode) + ? (long)Math.Round(Value / 100m) * 10000 + : (long)Math.Round(Value * 100); + } + private void ThrowIfCurrencyDoesntMatch(Amount other, string operation = "compare") { if (Currency.Equals(other.Currency)) return; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeConfirmationTokenService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeConfirmationTokenService.cs new file mode 100644 index 000000000..f2597a098 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeConfirmationTokenService.cs @@ -0,0 +1,16 @@ +using Stripe; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Service for managing Stripe confirmation tokens. +/// +public interface IStripeConfirmationTokenService +{ + /// + /// Gets the Stripe confirmation token with an Id of . + /// + /// The Stripe . + Task GetConfirmationTokenAsync(string confirmationTokenId); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeCustomerService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeCustomerService.cs new file mode 100644 index 000000000..a1eace580 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeCustomerService.cs @@ -0,0 +1,59 @@ +using Stripe; +using System.Threading.Tasks; +using Address = OrchardCore.Commerce.AddressDataType.Address; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Stripe customer related services. +/// +public interface IStripeCustomerService +{ + /// + /// Get the first customer with the given email in Stripe. + /// + Task GetFirstCustomerByEmailAsync(string customerEmail); + + /// + /// Returns with the given Id in Stripe. + /// + Task GetCustomerByIdAsync(string customerId); + + /// + /// Returns with the given email in Stripe. If not found, create a new customer. + /// + /// If not provided the current user's email will be used. + Task GetAndUpdateOrCreateCustomerAsync( + Address billingAddress, + Address shippingAddress, + string email, + string phone); + + /// + /// Create a new customer in Stripe with the given . + /// + /// The created Stripe . + Task CreateCustomerAsync(CustomerCreateOptions customerCreateOptions); + + /// + /// Create the customer in Stripe with the given details which will be used to create the + /// . + /// + /// The created Stripe . + Task CreateCustomerAsync( + Address billingAddress, + Address shippingAddress, + string email, + string phone); + + /// + /// Update the customer in Stripe with the given details. + /// + /// The updated Stripe . + Task UpdateCustomerAsync( + string customerId, + Address billingAddress, + Address shippingAddress, + string email, + string phone); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentIntentService.cs new file mode 100644 index 000000000..fdd31e758 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentIntentService.cs @@ -0,0 +1,17 @@ +using OrchardCore.Commerce.MoneyDataType; +using Stripe; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +public interface IStripePaymentIntentService +{ + Task GetPaymentIntentAsync(string paymentIntentId); + + Task CreatePaymentIntentAsync(Amount total); + Task CreatePaymentIntentAsync(PaymentIntentCreateOptions options); + + Task GetOrUpdatePaymentIntentAsync( + string paymentIntentId, + Amount defaultTotal); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentService.cs index d6f742fd9..9d09d5116 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripePaymentService.cs @@ -4,7 +4,6 @@ using OrchardCore.Commerce.MoneyDataType; using OrchardCore.Commerce.Payment.Stripe.Models; using OrchardCore.Commerce.Payment.Stripe.Services; -using OrchardCore.Commerce.Payment.Stripe.ViewModels; using OrchardCore.Commerce.Payment.ViewModels; using OrchardCore.ContentManagement; using OrchardCore.DisplayManagement.ModelBinding; @@ -18,10 +17,6 @@ namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; /// public interface IStripePaymentService { - Task GetConfirmationTokenAsync(string confirmationTokenId); - - long GetPaymentAmount(Amount total); - /// /// Returns the public key of the Stripe account. /// @@ -32,16 +27,6 @@ public interface IStripePaymentService /// Task CreateClientSecretAsync(Amount total, ShoppingCartViewModel cart); - /// - /// Returns a object for the given . - /// - Task GetPaymentIntentAsync(string paymentIntentId); - - /// - /// Returns a object based on the given . - /// - Task CreatePaymentIntentAsync(Amount total); - /// /// Creates an order content item in the database, based on the stored and on the /// current content. diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionEventHandler.cs new file mode 100644 index 000000000..e01ee9a64 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionEventHandler.cs @@ -0,0 +1,22 @@ +using Stripe.Checkout; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Event handler for Stripe sessions. +/// +public interface IStripeSessionEventHandler +{ + /// + /// Called before a Stripe session is created with a prepopulated + /// . Here you can modify the options before the session is created. + /// + Task StripeSessionCreatingAsync(SessionCreateOptions options) => Task.CompletedTask; + + /// + /// Called after a Stripe session is created with the created and the + /// used during creation. + /// + Task StripeSessionCreatedAsync(Session session, SessionCreateOptions options) => Task.CompletedTask; +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionService.cs new file mode 100644 index 000000000..8421fa073 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSessionService.cs @@ -0,0 +1,16 @@ +using Stripe.Checkout; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Service for managing Stripe sessions. +/// +public interface IStripeSessionService +{ + /// + /// Creates a Stripe session using the given . + /// + /// The created Stripe . + Task CreateSessionAsync(SessionCreateOptions options); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSubscriptionService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSubscriptionService.cs new file mode 100644 index 000000000..d73d934ac --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeSubscriptionService.cs @@ -0,0 +1,34 @@ +using OrchardCore.Commerce.Payment.Stripe.Models; +using OrchardCore.Commerce.Payment.Stripe.ViewModels; +using Stripe; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Service for managing Stripe subscriptions. +/// +public interface IStripeSubscriptionService +{ + /// + /// Updates a Stripe subscription. + /// + Task UpdateSubscriptionAsync(string subscriptionId, SubscriptionUpdateOptions options); + + /// + /// Gets a Stripe subscription. + /// + Task GetSubscriptionAsync(string subscriptionId, SubscriptionGetOptions options); + + /// + /// Creates a Stripe subscription using the given . + /// + /// The created Stripe . + Task CreateSubscriptionAsync(SubscriptionCreateOptions options); + + /// + /// Creates a Stripe subscription using the given . + /// + /// The created Stripe . + Task CreateSubscriptionAsync(StripeCreateSubscriptionViewModel viewModel); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeWebhookEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeWebhookEventHandler.cs new file mode 100644 index 000000000..e0050c533 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Abstractions/IStripeWebhookEventHandler.cs @@ -0,0 +1,16 @@ +using Stripe; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Abstractions; + +/// +/// Event handler for the Stripe webhook. +/// +public interface IStripeWebhookEventHandler +{ + /// + /// Called when a Stripe event is received. This is where you can handle the event. + /// + /// Contains the Stripe Event parameters. + Task ReceivedStripeEventAsync(Event stripeEvent); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs index 90cbc2e4a..5a400a6dc 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/WebhookController.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using OrchardCore.Commerce.Payment.Stripe.Abstractions; using OrchardCore.Commerce.Payment.Stripe.Models; -using OrchardCore.Commerce.Payment.Stripe.Services; using OrchardCore.Settings; using Stripe; using System.Collections.Generic; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/EndPoints/Api/StripeCheckoutApiEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/EndPoints/Api/StripeCheckoutApiEndpoint.cs index 4995f0df6..cfd1dd6cc 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/EndPoints/Api/StripeCheckoutApiEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/EndPoints/Api/StripeCheckoutApiEndpoint.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; using OrchardCore.Commerce.Payment.Stripe.Endpoints.Models; using OrchardCore.Commerce.Payment.Stripe.Endpoints.Permissions; using OrchardCore.Commerce.Payment.Stripe.Services; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeConfirmationTokenEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeConfirmationTokenEndpoint.cs index 21fb268fa..0bc89a89f 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeConfirmationTokenEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeConfirmationTokenEndpoint.cs @@ -21,7 +21,7 @@ public static IEndpointRouteBuilder AddStripeConfirmationTokenEndpoint(this IEnd private static async Task GetStripeConfirmationTokenAsync( [FromQuery] string? confirmationTokenId, - [FromServices] IStripePaymentService stripePaymentService, + [FromServices] IStripeConfirmationTokenService stripeConfirmationTokenService, [FromServices] IAuthorizationService authorizationService, HttpContext httpContext) { @@ -30,7 +30,7 @@ private static async Task GetStripeConfirmationTokenAsync( return httpContext.ChallengeOrForbidApi(); } - var confirmationToken = await stripePaymentService.GetConfirmationTokenAsync(confirmationTokenId); + var confirmationToken = await stripeConfirmationTokenService.GetConfirmationTokenAsync(confirmationTokenId); return TypedResults.Ok(confirmationToken); } } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeCustomerEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeCustomerEndpoint.cs index 215c238b3..b8ea66ce5 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeCustomerEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeCustomerEndpoint.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; using OrchardCore.Commerce.Payment.Stripe.Endpoints.Permissions; using OrchardCore.Commerce.Payment.Stripe.Services; using Stripe; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeParametersEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeParametersEndpoint.cs index 4d4896292..5c461ae70 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeParametersEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeParametersEndpoint.cs @@ -14,6 +14,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; +using static OrchardCore.Commerce.Payment.Constants.CurrencyCollectionConstants; using static OrchardCore.Commerce.Payment.Stripe.Endpoints.Constants.Endpoints; namespace OrchardCore.Commerce.Payment.Stripe.Endpoints.Api; @@ -65,7 +66,6 @@ public static IEndpointRouteBuilder AddStripeTotalEndpoint(this IEndpointRouteBu private static async Task GetStripeTotalAsync( [FromQuery] string? shoppingCartId, [FromServices] IShoppingCartService shoppingCartService, - [FromServices] IStripePaymentService stripePaymentService, [FromServices] IAuthorizationService authorizationService, HttpContext httpContext) { @@ -83,7 +83,7 @@ private static async Task GetStripeTotalAsync( var total = shoppingCartViewModel.Totals.Single(); return TypedResults.Ok(new { - Amount = stripePaymentService.GetPaymentAmount(total), + Amount = total.GetPaymentAmount(ZeroDecimalCurrencies, SpecialCases), total.Currency, }); } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripePaymentIntentEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripePaymentIntentEndpoint.cs index 04955522d..345734f87 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripePaymentIntentEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripePaymentIntentEndpoint.cs @@ -24,7 +24,7 @@ public static IEndpointRouteBuilder AddStripePaymentIntentGetEndpoint(this IEndp private static async Task GetPaymentIntentAsync( [FromQuery] string paymentIntentId, - [FromServices] IStripePaymentService stripePaymentService, + [FromServices] IStripePaymentIntentService stripePaymentIntentService, [FromServices] IAuthorizationService authorizationService, HttpContext httpContext ) @@ -34,7 +34,7 @@ HttpContext httpContext return httpContext.ChallengeOrForbidApi(); } - var paymentIntent = await stripePaymentService.GetPaymentIntentAsync(paymentIntentId); + var paymentIntent = await stripePaymentIntentService.GetPaymentIntentAsync(paymentIntentId); return TypedResults.Ok(paymentIntent); } @@ -46,6 +46,7 @@ public static IEndpointRouteBuilder AddStripePaymentIntentPostEndpoint(this IEnd private static async Task CreatePaymentIntentAsync( [FromBody] CreatePaymentIntentWithOrderViewModel viewModel, + [FromServices] IStripePaymentIntentService stripePaymentIntentService, [FromServices] IStripePaymentService stripePaymentService, [FromServices] IShoppingCartService shoppingCartService, [FromServices] IAuthorizationService authorizationService, @@ -58,7 +59,7 @@ private static async Task CreatePaymentIntentAsync( var shoppingCartViewModel = await shoppingCartService.GetAsync(viewModel.ShoppingCartId); var total = shoppingCartViewModel.Totals.Single(); - var paymentIntent = await stripePaymentService.CreatePaymentIntentAsync(total); + var paymentIntent = await stripePaymentIntentService.CreatePaymentIntentAsync(total); var order = await stripePaymentService.CreateOrUpdateOrderFromShoppingCartAsync( updateModelAccessor: null, diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeSubscriptionEndpoint.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeSubscriptionEndpoint.cs index 588b923af..1d3cc7c69 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeSubscriptionEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Endpoints/Api/StripeSubscriptionEndpoint.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using OrchardCore.Commerce.Endpoints; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; using OrchardCore.Commerce.Payment.Stripe.Endpoints.Permissions; using OrchardCore.Commerce.Payment.Stripe.Extensions; using OrchardCore.Commerce.Payment.Stripe.Services; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/DefaultStripeWebhookEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/DefaultStripeWebhookEventHandler.cs similarity index 72% rename from src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/DefaultStripeWebhookEventHandler.cs rename to src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/DefaultStripeWebhookEventHandler.cs index 16fd6c55c..ff66a991d 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/DefaultStripeWebhookEventHandler.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/DefaultStripeWebhookEventHandler.cs @@ -3,14 +3,20 @@ using System.Threading.Tasks; using static Stripe.Events; -namespace OrchardCore.Commerce.Payment.Stripe.Services; +namespace OrchardCore.Commerce.Payment.Stripe.Handlers; public class DefaultStripeWebhookEventHandler : IStripeWebhookEventHandler { + private readonly IStripePaymentIntentService _stripePaymentIntentService; private readonly IStripePaymentService _stripePaymentService; - public DefaultStripeWebhookEventHandler(IStripePaymentService stripePaymentService) => + public DefaultStripeWebhookEventHandler( + IStripePaymentIntentService stripePaymentIntentService, + IStripePaymentService stripePaymentService) + { + _stripePaymentIntentService = stripePaymentIntentService; _stripePaymentService = stripePaymentService; + } public async Task ReceivedStripeEventAsync(Event stripeEvent) { @@ -28,7 +34,7 @@ public async Task ReceivedStripeEventAsync(Event stripeEvent) return; } - var paymentIntent = await _stripePaymentService.GetPaymentIntentAsync(paymentIntentId); + var paymentIntent = await _stripePaymentIntentService.GetPaymentIntentAsync(paymentIntentId); await _stripePaymentService.UpdateOrderToOrderedAsync(paymentIntent, shoppingCartId: null); } else if (stripeEvent.Type == PaymentIntentPaymentFailed) diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/SubscriptionStripeWebhookEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/SubscriptionStripeWebhookEventHandler.cs similarity index 91% rename from src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/SubscriptionStripeWebhookEventHandler.cs rename to src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/SubscriptionStripeWebhookEventHandler.cs index e8ec012c3..577fca9b5 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/SubscriptionStripeWebhookEventHandler.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Handlers/SubscriptionStripeWebhookEventHandler.cs @@ -1,6 +1,8 @@ using Lombiq.HelpfulLibraries.OrchardCore.Users; using Microsoft.Extensions.Logging; using OrchardCore.Commerce.Models; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; +using OrchardCore.Commerce.Payment.Stripe.Services; using OrchardCore.Commerce.Services; using OrchardCore.Modules; using Stripe; @@ -8,7 +10,7 @@ using System.Threading.Tasks; using static Stripe.Events; -namespace OrchardCore.Commerce.Payment.Stripe.Services; +namespace OrchardCore.Commerce.Payment.Stripe.Handlers; public class SubscriptionStripeWebhookEventHandler : IStripeWebhookEventHandler { @@ -63,7 +65,7 @@ public async Task ReceivedStripeEventAsync(Event stripeEvent) var stripeSubscription = await _stripeSubscriptionService.GetSubscriptionAsync(invoice.SubscriptionId, options: null); subscriptionPart.Metadata = stripeSubscription.Metadata; - await _subscriptionService.CreateOrUpdateActiveSubscriptionAsync(invoice.SubscriptionId, subscriptionPart); + await _subscriptionService.CreateOrUpdateSubscriptionAsync(invoice.SubscriptionId, subscriptionPart); } } } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Indexes/StripeSessionDataIndex.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Indexes/StripeSessionDataIndex.cs index b1358674e..cd0d8cb79 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Indexes/StripeSessionDataIndex.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Indexes/StripeSessionDataIndex.cs @@ -25,6 +25,5 @@ public override void Describe(DescribeContext context) => StripeSessionUrl = sessionData.StripeSessionUrl, StripeInvoiceId = sessionData.StripeInvoiceId, SerializedAdditionalData = sessionData.SerializedAdditionalData, - }); } diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeCustomerService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeCustomerService.cs deleted file mode 100644 index 6431ade1b..000000000 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeCustomerService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Stripe; -using System.Threading.Tasks; -using Address = OrchardCore.Commerce.AddressDataType.Address; - -namespace OrchardCore.Commerce.Payment.Stripe.Services; - -public interface IStripeCustomerService -{ - Task CreateCustomerAsync(CustomerCreateOptions customerCreateOptions); - Task GetFirstCustomerByEmailAsync(string customerEmail); - Task GetCustomerByIdAsync(string customerId); - Task GetAndUpdateOrCreateCustomerAsync( - Address billingAddress, - Address shippingAddress, - string email, - string phone); - - Task CreateCustomerAsync( - Address billingAddress, - Address shippingAddress, - string email, - string phone); - - Task UpdateCustomerAsync( - string customerId, - Address billingAddress, - Address shippingAddress, - string email, - string phone); -} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionEventHandler.cs deleted file mode 100644 index a4181bf21..000000000 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionEventHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Stripe.Checkout; -using System.Threading.Tasks; - -namespace OrchardCore.Commerce.Payment.Stripe.Services; - -public interface IStripeSessionEventHandler -{ - Task StripeSessionCreatingAsync(SessionCreateOptions options) => Task.CompletedTask; - Task StripeSessionCreatedAsync(Session session, SessionCreateOptions options) => Task.CompletedTask; -} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionService.cs deleted file mode 100644 index cbd5024bc..000000000 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSessionService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Stripe.Checkout; -using System.Threading.Tasks; - -namespace OrchardCore.Commerce.Payment.Stripe.Services; - -public interface IStripeSessionService -{ - Task CreateSessionAsync(SessionCreateOptions options); -} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSubscriptionService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSubscriptionService.cs deleted file mode 100644 index 00ecacb33..000000000 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeSubscriptionService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OrchardCore.Commerce.Payment.Stripe.Models; -using OrchardCore.Commerce.Payment.Stripe.ViewModels; -using Stripe; -using System.Threading.Tasks; - -namespace OrchardCore.Commerce.Payment.Stripe.Services; - -public interface IStripeSubscriptionService -{ - Task UpdateSubscriptionAsync(string subscriptionId, SubscriptionUpdateOptions options); - Task GetSubscriptionAsync(string subscriptionId, SubscriptionGetOptions options); - Task CreateSubscriptionAsync(SubscriptionCreateOptions options); - Task CreateSubscriptionAsync(StripeCreateSubscriptionViewModel viewModel); -} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeWebhookEventHandler.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeWebhookEventHandler.cs deleted file mode 100644 index 3d5d989be..000000000 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/IStripeWebhookEventHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Stripe; -using System.Threading.Tasks; - -namespace OrchardCore.Commerce.Payment.Stripe.Services; - -public interface IStripeWebhookEventHandler -{ - Task ReceivedStripeEventAsync(Event stripeEvent); -} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeConfirmationTokenService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeConfirmationTokenService.cs new file mode 100644 index 000000000..655d3bfda --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeConfirmationTokenService.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; +using Stripe; +using System.Threading.Tasks; + +namespace OrchardCore.Commerce.Payment.Stripe.Services; + +public class StripeConfirmationTokenService : IStripeConfirmationTokenService +{ + private readonly ConfirmationTokenService _confirmationTokenService = new(); + private readonly IHttpContextAccessor _hca; + private readonly IRequestOptionsService _requestOptionsService; + + public StripeConfirmationTokenService(IHttpContextAccessor httpContextAccessor, IRequestOptionsService requestOptionsService) + { + _hca = httpContextAccessor; + _requestOptionsService = requestOptionsService; + } + + public async Task GetConfirmationTokenAsync(string confirmationTokenId) => + await _confirmationTokenService.GetAsync( + confirmationTokenId, + cancellationToken: _hca.HttpContext.RequestAborted, + requestOptions: await _requestOptionsService.SetIdempotencyKeyAsync()); +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeCustomerService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeCustomerService.cs index 0fc89a0a7..931611894 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeCustomerService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeCustomerService.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Http; using OrchardCore.Commerce.Payment.Stripe.Abstractions; using Stripe; -using System; using System.Linq; using System.Threading.Tasks; using Address = OrchardCore.Commerce.AddressDataType.Address; @@ -35,7 +34,8 @@ public async Task GetCustomerByIdAsync(string customerId) requestOptions: await _requestOptionsService.SetIdempotencyKeyAsync(), cancellationToken: _hca.HttpContext.RequestAborted); } - catch (StripeException stripeException) + // TODO: Actually check the exception and only throw if it's not a user not found error. + catch (StripeException exception) { return null; } @@ -55,6 +55,7 @@ public async Task GetFirstCustomerByEmailAsync(string customerEmail) cancellationToken: _hca.HttpContext.RequestAborted); return list.Data.FirstOrDefault(); } + // TODO: Actually check the exception and only throw if it's not a user not found error. catch (StripeException stripeException) { return null; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs new file mode 100644 index 000000000..aa63a19c5 --- /dev/null +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs @@ -0,0 +1,96 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Localization; +using OrchardCore.Commerce.MoneyDataType; +using OrchardCore.Commerce.Payment.Stripe.Abstractions; +using OrchardCore.Commerce.Payment.Stripe.Constants; +using OrchardCore.Commerce.Payment.Stripe.Extensions; +using OrchardCore.Settings; +using Stripe; +using System.Threading.Tasks; +using static OrchardCore.Commerce.Payment.Constants.CurrencyCollectionConstants; + +namespace OrchardCore.Commerce.Payment.Stripe.Services; + +public class StripePaymentIntentService : IStripePaymentIntentService +{ + private readonly PaymentIntentService _paymentIntentService = new(); + private readonly IHttpContextAccessor _hca; + private readonly IRequestOptionsService _requestOptionsService; + private readonly ISiteService _siteService; + private readonly IPaymentIntentPersistence _paymentIntentPersistence; + private readonly IStringLocalizer T; + + public StripePaymentIntentService( + IHttpContextAccessor httpContextAccessor, + IRequestOptionsService requestOptionsService, + ISiteService siteService, + IPaymentIntentPersistence paymentIntentPersistence, + IStringLocalizer localizer) + { + _hca = httpContextAccessor; + _requestOptionsService = requestOptionsService; + _siteService = siteService; + _paymentIntentPersistence = paymentIntentPersistence; + T = localizer; + } + + public async Task GetPaymentIntentAsync(string paymentIntentId) + { + var paymentIntentGetOptions = new PaymentIntentGetOptions(); + paymentIntentGetOptions.AddExpansions(); + return await _paymentIntentService.GetAsync( + paymentIntentId, + paymentIntentGetOptions, + await _requestOptionsService.SetIdempotencyKeyAsync(), + _hca.HttpContext.RequestAborted); + } + + public async Task CreatePaymentIntentAsync(Amount total) + { + var siteSettings = await _siteService.GetSiteSettingsAsync(); + var paymentIntentOptions = new PaymentIntentCreateOptions + { + Amount = total.GetPaymentAmount(ZeroDecimalCurrencies, SpecialCases), + Currency = total.Currency.CurrencyIsoCode, + Description = T["User checkout on {0}", siteSettings.SiteName].Value, + AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions { Enabled = true, }, + }; + + var paymentIntent = await CreatePaymentIntentAsync(paymentIntentOptions); + + _paymentIntentPersistence.Store(paymentIntent.Id); + + return paymentIntent; + } + + public async Task CreatePaymentIntentAsync(PaymentIntentCreateOptions options) => + await _paymentIntentService.CreateAsync( + options, + await _requestOptionsService.SetIdempotencyKeyAsync(), + _hca.HttpContext.RequestAborted); + + public async Task GetOrUpdatePaymentIntentAsync( + string paymentIntentId, + Amount defaultTotal) + { + var paymentIntent = await GetPaymentIntentAsync(paymentIntentId); + + if (paymentIntent?.Status is PaymentIntentStatuses.Succeeded or PaymentIntentStatuses.Processing) + { + return paymentIntent; + } + + var updateOptions = new PaymentIntentUpdateOptions + { + Amount = defaultTotal.GetPaymentAmount(ZeroDecimalCurrencies, SpecialCases), + Currency = defaultTotal.Currency.CurrencyIsoCode, + }; + + updateOptions.AddExpansions(); + return await _paymentIntentService.UpdateAsync( + paymentIntentId, + updateOptions, + await _requestOptionsService.SetIdempotencyKeyAsync(), + _hca.HttpContext.RequestAborted); + } +} diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs index f58a4b106..9f27e3d7e 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentProvider.cs @@ -20,6 +20,7 @@ public class StripePaymentProvider : IPaymentProvider private readonly IPaymentIntentPersistence _paymentIntentPersistence; private readonly ISession _session; private readonly ISiteService _siteService; + private readonly IStripePaymentIntentService _stripePaymentIntentService; private readonly IStripePaymentService _stripePaymentService; public string Name => ProviderName; @@ -28,12 +29,14 @@ public StripePaymentProvider( IPaymentIntentPersistence paymentIntentPersistence, ISession session, ISiteService siteService, - IStripePaymentService stripePaymentService) + IStripePaymentService stripePaymentService, + IStripePaymentIntentService stripePaymentIntentService) { _paymentIntentPersistence = paymentIntentPersistence; _session = session; _siteService = siteService; _stripePaymentService = stripePaymentService; + _stripePaymentIntentService = stripePaymentIntentService; } public async Task CreatePaymentProviderDataAsync(IPaymentViewModel model, bool isPaymentRequest = false) @@ -42,7 +45,7 @@ public async Task CreatePaymentProviderDataAsync(IPaymentViewModel model try { - paymentIntent = await _stripePaymentService.CreatePaymentIntentAsync(model.SingleCurrencyTotal); + paymentIntent = await _stripePaymentIntentService.CreatePaymentIntentAsync(model.SingleCurrencyTotal); } catch (StripeException exception) when (exception.Message.StartsWithOrdinal("No API key provided.")) { diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs index a89f122ff..5dd809eb7 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs @@ -13,7 +13,6 @@ using OrchardCore.Commerce.Payment.Stripe.Extensions; using OrchardCore.Commerce.Payment.Stripe.Indexes; using OrchardCore.Commerce.Payment.Stripe.Models; -using OrchardCore.Commerce.Payment.Stripe.ViewModels; using OrchardCore.Commerce.Payment.ViewModels; using OrchardCore.Commerce.Promotion.Extensions; using OrchardCore.ContentFields.Fields; @@ -32,40 +31,38 @@ namespace OrchardCore.Commerce.Payment.Stripe.Services; public class StripePaymentService : IStripePaymentService { - private readonly PaymentIntentService _paymentIntentService = new(); - private readonly ConfirmationTokenService _confirmationTokenService = new(); - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpContextAccessor _hca; private readonly IContentManager _contentManager; private readonly ISiteService _siteService; - private readonly IRequestOptionsService _requestOptionsService; private readonly IStringLocalizer T; private readonly YesSql.ISession _session; private readonly IPaymentIntentPersistence _paymentIntentPersistence; private readonly IPaymentService _paymentService; private readonly IHtmlLocalizer H; + private readonly IStripePaymentIntentService _stripePaymentIntentService; #pragma warning disable S107 // Methods should not have too many parameters public StripePaymentService( IContentManager contentManager, ISiteService siteService, - IRequestOptionsService requestOptionsService, IStringLocalizer stringLocalizer, YesSql.ISession session, IPaymentIntentPersistence paymentIntentPersistence, IPaymentService paymentService, IHtmlLocalizer htmlLocalizer, - IHttpContextAccessor httpContextAccessor) + IHttpContextAccessor hca, + IStripePaymentIntentService stripePaymentIntentService) #pragma warning restore S107 // Methods should not have too many parameters { _contentManager = contentManager; _siteService = siteService; - _requestOptionsService = requestOptionsService; _session = session; _paymentIntentPersistence = paymentIntentPersistence; T = stringLocalizer; _paymentService = paymentService; H = htmlLocalizer; - _httpContextAccessor = httpContextAccessor; + _hca = hca; + _stripePaymentIntentService = stripePaymentIntentService; } public async Task GetPublicKeyAsync() @@ -93,23 +90,12 @@ public async Task CreateClientSecretAsync(Amount total, ShoppingCartView var defaultTotal = totals.SingleOrDefault(); var initPaymentIntent = string.IsNullOrEmpty(paymentIntentId) - ? await CreatePaymentIntentAsync(defaultTotal) - : await GetOrUpdatePaymentIntentAsync(paymentIntentId, defaultTotal); + ? await _stripePaymentIntentService.CreatePaymentIntentAsync(defaultTotal) + : await _stripePaymentIntentService.GetOrUpdatePaymentIntentAsync(paymentIntentId, defaultTotal); return initPaymentIntent.ClientSecret; } - public async Task GetPaymentIntentAsync(string paymentIntentId) - { - var paymentIntentGetOptions = new PaymentIntentGetOptions(); - paymentIntentGetOptions.AddExpansions(); - return await _paymentIntentService.GetAsync( - paymentIntentId, - paymentIntentGetOptions, - await _requestOptionsService.SetIdempotencyKeyAsync(), - _httpContextAccessor.HttpContext.RequestAborted); - } - public async Task UpdateOrderToOrderedAsync(PaymentIntent paymentIntent, string shoppingCartId) => await _paymentService.UpdateOrderToOrderedAsync( await GetOrderByPaymentIntentIdAsync(paymentIntent.Id), @@ -173,7 +159,7 @@ public async Task CreateOrUpdateOrderFromShoppingCartAsync( OrderPart orderPart = null) { var innerPaymentIntentId = paymentIntentId ?? _paymentIntentPersistence.Retrieve(); - var paymentIntent = await GetPaymentIntentAsync(innerPaymentIntentId); + var paymentIntent = await _stripePaymentIntentService.GetPaymentIntentAsync(innerPaymentIntentId); // Stripe doesn't support multiple shopping cart IDs because we can't send that info to the middleware anyway. var (order, isNew) = await _paymentService.CreateOrUpdateOrderFromShoppingCartAsync( @@ -235,13 +221,13 @@ public async Task PaymentConfirmationAsync( return new PaymentOperationStatusViewModel { Status = PaymentOperationStatus.WaitingForRedirect, - Url = _httpContextAccessor.HttpContext.Request.GetDisplayUrl(), + Url = _hca.HttpContext.Request.GetDisplayUrl(), }; } // If we can't find a valid payment intent based on ID or if we can't find the associated order, then something // went wrong and continuing from here would only cause a crash anyway. - if (await GetPaymentIntentAsync(paymentIntentId) is not { PaymentMethod: not null } fetchedPaymentIntent || + if (await _stripePaymentIntentService.GetPaymentIntentAsync(paymentIntentId) is not { PaymentMethod: not null } fetchedPaymentIntent || (await GetOrderPaymentByPaymentIntentIdAsync(paymentIntentId))?.OrderId is not { } orderId || await _contentManager.GetAsync(orderId) is not { } order) { @@ -297,7 +283,7 @@ await _contentManager.GetAsync(orderId) is not { } order) return new PaymentOperationStatusViewModel { Status = PaymentOperationStatus.WaitingForRedirect, - Url = _httpContextAccessor.HttpContext.Request.GetDisplayUrl(), + Url = _hca.HttpContext.Request.GetDisplayUrl(), }; } @@ -310,39 +296,6 @@ await _contentManager.GetAsync(orderId) is not { } order) }; } - public long GetPaymentAmount(Amount total) - { - if (CurrencyCollectionConstants.ZeroDecimalCurrencies.Contains(total.Currency.CurrencyIsoCode)) - { - return (long)Math.Round(total.Value); - } - - return CurrencyCollectionConstants.SpecialCases.Contains(total.Currency.CurrencyIsoCode) - ? (long)Math.Round(total.Value / 100m) * 10000 - : (long)Math.Round(total.Value * 100); - } - - public async Task CreatePaymentIntentAsync(Amount total) - { - var siteSettings = await _siteService.GetSiteSettingsAsync(); - var paymentIntentOptions = new PaymentIntentCreateOptions - { - Amount = GetPaymentAmount(total), - Currency = total.Currency.CurrencyIsoCode, - Description = T["User checkout on {0}", siteSettings.SiteName].Value, - AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions { Enabled = true, }, - }; - - var paymentIntent = await _paymentIntentService.CreateAsync( - paymentIntentOptions, - await _requestOptionsService.SetIdempotencyKeyAsync(), - _httpContextAccessor.HttpContext.RequestAborted); - - _paymentIntentPersistence.Store(paymentIntent.Id); - - return paymentIntent; - } - public async Task GetStripeConfirmParametersAsync( string returnUrl, ContentItem order = null) @@ -382,12 +335,6 @@ public async Task GetStripeConfirmParametersAsync( return model; } - public async Task GetConfirmationTokenAsync(string confirmationTokenId) => - await _confirmationTokenService.GetAsync( - confirmationTokenId, - cancellationToken: _httpContextAccessor.HttpContext.RequestAborted, - requestOptions: await _requestOptionsService.SetIdempotencyKeyAsync()); - private static AddressOptions CreateAddressOptions(Address address) => new() { @@ -398,30 +345,6 @@ private static AddressOptions CreateAddressOptions(Address address) => PostalCode = address.PostalCode ?? string.Empty, State = address.Province ?? string.Empty, }; - private async Task GetOrUpdatePaymentIntentAsync( - string paymentIntentId, - Amount defaultTotal) - { - var paymentIntent = await GetPaymentIntentAsync(paymentIntentId); - - if (paymentIntent?.Status is PaymentIntentStatuses.Succeeded or PaymentIntentStatuses.Processing) - { - return paymentIntent; - } - - var updateOptions = new PaymentIntentUpdateOptions - { - Amount = GetPaymentAmount(defaultTotal), - Currency = defaultTotal.Currency.CurrencyIsoCode, - }; - - updateOptions.AddExpansions(); - return await _paymentIntentService.UpdateAsync( - paymentIntentId, - updateOptions, - await _requestOptionsService.SetIdempotencyKeyAsync(), - _httpContextAccessor.HttpContext.RequestAborted); - } private async Task GetOrderByPaymentIntentIdAsync(string paymentIntentId) { diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeSessionService.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeSessionService.cs index cd4518daa..ba7257bef 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeSessionService.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripeSessionService.cs @@ -1,5 +1,4 @@ -#nullable enable -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using OrchardCore.Commerce.Payment.Stripe.Abstractions; using Stripe.Checkout; using System.Collections.Generic; @@ -32,7 +31,7 @@ public async Task CreateSessionAsync(SessionCreateOptions options) var session = await _sessionService.CreateAsync( options, await _requestOptionsService.SetIdempotencyKeyAsync(), - cancellationToken: _hca.HttpContext.RequestAborted); + cancellationToken: _hca.HttpContext!.RequestAborted); await _stripeSessionEventHandlers.AwaitEachAsync(handler => handler.StripeSessionCreatedAsync(session, options)); return session; diff --git a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Startup.cs b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Startup.cs index 57157f68b..c62e12086 100644 --- a/src/Modules/OrchardCore.Commerce.Payment.Stripe/Startup.cs +++ b/src/Modules/OrchardCore.Commerce.Payment.Stripe/Startup.cs @@ -7,6 +7,7 @@ using OrchardCore.Commerce.Payment.Stripe.Abstractions; using OrchardCore.Commerce.Payment.Stripe.Drivers; using OrchardCore.Commerce.Payment.Stripe.Endpoints.Extensions; +using OrchardCore.Commerce.Payment.Stripe.Handlers; using OrchardCore.Commerce.Payment.Stripe.Indexes; using OrchardCore.Commerce.Payment.Stripe.Migrations; using OrchardCore.Commerce.Payment.Stripe.Models; @@ -55,9 +56,13 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentSecurityPolicyProvider(); services.AddDataMigration(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); diff --git a/src/Modules/OrchardCore.Commerce/Endpoints/Api/OrderEndpoint.cs b/src/Modules/OrchardCore.Commerce/Endpoints/Api/OrderEndpoint.cs index 4501ce63f..cdf5dd7d7 100644 --- a/src/Modules/OrchardCore.Commerce/Endpoints/Api/OrderEndpoint.cs +++ b/src/Modules/OrchardCore.Commerce/Endpoints/Api/OrderEndpoint.cs @@ -3,12 +3,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Routing; -using OrchardCore.Commerce.Abstractions.Models; using OrchardCore.Commerce.Endpoints.Permissions; using OrchardCore.ContentManagement; -using System.Security.Claims; using System.Threading.Tasks; using static OrchardCore.Commerce.Abstractions.Constants.ContentTypes; diff --git a/src/Modules/OrchardCore.Commerce/Endpoints/Services/IShoppingCartService.cs b/src/Modules/OrchardCore.Commerce/Endpoints/Services/IShoppingCartService.cs index 5116d9c78..6f7487e3b 100644 --- a/src/Modules/OrchardCore.Commerce/Endpoints/Services/IShoppingCartService.cs +++ b/src/Modules/OrchardCore.Commerce/Endpoints/Services/IShoppingCartService.cs @@ -9,7 +9,13 @@ namespace OrchardCore.Commerce.Endpoints; /// public interface IShoppingCartService { + /// + /// Get shopping cart. + /// + /// Shopping cart Id. + /// A that contains all the necessary information a shopping cart have. Task GetAsync(string shoppingCartId = null); + /// /// Update shopping cart. /// diff --git a/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs b/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs index 7f7c7dd6e..977cf851a 100644 --- a/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs +++ b/src/Modules/OrchardCore.Commerce/Endpoints/Services/ShoppingCartService.cs @@ -1,6 +1,5 @@ using Lombiq.HelpfulLibraries.AspNetCore.Exceptions; using Lombiq.HelpfulLibraries.OrchardCore.Workflow; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Localization; using OrchardCore.Commerce.Abstractions; using OrchardCore.Commerce.Abstractions.Abstractions; diff --git a/src/Modules/OrchardCore.Commerce/Models/SubscriptionPart.cs b/src/Modules/OrchardCore.Commerce/Models/SubscriptionPart.cs index 82d4fc60f..c6973c8d4 100644 --- a/src/Modules/OrchardCore.Commerce/Models/SubscriptionPart.cs +++ b/src/Modules/OrchardCore.Commerce/Models/SubscriptionPart.cs @@ -18,7 +18,10 @@ public class SubscriptionPart : ContentPart public TextField SerializedMetadata { get; set; } = new(); [JsonIgnore] + // We are not directly setting the metadata field, but we are serializing it to a text field +#pragma warning disable CA2227 // CA2227: Change 'Metadata' to be read-only by removing the property setter public IDictionary Metadata +#pragma warning restore CA2227 { get => JsonSerializer.Deserialize>(SerializedMetadata.Text); set => SerializedMetadata.Text = JsonSerializer.Serialize(value); diff --git a/src/Modules/OrchardCore.Commerce/Services/ISubscriptionService.cs b/src/Modules/OrchardCore.Commerce/Services/ISubscriptionService.cs index 5d33ca8e4..7f3b7a06b 100644 --- a/src/Modules/OrchardCore.Commerce/Services/ISubscriptionService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/ISubscriptionService.cs @@ -3,7 +3,14 @@ namespace OrchardCore.Commerce.Services; +/// +/// Subscription content type services. +/// public interface ISubscriptionService { - Task CreateOrUpdateActiveSubscriptionAsync(string subscriptionId, SubscriptionPart subscriptionPart); + /// + /// Creates or updates a subscription if exists as a subscription content item + /// with the provided . + /// + Task CreateOrUpdateSubscriptionAsync(string idInPaymentProvider, SubscriptionPart subscriptionPart); } diff --git a/src/Modules/OrchardCore.Commerce/Services/SubscriptionService.cs b/src/Modules/OrchardCore.Commerce/Services/SubscriptionService.cs index edbea643d..7af5a39a6 100644 --- a/src/Modules/OrchardCore.Commerce/Services/SubscriptionService.cs +++ b/src/Modules/OrchardCore.Commerce/Services/SubscriptionService.cs @@ -1,5 +1,4 @@ -using OrchardCore.Commerce.Constants; -using OrchardCore.Commerce.Indexes; +using OrchardCore.Commerce.Indexes; using OrchardCore.Commerce.Models; using OrchardCore.ContentManagement; using System.Threading.Tasks; @@ -19,10 +18,10 @@ public SubscriptionService(IContentManager contentManager, ISession session) _session = session; } - public async Task CreateOrUpdateActiveSubscriptionAsync(string subscriptionId, SubscriptionPart subscriptionPart) + public async Task CreateOrUpdateSubscriptionAsync(string idInPaymentProvider, SubscriptionPart subscriptionPart) { var subscription = await _session.Query( - item => item.IdInPaymentProvider == subscriptionId) + item => item.IdInPaymentProvider == idInPaymentProvider) .FirstOrDefaultAsync(); subscription ??= await _contentManager.NewAsync(Subscription);