Skip to content

Commit 0e89e12

Browse files
committed
Merge remote-tracking branch 'origin/main' into issue/NEST-608
2 parents 53209af + d472586 commit 0e89e12

14 files changed

Lines changed: 285 additions & 237 deletions

File tree

4.0.0.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ Please check the GitHub release entry [here](https://github.com/OrchardCMS/Orcha
1515
### SKU Generators
1616

1717
Now it's possible to automatically generate the product SKU. Manual entry remains the default for backwards compatibility, but if the SKU doesn't have to be a specific value you can enable the "Orchard Core Commerce - SKU Generator - GUID" feature. This makes the SKU field read-only even during product creation. Instead, when you publish the product a new GUID string is automatically generated and filled as the SKU. You can also implement your own SKU generator by implementing the `ISkuGenerator` service.
18+
19+
### Stripe
20+
21+
The client script in _stripe-payment-form.js_ has changed. Now `stripePaymentForm` accepts an object instead of a lot of individual positional parameters. Additionally, some parameters that are no longer used have been removed. If you have overridden the `CheckoutStripe` shape on your site, make sure to update it based on the one in OCC.

Directory.Packages.props

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,28 @@
66
<!-- The Orchard Core version should always be x.y.0 of the latest minor version for maximum compatibility when
77
distributed as NuGet packages. On the other hand, the consuming projects (including OrchardCore.Commerce.Web)
88
should use Orchard Core references for the latest patch version to pull all versions up in the final app. -->
9-
<OrchardCoreVersion>3.0.0-preview-18991</OrchardCoreVersion>
9+
<OrchardCoreVersion>3.0.0-preview-19009</OrchardCoreVersion>
1010
</PropertyGroup>
1111

1212
<ItemGroup>
1313
<PackageVersion Include="GraphQL.MicrosoftDI" Version="8.8.4" />
1414
<PackageVersion Include="GraphQL.SystemTextJson" Version="8.8.4" />
15-
<PackageVersion Include="HtmlSanitizer" Version="9.1.893-beta" />
16-
<PackageVersion Include="Lombiq.Analyzers.OrchardCore" Version="5.2.1-alpha.3.osoe-925" />
15+
<PackageVersion Include="HtmlSanitizer" Version="9.1.923-beta" />
16+
<PackageVersion Include="Lombiq.Analyzers.OrchardCore" Version="5.2.1-alpha.4.osoe-925" />
1717
<PackageVersion Include="Lombiq.HelpfulLibraries.OrchardCore" Version="12.6.1-alpha.7.osoe-925" />
1818
<PackageVersion Include="Lombiq.HelpfulLibraries.AspNetCore" Version="12.6.1-alpha.7.osoe-925" />
1919
<PackageVersion Include="Lombiq.HelpfulLibraries.Common" Version="12.6.1-alpha.7.osoe-925" />
2020
<PackageVersion Include="Lombiq.HelpfulLibraries.Refit" Version="12.6.1-alpha.7.osoe-925" />
2121
<PackageVersion Include="Lombiq.Tests" Version="5.0.1-alpha.1.osoe-925" />
22-
<PackageVersion Include="Lombiq.Tests.UI" Version="14.2.2-alpha.7.osoe-925" />
23-
<PackageVersion Include="Lombiq.Tests.UI.AppExtensions" Version="14.2.2-alpha.7.osoe-925" />
24-
<PackageVersion Include="Lombiq.Tests.UI.Shortcuts" Version="14.2.2-alpha.7.osoe-925" />
25-
<PackageVersion Include="Microsoft.Identity.Web" Version="4.6.0" />
26-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
22+
<PackageVersion Include="Lombiq.Tests.UI" Version="14.2.2-alpha.14.osoe-925" />
23+
<PackageVersion Include="Lombiq.Tests.UI.AppExtensions" Version="14.2.2-alpha.14.osoe-925" />
24+
<PackageVersion Include="Lombiq.Tests.UI.Shortcuts" Version="14.2.2-alpha.14.osoe-925" />
25+
<PackageVersion Include="Microsoft.Identity.Web" Version="4.9.0" />
26+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
2727
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
2828
<PackageVersion Include="OrchardCore.Application.Cms.Targets" Version="$(OrchardCoreVersion)" />
2929
<PackageVersion Include="OrchardCore.ContentFields" Version="$(OrchardCoreVersion)" />
30-
<PackageVersion Include="OrchardCore.Commerce.Translations" Version="1.0.0-preview.4.occ-121" />
30+
<PackageVersion Include="OrchardCore.Commerce.Translations" Version="1.0.0-preview.5.occ-221" />
3131
<PackageVersion Include="OrchardCore.ContentManagement" Version="$(OrchardCoreVersion)" />
3232
<PackageVersion Include="OrchardCore.ContentManagement.Abstractions" Version="$(OrchardCoreVersion)" />
3333
<PackageVersion Include="OrchardCore.ContentTypes" Version="$(OrchardCoreVersion)" />
@@ -46,7 +46,7 @@
4646
<PackageVersion Include="OrchardCore.Workflows.Abstractions" Version="$(OrchardCoreVersion)" />
4747
<PackageVersion Include="Shouldly" Version="4.3.0" />
4848
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
49-
<PackageVersion Include="Stripe.net" Version="50.4.1" />
49+
<PackageVersion Include="Stripe.net" Version="51.1.0" />
5050
<PackageVersion Include="xunit.v3" Version="3.2.2" />
5151
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
5252
</ItemGroup>
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
using OrchardCore.Commerce.Payment.Stripe.Models;
13
using System.Threading.Tasks;
24

35
namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;
@@ -8,17 +10,17 @@ namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;
810
public interface IPaymentIntentPersistence
911
{
1012
/// <summary>
11-
/// Returns the payment intent Id stored in the current session.
13+
/// Returns the payment intent information stored in the current session.
1214
/// </summary>
13-
Task<string> RetrieveAsync(string shoppingCartId = null);
15+
Task<PaymentIntentPersistenceInfo?> RetrieveAsync(string? shoppingCartId);
1416

1517
/// <summary>
16-
/// Saves a payment intent Id to the session.
18+
/// Saves a payment intent information to the session.
1719
/// </summary>
18-
Task StoreAsync(string paymentIntentId, string shoppingCartId = null);
20+
Task StoreAsync(string? shoppingCartId, PaymentIntentPersistenceInfo info);
1921

2022
/// <summary>
21-
/// Removes the payment intent Id stored in the current session.
23+
/// Removes the payment intent information stored in the current session.
2224
/// </summary>
23-
Task RemoveAsync(string shoppingCartId = null);
25+
Task RemoveAsync(string? shoppingCartId);
2426
}

src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,14 @@ namespace OrchardCore.Commerce.Payment.Stripe.Controllers;
1313

1414
public class StripeController : PaymentBaseController
1515
{
16-
private readonly IPaymentIntentPersistence _paymentIntentPersistence;
1716
private readonly IStripePaymentService _stripePaymentService;
1817

1918
public StripeController(
2019
IOrchardServices<StripeController> services,
2120
INotifier notifier,
22-
IPaymentIntentPersistence paymentIntentPersistence,
2321
IStripePaymentService stripePaymentService)
24-
: base(notifier, services.Logger.Value)
25-
{
26-
_paymentIntentPersistence = paymentIntentPersistence;
22+
: base(notifier, services.Logger.Value) =>
2723
_stripePaymentService = stripePaymentService;
28-
}
29-
30-
public async Task<IActionResult> UpdatePaymentIntent(string paymentIntent)
31-
{
32-
await _paymentIntentPersistence.StoreAsync(paymentIntent);
33-
return Ok();
34-
}
3524

3625
[AllowAnonymous]
3726
[HttpGet("stripe/middleware")]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using OrchardCore.Commerce.MoneyDataType;
2+
3+
namespace OrchardCore.Commerce.Payment.Stripe.Models;
4+
5+
public record PaymentIntentPersistenceInfo(string PaymentIntentId, Amount Amount);
Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,81 @@
1+
#nullable enable
2+
13
using Microsoft.AspNetCore.Http;
24
using OrchardCore.Commerce.Payment.Stripe.Abstractions;
5+
using OrchardCore.Commerce.Payment.Stripe.Models;
6+
using System.Text.Json;
37
using System.Threading.Tasks;
48

59
namespace OrchardCore.Commerce.Payment.Stripe.Services;
610

711
public class PaymentIntentPersistence : IPaymentIntentPersistence
812
{
913
// Using _ as a separator to avoid separator character conflicts.
10-
private const string PaymentIntentKeyPrefix = "OrchardCore_Commerce_PaymentIntent";
14+
private const string PaymentIntentKeyPrefix = "OrchardCore_Commerce_" + nameof(PaymentIntentPersistenceInfo);
1115

1216
private readonly IHttpContextAccessor _httpContextAccessor;
13-
private ISession Session => _httpContextAccessor.HttpContext?.Session;
17+
18+
private ISession? Session => _httpContextAccessor.HttpContext?.Session;
19+
private HttpRequest? Request => _httpContextAccessor.HttpContext?.Request;
1420

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

17-
public Task<string> RetrieveAsync(string shoppingCartId = null)
23+
public Task<PaymentIntentPersistenceInfo?> RetrieveAsync(string? shoppingCartId)
1824
{
1925
var key = GetCacheId(shoppingCartId);
20-
var serialized = Session.GetString(key);
21-
if (serialized == null && _httpContextAccessor.HttpContext != null)
26+
27+
if (Session?.GetString(key)?.Trim() is { Length: > 0 } serializedFromSession &&
28+
TryParse(serializedFromSession, out var sessionResult))
2229
{
23-
_httpContextAccessor.HttpContext.Request.Cookies.TryGetValue(key, out var serializedCart);
24-
return Task.FromResult(serializedCart);
30+
return Task.FromResult(sessionResult);
2531
}
2632

27-
return Task.FromResult(serialized);
33+
if (Request != null &&
34+
Request.Cookies.TryGetValue(key, out var serializedFromCookie) &&
35+
TryParse(serializedFromCookie, out var cookieResult))
36+
{
37+
return Task.FromResult(cookieResult);
38+
}
39+
40+
return Task.FromResult<PaymentIntentPersistenceInfo?>(null);
2841
}
2942

30-
public Task StoreAsync(string paymentIntentId, string shoppingCartId = null)
43+
public Task StoreAsync(string? shoppingCartId, PaymentIntentPersistenceInfo info)
3144
{
3245
var key = GetCacheId(shoppingCartId);
33-
if (Session.GetString(key) == paymentIntentId) return Task.CompletedTask;
46+
var serialized = JsonSerializer.Serialize(info);
3447

35-
Session.SetString(key, paymentIntentId);
36-
_httpContextAccessor.SetCookieForever(key, paymentIntentId);
48+
Session?.SetString(key, serialized);
49+
_httpContextAccessor.SetCookieForever(key, serialized);
3750

3851
return Task.CompletedTask;
3952
}
4053

41-
public Task RemoveAsync(string shoppingCartId = null)
54+
public Task RemoveAsync(string? shoppingCartId)
4255
{
4356
var key = GetCacheId(shoppingCartId);
44-
Session.Remove(key);
57+
Session?.Remove(key);
4558
_httpContextAccessor.HttpContext?.Response.Cookies.Delete(key);
4659

4760
return Task.CompletedTask;
4861
}
4962

50-
protected string GetCacheId(string shoppingCartId) =>
63+
protected string GetCacheId(string? shoppingCartId) =>
5164
string.IsNullOrEmpty(shoppingCartId) ? PaymentIntentKeyPrefix : $"{PaymentIntentKeyPrefix}_{shoppingCartId}";
65+
66+
private static bool TryParse(string serialized, out PaymentIntentPersistenceInfo? result)
67+
{
68+
result = null;
69+
if (string.IsNullOrWhiteSpace(serialized)) return false;
70+
71+
try
72+
{
73+
result = JsonSerializer.Deserialize<PaymentIntentPersistenceInfo?>(serialized);
74+
return !string.IsNullOrWhiteSpace(result?.PaymentIntentId);
75+
}
76+
catch
77+
{
78+
return false;
79+
}
80+
}
5281
}

src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentIntentService.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using OrchardCore.Commerce.Payment.Stripe.Helpers;
88
using OrchardCore.Settings;
99
using Stripe;
10+
using System.Threading;
1011
using System.Threading.Tasks;
12+
using static OrchardCore.Commerce.Payment.Stripe.Constants.PaymentIntentStatuses;
1113

1214
namespace OrchardCore.Commerce.Payment.Stripe.Services;
1315

@@ -20,6 +22,8 @@ public class StripePaymentIntentService : IStripePaymentIntentService
2022
private readonly IPaymentIntentPersistence _paymentIntentPersistence;
2123
private readonly IStringLocalizer<StripePaymentIntentService> T;
2224

25+
private CancellationToken Aborted => _hca.HttpContext?.RequestAborted ?? default;
26+
2327
public StripePaymentIntentService(
2428
PaymentIntentService paymentIntentService,
2529
IHttpContextAccessor httpContextAccessor,
@@ -44,11 +48,19 @@ public async Task<PaymentIntent> GetPaymentIntentAsync(string paymentIntentId)
4448
paymentIntentId,
4549
paymentIntentGetOptions,
4650
await _requestOptionsService.SetIdempotencyKeyAsync(),
47-
_hca.HttpContext.RequestAborted);
51+
Aborted);
4852
}
4953

