Skip to content

Commit

Permalink
Make stripe workflow possible on headless
Browse files Browse the repository at this point in the history
  • Loading branch information
wAsnk committed Oct 10, 2024
1 parent 869b9d2 commit 53fcc82
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;
/// </summary>
public interface IStripePaymentService
{
long GetPaymentAmount(Amount total);

/// <summary>
/// Returns the public key of the Stripe account.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,51 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using OrchardCore.Commerce.Endpoints;
using OrchardCore.Commerce.Endpoints.Permissions;
using OrchardCore.Commerce.MoneyDataType;
using OrchardCore.Commerce.Payment.Abstractions;
using OrchardCore.Commerce.Payment.Stripe.Abstractions;
using OrchardCore.Commerce.Payment.Stripe.EndPoints.Models;
using OrchardCore.Commerce.Payment.Stripe.Models;
using OrchardCore.Commerce.Payment.Stripe.Services;
using OrchardCore.Modules;
using Stripe;
using OrchardCore.Settings;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OrchardCore.Commerce.Payment.Stripe.EndPoints.Api;
public static class StripeEndpoint
{
// Get total amount
public static IEndpointRouteBuilder AddStripeTotalEndpoint(this IEndpointRouteBuilder builder)
{
builder.MapGet("api/checkout/stripe/total/{shoppingCartId?}", GetStripeTotalAsync)
.AllowAnonymous()
.DisableAntiforgery();

return builder;
}

private static async Task<IResult> GetStripeTotalAsync(
[FromRoute] string? shoppingCartId,
[FromServices] IShoppingCartService shoppingCartService,
[FromServices] IStripePaymentService stripePaymentService
)
{
var shoppingCartViewModel = await shoppingCartService.GetAsync(shoppingCartId);
var total = shoppingCartViewModel.Totals.Single();
return TypedResults.Ok(new
{
Amount = stripePaymentService.GetPaymentAmount(total),
total.Currency,
});
}

public static IEndpointRouteBuilder AddStripePublicKeyEndpoint(this IEndpointRouteBuilder builder)
{
builder.MapGet("api/checkout/stripe/public-key", GetStripePublicKeyAsync)
.RequireAuthorization("Api")
.DisableAntiforgery();

return builder;
Expand All @@ -41,30 +73,27 @@ HttpContext httpContext
public static IEndpointRouteBuilder AddStripePaymentIntentEndpoint(this IEndpointRouteBuilder builder)
{
builder.MapPost("api/checkout/stripe/payment-intent", CreatePaymentIntentAsync)
.RequireAuthorization("Api")
.DisableAntiforgery();

return builder;
}

[HttpPost]
public static async Task<IResult> CreatePaymentIntentAsync(
[FromBody] string amount,
[FromServices] PaymentIntentService paymentIntentService
[FromBody] CreatePaymentIntentViewModel viewModel,
[FromServices] ISiteService siteService,
[FromServices] IStripePaymentService stripePaymentService,
[FromServices] IShoppingCartService shoppingCartService,
[FromServices] IEnumerable<IPaymentProvider> paymentProviders
)
{
var paymentIntent = await paymentIntentService.CreateAsync(new PaymentIntentCreateOptions
{
Amount = long.Parse(amount),
Currency = "eur",
AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions { Enabled = true, },
});
var shoppingCartViewModel = await shoppingCartService.GetAsync(viewModel.ShoppingCartId);
var total = shoppingCartViewModel.Totals.Single();
var paymentIntent = await stripePaymentService.CreatePaymentIntentAsync(total);

return TypedResults.Ok(new
{
clientSecret = paymentIntent.ClientSecret,
// [DEV]: For demo purposes only, you should avoid exposing the PaymentIntent ID in the client-side code.
dpmCheckerLink = $"https://dashboard.stripe.com/settings/payment_methods/review?transaction_id={paymentIntent.Id}",
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace OrchardCore.Commerce.Payment.Stripe.EndPoints.Models;

public class CreatePaymentIntentViewModel
{
public string ShoppingCartId { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,5 @@
<ItemGroup Condition="!Exists($(LombiqNodeJsExtensionsPath))">
<PackageReference Include="Lombiq.NodeJs.Extensions" />
</ItemGroup>
<ItemGroup>
<Folder Include="EndPoints\Models\" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,37 @@ namespace OrchardCore.Commerce.Payment.Stripe.Services;

public class PaymentIntentPersistence : IPaymentIntentPersistence
{
private const string PaymentIntentKey = "OrchardCore:Commerce:PaymentIntent";
// Using _ as a separator to avoid separator character conflicts.
private const string PaymentIntentKey = "OrchardCore_Commerce_PaymentIntent";

private readonly IHttpContextAccessor _httpContextAccessor;
private ISession Session => _httpContextAccessor.HttpContext?.Session;

public PaymentIntentPersistence(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;

public string Retrieve() => Session.GetString(PaymentIntentKey);
public string Retrieve()
{
var serialized = Session.GetString(PaymentIntentKey);
if (serialized == null && _httpContextAccessor.HttpContext != null)
{
_httpContextAccessor.HttpContext.Request.Cookies.TryGetValue(PaymentIntentKey, out var serializedCart);
return serializedCart;
}

public void Store(string paymentIntentId) => Session.SetString(PaymentIntentKey, paymentIntentId);
return serialized;
}

public void Remove() => Session.Remove(PaymentIntentKey);
public void Store(string paymentIntentId)
{
if (Session.GetString(PaymentIntentKey) == paymentIntentId) return;

Session.SetString(PaymentIntentKey, paymentIntentId);
_httpContextAccessor.SetCookieForever(PaymentIntentKey, paymentIntentId);
}

public void Remove()
{
Session.Remove(PaymentIntentKey);
_httpContextAccessor.HttpContext?.Response.Cookies.Delete(PaymentIntentKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ await _contentManager.GetAsync(orderId) is not { } order)
};
}

private static long GetPaymentAmount(Amount total)
public long GetPaymentAmount(Amount total)
{
if (CurrencyCollectionConstants.ZeroDecimalCurrencies.Contains(total.Currency.CurrencyIsoCode))
{
Expand Down
4 changes: 3 additions & 1 deletion src/Modules/OrchardCore.Commerce.Payment.Stripe/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,7 @@ public override void ConfigureServices(IServiceCollection services)
public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) =>
routes.AddStripeMiddlewareEndpoint()
.AddStripePublicKeyEndpoint()
.AddStripePaymentIntentEndpoint();
.AddStripePaymentIntentEndpoint()
.AddStripeTotalEndpoint()
;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@
using OrchardCore.Commerce.Endpoints.Permissions;
using OrchardCore.Commerce.Endpoints.ViewModels;
using OrchardCore.Modules;
using System;
using System.Threading.Tasks;

namespace OrchardCore.Commerce.Endpoints.Api;
public static class ShoppingCartLineEndpoint
{
public static IEndpointRouteBuilder AddGetCartEndpoint(this IEndpointRouteBuilder builder)
{
builder.MapGet("api/shoppingcart/get-cart/{shoppingCartId}", GetCartAsync)
builder.MapGet("api/shoppingcart/get-cart/{shoppingCartId?}", GetCartAsync)
.DisableAntiforgery();

return builder;
}

[Authorize(AuthenticationSchemes = "Api")]
private static async Task<IResult> GetCartAsync(
[FromRoute] string shoppingCartId,
[FromRoute] string? shoppingCartId,
[FromServices] IAuthorizationService authorizationService,
[FromServices] IShoppingCartService shoppingCartService,
HttpContext httpContext)
Expand Down Expand Up @@ -65,8 +64,6 @@ private static async Task<IResult> AddItemAsync(
return httpContext.ChallengeOrForbid("Api");
}

if (string.IsNullOrEmpty(addItemVM.ShoppingCartId)) { addItemVM.ShoppingCartId = Guid.NewGuid().ToString("n"); }

var errored = await shoppingCartService.AddItemAsync(addItemVM.Line, addItemVM.Token, addItemVM.ShoppingCartId);
if (string.IsNullOrEmpty(errored))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,26 @@ public SessionShoppingCartPersistence(
_shoppingCartSerializer = shoppingCartSerializer;
}

protected override Task<ShoppingCart> RetrieveInnerAsync(string key) =>
_shoppingCartSerializer.DeserializeAsync(Session.GetString(key));
protected override Task<ShoppingCart> RetrieveInnerAsync(string key)
{
var serialized = Session.GetString(key);
if (serialized == null && _httpContextAccessor.HttpContext != null)
{
_httpContextAccessor.HttpContext.Request.Cookies.TryGetValue(key, out var serializedCart);
return _shoppingCartSerializer.DeserializeAsync(serializedCart);
}

return _shoppingCartSerializer.DeserializeAsync(serialized);
}

protected override async Task<bool> StoreInnerAsync(string key, ShoppingCart items)
{
var cartString = await _shoppingCartSerializer.SerializeAsync(items);
if (Session.GetString(key) == cartString) return false;

Session.SetString(key, cartString);
_httpContextAccessor.SetCookieForever(key, cartString);

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace OrchardCore.Commerce.Services;

public abstract class ShoppingCartPersistenceBase : IShoppingCartPersistence
{
private const string ShoppingCartPrefix = "OrchardCore:Commerce:ShoppingCart";
// Using _ as a separator to avoid separator character conflicts.
private const string ShoppingCartPrefix = "OrchardCore_Commerce_ShoppingCart";

private readonly Dictionary<string, JsonObject> _scopeCache = [];

Expand Down Expand Up @@ -68,5 +69,5 @@ public async Task StoreAsync(ShoppingCart items)
protected abstract Task<bool> StoreInnerAsync(string key, ShoppingCart items);

protected string GetCacheId(string shoppingCartId) =>
string.IsNullOrEmpty(shoppingCartId) ? ShoppingCartPrefix : $"{ShoppingCartPrefix}:{shoppingCartId}";
string.IsNullOrEmpty(shoppingCartId) ? ShoppingCartPrefix : $"{ShoppingCartPrefix}_{shoppingCartId}";
}

0 comments on commit 53fcc82

Please sign in to comment.