Skip to content

Commit

Permalink
Merge branch 'main' into issue/OCC-194
Browse files Browse the repository at this point in the history
# Conflicts:
#	Directory.Packages.props
#	docs/releases/3.0.0.md
#	src/Modules/OrchardCore.Commerce.Payment.Stripe/Controllers/StripeController.cs
  • Loading branch information
BenedekFarkas committed Dec 12, 2024
2 parents 6972646 + 79c19a6 commit b05b615
Show file tree
Hide file tree
Showing 121 changed files with 2,886 additions and 528 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ jobs:
name: Build and Test (Linux)
uses: Lombiq/GitHub-Actions/.github/workflows/build-and-test-orchard-core.yml@dev
with:
machine-types: '["ubuntu-latest"]'
machine-types: '["ubuntu-24.04"]'
timeout-minutes: 60

build-and-test-windows:
name: Build and Test (Windows)
uses: Lombiq/GitHub-Actions/.github/workflows/build-and-test-orchard-core.yml@dev
with:
machine-types: '["windows-latest"]'
machine-types: '["windows-2022"]'
timeout-minutes: 60
# Running ZAP for security scans in Docker under GHA Windows runners won't work since such virtualization is not
# supported by GHA.
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
version: 2

build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.11"

Expand Down
8 changes: 5 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
should use Orchard Core references for the latest patch version to pull all versions up in the final app. -->
<OrchardCoreVersion>2.0.0</OrchardCoreVersion>

<LombiqHelpfulLibrariesVersion>11.0.0</LombiqHelpfulLibrariesVersion>
<LombiqHelpfulLibrariesVersion>11.0.1-alpha.0.offi-126</LombiqHelpfulLibrariesVersion>
<LombiqTestsUIVersion>11.0.0</LombiqTestsUIVersion>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Lombiq.Analyzers.OrchardCore" Version="5.0.0" />
<PackageVersion Include="Lombiq.HelpfulLibraries.OrchardCore" Version="$(LombiqHelpfulLibrariesVersion)" />
<PackageVersion Include="Lombiq.HelpfulLibraries.AspNetCore" Version="$(LombiqHelpfulLibrariesVersion)" />
<PackageVersion Include="Lombiq.HelpfulLibraries.Refit" Version="$(LombiqHelpfulLibrariesVersion)" />
<PackageVersion Include="Lombiq.NodeJs.Extensions" Version="2.1.0" />
<PackageVersion Include="Lombiq.Tests" Version="3.0.0" />
Expand All @@ -28,6 +29,7 @@
<PackageVersion Include="OrchardCore.ContentManagement.Abstractions" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.ContentTypes" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.ContentTypes.Abstractions" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.Flows" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.Html" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.Indexing.Abstractions" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.Localization" Version="$(OrchardCoreVersion)" />
Expand All @@ -39,8 +41,8 @@
<PackageVersion Include="OrchardCore.Title" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="OrchardCore.Workflows.Abstractions" Version="$(OrchardCoreVersion)" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="Stripe.net" Version="44.8.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="Stripe.net" Version="44.13.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="xunit" Version="2.8.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Orchard Core Commerce