5054
public async Task<PaymentIntent> CreatePaymentIntentAsync(Amount total, string shoppingCartId = null)
5155
{
56+
var paymentIntentInfo = await _paymentIntentPersistence.RetrieveAsync(shoppingCartId);
57+
if (paymentIntentInfo?.Amount == total &&
58+
await GetPaymentIntentAsync(paymentIntentInfo.PaymentIntentId) is { Status: RequiresPaymentMethod } storedPaymentIntent &&
59+
storedPaymentIntent.Amount == AmountHelpers.GetPaymentAmount(total))
60+
{
61+
return storedPaymentIntent;
62+
}
63+
5264
var siteSettings = await _siteService.GetSiteSettingsAsync();
5365
var paymentIntentOptions = new PaymentIntentCreateOptions
5466
{
@@ -59,8 +71,7 @@ public async Task<PaymentIntent> CreatePaymentIntentAsync(Amount total, string s
5971
};
6072

6173
var paymentIntent = await CreatePaymentIntentAsync(paymentIntentOptions);
62-
63-
await _paymentIntentPersistence.StoreAsync(paymentIntent.Id, shoppingCartId);
74+
await _paymentIntentPersistence.StoreAsync(shoppingCartId, new(paymentIntent.Id, total));
6475

6576
return paymentIntent;
6677
}
@@ -69,7 +80,7 @@ public async Task<PaymentIntent> CreatePaymentIntentAsync(PaymentIntentCreateOpt
6980
await _paymentIntentService.CreateAsync(
7081
options,
7182
await _requestOptionsService.SetIdempotencyKeyAsync(),
72-
_hca.HttpContext.RequestAborted);
83+
Aborted);
7384

7485
public async Task<PaymentIntent> GetOrUpdatePaymentIntentAsync(
7586
string paymentIntentId,
@@ -93,6 +104,6 @@ public async Task<PaymentIntent> GetOrUpdatePaymentIntentAsync(
93104
paymentIntentId,
94105
updateOptions,
95106
await _requestOptionsService.SetIdempotencyKeyAsync(),
96-
_hca.HttpContext.RequestAborted);
107+
Aborted);
97108
}
98109
}

src/Modules/OrchardCore.Commerce.Payment.Stripe/Services/StripePaymentService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public async Task<string> CreateClientSecretAsync(Amount total, ShoppingCartView
8181
return null;
8282
}
8383

84-
var paymentIntentId = await _paymentIntentPersistence.RetrieveAsync(cart.Id);
84+
var paymentIntentId = (await _paymentIntentPersistence.RetrieveAsync(cart.Id))?.PaymentIntentId;
8585
var totals = cart.GetTotalsOrThrowIfEmpty();
8686

8787
// Same here as on the checkout page: Later we have to figure out what to do if there are multiple
@@ -157,7 +157,7 @@ public async Task<ContentItem> CreateOrUpdateOrderFromShoppingCartAsync(
157157
string paymentIntentId = null,
158158
OrderPart orderPart = null)
159159
{
160-
var innerPaymentIntentId = paymentIntentId ?? await _paymentIntentPersistence.RetrieveAsync(shoppingCartId);
160+
var innerPaymentIntentId = paymentIntentId ?? (await _paymentIntentPersistence.RetrieveAsync(shoppingCartId))?.PaymentIntentId;
161161
var paymentIntent = await _stripePaymentIntentService.GetPaymentIntentAsync(innerPaymentIntentId);
162162

163163
// Stripe doesn't support multiple shopping cart IDs because we can't send that info to the middleware anyway.
@@ -215,7 +215,7 @@ public async Task<PaymentOperationStatusViewModel> PaymentConfirmationAsync(
215215
bool needToJudgeIntentStorage = true)
216216
{
217217
// If it is null it means the session was not loaded yet and a redirect is needed.
218-
if (needToJudgeIntentStorage && string.IsNullOrEmpty(await _paymentIntentPersistence.RetrieveAsync(shoppingCartId)))
218+
if (needToJudgeIntentStorage && string.IsNullOrEmpty((await _paymentIntentPersistence.RetrieveAsync(shoppingCartId))?.PaymentIntentId))
219219
{
220220
return new PaymentOperationStatusViewModel
221221
{

src/Modules/OrchardCore.Commerce.Payment.Stripe/Views/CheckoutStripe.cshtml

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
string clientSecret = Model.PaymentProviderData?.ClientSecret ?? string.Empty;
1414
string paymentIntentId = Model.PaymentProviderData?.PaymentIntentId ?? string.Empty;
1515
string accountId = Model.PaymentProviderData?.AccountId?.Trim();
16+
17+
var urlPrefix = Url.Content("~/").TrimEnd('/');
18+
var missingText = T["A value is required for %LABEL%."].Value;
19+
var stripeData = new
20+
{
21+
clientSecret,
22+
paymentIntentId,
23+
urlPrefix,
24+
publishableKey,
25+
missingText,
26+
};
1627
}
1728

1829
<input type="hidden" id="StripePaymentPart_PaymentIntentId_Text" name="StripePaymentPart.PaymentIntentId.Text">
@@ -45,24 +56,8 @@
4556
<script at="Head" asp-name="StripeJs" asp-src="https://js.stripe.com/v3/"></script>
4657
<script at="Head" asp-name="@ResourceNames.StripePaymentForm"></script>
4758
<script at="Foot" depends-on="StripeJs,StripePaymentForm">
48-
stripePaymentForm(
49-
@if (!string.IsNullOrEmpty(accountId))
50-
{
51-
<text>
52-
Stripe(@publishableKey.JsonHtmlContent(), @Json.Serialize(new { StripeAccount = accountId })),
53-
</text>
54-
}
55-
else {
56-
<text>
57-
Stripe(@publishableKey.JsonHtmlContent()),
58-
</text>
59-
}
60-
@clientSecret.JsonHtmlContent(),
61-
@paymentIntentId.JsonHtmlContent(),
62-
document.querySelector('input[name="__RequestVerificationToken"]').value,
63-
@Url.Content("~/").TrimEnd('/').JsonHtmlContent(),
64-
@T["There was an error during confirming the payment, please try again."].Json(),
65-
@T["A value is required for %LABEL%."].Json(),
66-
@(Orchard.Action<StripeController>(controller => controller.UpdatePaymentIntent("PAYMENT_INTENT")).JsonHtmlContent()));
59+
const data = @Json.Serialize(stripeData);
60+
data.stripeAccountId = @(string.IsNullOrEmpty(accountId) ? Html.Raw("undefined") : Json.Serialize(new { StripeAccount = accountId }));
61+
stripePaymentForm(data);
6762
</script>
6863
}

0 commit comments

Comments
 (0)