[![Join the chat at https://discord.gg/PtaYnX63](https://img.shields.io/badge/chat_on-Discord-7289d9?color=7289d9)](https://discord.gg/PtaYnX63) [![Documentation](https://readthedocs.org/projects/orchardcorecommerce/badge/)](https://commerce.orchardcore.net/) [![OrchardCore.Commerce NuGet](https://img.shields.io/nuget/v/OrchardCore.Commerce?label=OrchardCore.Commerce)](https://www.nuget.org/packages/OrchardCore.Commerce/) [![Cloudsmith](https://api-prd.cloudsmith.io/badges/version/orchardcore/commerce/nuget/OrchardCore.Commerce/latest/x/?render=true&badge_token=gAAAAABey9hKFD_C-ZIpLvayS3HDsIjIorQluDs53KjIdlxoDz6Ntt1TzvMNJp7a_UWvQbsfN5nS7_0IbxCyqHZsjhmZP6cBkKforo-NqwrH5-E6QCrJ3D8%3D)](https://cloudsmith.io/~orchardcore/repos/commerce/packages/detail/nuget/OrchardCore.Commerce/latest/)
[![Discord](https://img.shields.io/discord/551136772243980291?color=%237289DA&label=Discord&logo=discord&logoColor=white&style=flat)](https://discord.gg/rYHxgqU5) [![Read the Docs](https://img.shields.io/readthedocs/orchardcorecommerce?label=Documentation)](https://commerce.orchardcore.net/) [![Latest version of 'OrchardCore.Commerce' on NuGet](https://img.shields.io/nuget/v/OrchardCore.Commerce?style=flat&label=NuGet)](https://www.nuget.org/packages/OrchardCore.Commerce/) [![Latest version of 'OrchardCore.Commerce' on Cloudsmith](https://api-prd.cloudsmith.io/v1/badges/version/orchardcore/commerce/nuget/OrchardCore.Commerce/latest/xsp=True/?render=true&show_latest=true&style=flat&labelColor=grey&label=Cloudsmith)](https://cloudsmith.io/~orchardcore/repos/commerce/packages/detail/nuget/OrchardCore.Commerce/latest/xsp=True/)

The commerce module for [Orchard Core](https://github.com/OrchardCMS/OrchardCore).

Expand Down
6 changes: 5 additions & 1 deletion docs/releases/3.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ The `order_line_item_view_models_and_tax_rates` Liquid filter has been removed.

The new `amount_to_string` filter processes the input object as `Amount` (like the `amount` filter) and correctly formats it just like the `Amount.ToString()` override in C#. You can use `amount_to_string: dot: ","` to make it display a comma as the decimal separator when it would use a dot. Unlike `amount`, you can also use this filter on a number with the `currency: "three-letter-code""` argument (e.g. `{{ value | amount_to_string: currency: "EUR" }}`). This will display any numeric value as the given currency.

### Stripe controllers and endpoints

- The `PaymentConfirmationMiddleware` action with the `checkout/middleware/Stripe` path in `StripeController` was changed to `PaymentConfirmation` and its path to `stripe/middleware`.
- The `ConfirmPaymentParameters` action with the `checkout/params/Stripe` path in `StripeController` was change to `stripe/params` path.

## Change Logs

Please check the GitHub release entry [here](https://github.com/OrchardCMS/OrchardCore.Commerce/releases/tag/v3.0.0).

Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public static class ContentTypes
public const string ShoppingCartWidget = nameof(ShoppingCartWidget);
public const string UserAddresses = nameof(UserAddresses);
public const string UserDetails = nameof(UserDetails);
public const string Subscription = nameof(Subscription);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace OrchardCore.Commerce.Abstractions.ViewModels;

public class ShoppingCartViewModel
{
public string Id { get; set; }

[JsonIgnore]
public IList<LocalizedHtmlString> InvalidReasons { get; } = new List<LocalizedHtmlString>();

[JsonIgnore]
public IList<LocalizedHtmlString> Headers { get; } = new List<LocalizedHtmlString>();

[JsonIgnore]
public IList<List<IShape>> TableShapes { get; } = new List<List<IShape>>();
public IList<ShoppingCartLineViewModel> Lines { get; } = new List<ShoppingCartLineViewModel>();
public IList<Amount> Totals { get; } = new List<Amount>();
Expand Down
8 changes: 5 additions & 3 deletions src/Libraries/OrchardCore.Commerce.AddressDataType/Region.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ namespace OrchardCore.Commerce.AddressDataType;

public record Region(string EnglishName, string TwoLetterISORegionName, string DisplayName)
{
public Region(RegionInfo info)
: this(info.EnglishName, info.TwoLetterISORegionName, info.EnglishName)
{ }
// This used to be a constructor, but has been turned into a factory method to avoid causing JSON serialization
// exceptions. See https://github.com/dotnet/runtime/issues/45373#issuecomment-812091894 for more info on the
// problem in general.
public static Region FromRegionInfo(RegionInfo info) =>
new(info.EnglishName, info.TwoLetterISORegionName, info.EnglishName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static class Regions
region is { TwoLetterISORegionName.Length: 2 } && // Filter out world and other 3-digit regions.
!string.IsNullOrEmpty(region.EnglishName))
.Distinct()
.Select(region => new Region(region))
.Select(Region.FromRegionInfo)
.ToList();

/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions src/Libraries/OrchardCore.Commerce.MoneyDataType/Amount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using OrchardCore.Commerce.MoneyDataType.Abstractions;
using OrchardCore.Commerce.MoneyDataType.Serialization;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -82,6 +83,35 @@ public int CompareTo(Amount other)
public Amount GetRounded() =>
new(Math.Round(Value, Currency.DecimalPlaces), Currency);

/// <summary>
/// Converts the <see cref="Amount"/> to a fixed-point fractional value by keeping some digits based on the <see
/// cref="ICurrency.CurrencyIsoCode"/>.
/// </summary>
/// <param name="roundingByCurrencyCode">
/// Provides exceptional rounding rules for currencies that aren't converted according to the default. The key is
/// the <see cref="Currency"/>'s ISO code, the value pairs follow the same logic as the matching default parameters.
/// </param>
/// <param name="defaultKeepDigits">Indicates how many digits should be kept after the decimal point.</param>
/// <param name="defaultRoundTens">
/// If positive, the <see cref="Amount"/> is rounded to this many digits before converted to a fixed-point
/// fractional. Ignored otherwise.
/// </param>
public long GetFixedPointAmount(
IDictionary<string, (int KeepDigits, int RoundTens)> roundingByCurrencyCode,
int defaultKeepDigits = 2,
int defaultRoundTens = 0)
{
static int Tens(int zeroes) => (int)Math.Pow(10, zeroes);

var (keepDigits, roundTens) = roundingByCurrencyCode.TryGetValue(Currency.CurrencyIsoCode, out var pair)
? pair
: (defaultKeepDigits, defaultRoundTens);

return roundTens > 0
? (long)Math.Round(Value / Tens(roundTens)) * Tens(roundTens + keepDigits)
: (long)Math.Round(Value * Tens(keepDigits));
}

private void ThrowIfCurrencyDoesntMatch(Amount other, string operation = "compare")
{
if (Currency.Equals(other.Currency)) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Stripe;
using System.Threading.Tasks;

namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;

/// <summary>
/// Service for managing Stripe confirmation tokens.
/// </summary>
public interface IStripeConfirmationTokenService
{
/// <summary>
/// Gets the Stripe confirmation token with an Id of <paramref name="confirmationTokenId"/>.
/// </summary>
/// <returns>The Stripe <see cref="ConfirmationToken"/>.</returns>
Task<ConfirmationToken> GetConfirmationTokenAsync(string confirmationTokenId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Stripe;
using System.Threading.Tasks;
using Address = OrchardCore.Commerce.AddressDataType.Address;

namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;

/// <summary>
/// Stripe customer related services.
/// </summary>
public interface IStripeCustomerService
{
/// <summary>
/// Search for customers in Stripe with the given <paramref name="options"/>.
/// </summary>
Task<StripeSearchResult<Customer>> SearchCustomersAsync(CustomerSearchOptions options);

/// <summary>
/// Get the first customer with the given email in Stripe.
/// </summary>
Task<Customer> GetFirstCustomerByEmailAsync(string customerEmail);

/// <summary>
/// Returns <see cref="Customer"/> with the given Id in Stripe.
/// </summary>
Task<Customer> GetCustomerByIdAsync(string customerId);

/// <summary>
/// Returns <see cref="Customer"/> with the given email in Stripe. If not found, create a new customer.
/// </summary>
/// <param name="email">If not provided the current user's email will be used.</param>
Task<Customer> GetAndUpdateOrCreateCustomerAsync(
Address billingAddress,
Address shippingAddress,
string email,
string phone);

/// <summary>
/// Create a new customer in Stripe with the given <paramref name="customerCreateOptions"/>.
/// </summary>
/// <returns>The created Stripe <see cref="Customer"/>.</returns>
Task<Customer> CreateCustomerAsync(CustomerCreateOptions customerCreateOptions);

/// <summary>
/// Create the customer in Stripe with the given details which will be used to create the
/// <see cref="CustomerCreateOptions"/>.
/// </summary>
/// <returns>The created Stripe <see cref="Customer"/>.</returns>
Task<Customer> CreateCustomerAsync(
Address billingAddress,
Address shippingAddress,
string email,
string phone);

/// <summary>
/// Update the customer in Stripe with the given details.
/// </summary>
/// <returns>The updated Stripe <see cref="Customer"/>.</returns>
Task<Customer> UpdateCustomerAsync(
string customerId,
Address billingAddress,
Address shippingAddress,
string email,
string phone);

/// <summary>
/// Populate the returned <see cref="CustomerCreateOptions"/> with the given details.
/// </summary>
CustomerCreateOptions PopulateCustomerCreateOptions(
Address billingAddress,
Address shippingAddress,
string email,
string phone);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Stripe;

namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;

/// <summary>
/// Stripe helping services, needed so we can mock this part of Stripe also.
/// </summary>
public interface IStripeHelperService
{
/// <summary>
/// Parses a JSON string from a Stripe webhook into a <see cref="Event"/> object, while
/// verifying the <a href="https://stripe.com/docs/webhooks/signatures">webhook's
/// signature</a>.
/// </summary>
/// <param name="json">The JSON string to parse.</param>
/// <param name="stripeSignatureHeader">
/// The value of the <c>Stripe-Signature</c> header from the webhook request.
/// </param>
/// <param name="secret">The webhook endpoint's signing secret.</param>
/// <param name="throwOnApiVersionMismatch">
/// If <see langword="true"/> (default), the method will throw a <see cref="StripeException"/> if the
/// API version of the event doesn't match Stripe.net's default API version (see
/// <see cref="StripeConfiguration.ApiVersion"/>).
/// </param>
/// <returns>The deserialized <see cref="Event"/>.</returns>
/// <exception cref="StripeException">
/// Thrown if the signature verification fails for any reason, of if the API version of the
/// event doesn't match Stripe.net's default API version.
/// </exception>
Event PrepareStripeEvent(string json, string stripeSignatureHeader, string secret, bool throwOnApiVersionMismatch);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using OrchardCore.Commerce.MoneyDataType;
using OrchardCore.Commerce.Payment.Stripe.Constants;
using Stripe;
using System.Threading.Tasks;

namespace OrchardCore.Commerce.Payment.Stripe.Abstractions;

/// <summary>
/// Service for managing Stripe Payment Intents.
/// </summary>
public interface IStripePaymentIntentService
{
/// <summary>
/// Gets a PaymentIntent by its Stripe Id.
/// </summary>
/// <returns>Stripe <see cref="PaymentIntent"/> model.</returns>
Task<PaymentIntent> GetPaymentIntentAsync(string paymentIntentId);

/// <summary>
/// Gets the PaymentIntent by its Stripe Id if it is <see cref="PaymentIntentStatuses.Succeeded"/> or
/// <see cref="PaymentIntentStatuses.Processing"/>. Otherwise, updates it with the provided
/// <paramref name="defaultTotal"/>.
/// </summary>
/// <returns>Updated or original Stripe <see cref="PaymentIntent"/> model.</returns>
Task<PaymentIntent> GetOrUpdatePaymentIntentAsync(
string paymentIntentId,
Amount defaultTotal);

/// <summary>
/// Creates a PaymentIntent with the provided <paramref name="total"/>. And adds description and other values to
/// the payment intent. Check the implementation for more details.
/// </summary>
/// <returns>Created Stripe <see cref="PaymentIntent"/>.</returns>
Task<PaymentIntent> CreatePaymentIntentAsync(Amount total);

/// <summary>
/// Creates a PaymentIntent with the provided <paramref name="options"/>.
/// </summary>
/// <returns>Created Stripe <see cref="PaymentIntent"/> model.</returns>
Task<PaymentIntent> CreatePaymentIntentAsync(PaymentIntentCreateOptions options);
}
Loading

0 comments on commit b05b615

Please sign in to comment.