Skip to content

Commit

Permalink
Multi-store (#5)
Browse files Browse the repository at this point in the history
* Implemented `XummSdk` to support multi-store
  • Loading branch information
DominiqueBlomsma authored Nov 22, 2022
1 parent 9966e4f commit 59dd0dc
Show file tree
Hide file tree
Showing 20 changed files with 270 additions and 253 deletions.
110 changes: 39 additions & 71 deletions src/Controllers/XummConfigurationController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Nop.Core;
using Nop.Plugin.Payments.Xumm.Extensions;
Expand All @@ -11,8 +13,6 @@
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;
using System.Globalization;
using System.Threading.Tasks;
using XUMM.NET.SDK.Extensions;

namespace Nop.Plugin.Payments.Xumm.Controllers;
Expand Down Expand Up @@ -66,32 +66,34 @@ public async Task<IActionResult> Configure()
var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync();
var settings = await _settingService.LoadSettingAsync<XummPaymentSettings>(storeScope);

var pong = await _xummService.GetPongAsync();
var pong = await _xummService.GetPongAsync(storeScope);
var model = new ConfigurationModel
{
ActiveStoreScopeConfiguration = storeScope,
AdditionalFee = settings.AdditionalFee,
AdditionalFeePercentage = settings.AdditionalFeePercentage,
ApiKey = settings.ApiKey,
ApiSecret = settings.ApiSecret,
WebhookUrl = _xummService.WebhookUrl,
XrplAddress = settings.XrplAddress,
XrplPaymentDestinationTag = settings.XrplPaymentDestinationTag?.ToString(),
XrplRefundDestinationTag = settings.XrplRefundDestinationTag?.ToString(),
XrplCurrency = IssuerCurrencyExtensions.GetCurrencyIdentifier(settings.XrplIssuer, settings.XrplCurrency),
XrplCurrencyAndIssuer = IssuerCurrencyExtensions.GetCurrencyIdentifier(settings.XrplIssuer, settings.XrplCurrency),
ValidXrplAddress = settings.XrplAddress.IsAccountAddress(),
ValidApiCredentials = pong?.Pong ?? false,
HasWebhookUrlConfigured = _xummService.HasWebhookUrlConfigured(pong)
WebhookUrl = await _xummService.GetWebhookUrlAsync(storeScope),
HasWebhookUrlConfigured = await _xummService.HasWebhookUrlConfiguredAsync(storeScope, pong)
};

if (storeScope > 0)
{
model.AdditionalFee_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.AdditionalFee, storeScope);
model.AdditionalFeePercentage_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.AdditionalFeePercentage, storeScope);
model.ApiKey_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.ApiKey, storeScope);
model.ApiSecret_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.ApiSecret, storeScope);
model.XrplAddress_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.XrplAddress, storeScope);
model.XrplPaymentDestinationTag_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.XrplPaymentDestinationTag, storeScope);
model.XrplRefundDestinationTag_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.XrplRefundDestinationTag, storeScope);
model.XrplCurrency_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.XrplCurrency, storeScope);
model.XrplCurrencyAndIssuer_OverrideForStore = await _settingService.SettingExistsAsync(settings, x => x.XrplCurrency, storeScope);
}

if (model.ValidApiCredentials)
Expand All @@ -103,7 +105,7 @@ public async Task<IActionResult> Configure()

if (model.ValidXrplAddress)
{
var issuers = await _xummService.GetOrderedCurrenciesAsync(settings.XrplAddress);
var issuers = await _xummService.GetOrderedCurrenciesAsync(storeScope, settings.XrplAddress);

foreach (var issuer in issuers)
{
Expand All @@ -114,7 +116,7 @@ public async Task<IActionResult> Configure()

foreach (var currency in issuer.Currencies)
{
var isSelected = currency.Identifier == model.XrplCurrency;
var isSelected = currency.Identifier == model.XrplCurrencyAndIssuer;
var listItem = new SelectListItem
{
Text = currency.CurrencyCodeFormatted,
Expand All @@ -131,7 +133,7 @@ public async Task<IActionResult> Configure()
model.TrustSetRequired = true;
}

if (!await _xummService.IsPrimaryStoreCurrency(currency.CurrencyCodeFormatted))
if (!await _xummService.IsPrimaryStoreCurrency(storeScope, currency.CurrencyCodeFormatted))
{
_notificationService.WarningNotification(string.Format(await _localizationService.GetResourceAsync("Plugins.Payments.Xumm.Fields.XrplCurrency.MissingPrimaryStoreCurrency"), currency.CurrencyCodeFormatted));
model.ShopCurrencyRequired = true;
Expand All @@ -151,7 +153,7 @@ public async Task<IActionResult> Configure()
return View("~/Plugins/Payments.Xumm/Views/Configure.cshtml", model);
}

public async Task<IActionResult> ProcessPayloadAsync(int storeScope, string customIdentifier)
public async Task<IActionResult> ProcessPayloadAsync(int storeId, string customIdentifier)
{
if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManagePaymentMethods))
{
Expand All @@ -160,7 +162,7 @@ public async Task<IActionResult> ProcessPayloadAsync(int storeScope, string cust

if (!string.IsNullOrWhiteSpace(customIdentifier))
{
await _xummService.ProcessPayloadAsync(storeScope, customIdentifier);
await _xummService.ProcessPayloadAsync(storeId, customIdentifier);
}

return RedirectToAction("Configure");
Expand Down Expand Up @@ -198,84 +200,50 @@ public async Task<IActionResult> Configure(ConfigurationModel model)
var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync();
var settings = await _settingService.LoadSettingAsync<XummPaymentSettings>(storeScope);

var restartRequired = model.ApiKey != settings.ApiKey || model.ApiSecret != settings.ApiSecret;
if (restartRequired)
{
// API Credentials are configured during startup so we need to restart after changed values.
settings.ApiKey = model.ApiKey;
settings.ApiSecret = model.ApiSecret;
settings.ApiKey = model.ApiKey;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.ApiKey, model.ApiKey_OverrideForStore, storeScope, clearCache: false);

await _settingService.SaveSettingAsync(settings, setting => setting.ApiKey, clearCache: false);
await _settingService.SaveSettingAsync(settings, setting => setting.ApiSecret, clearCache: false);
settings.ApiSecret = model.ApiSecret;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.ApiSecret, model.ApiSecret_OverrideForStore, storeScope, clearCache: false);

return View("~/Areas/Admin/Views/Shared/RestartApplication.cshtml", Url.Action("Configure", "XummConfiguration"));
}
settings.AdditionalFee = model.AdditionalFee;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.AdditionalFee, model.AdditionalFee_OverrideForStore, storeScope, clearCache: false);

if (settings.AdditionalFee != model.AdditionalFee || settings.AdditionalFeePercentage != model.AdditionalFeePercentage)
{
settings.AdditionalFee = model.AdditionalFee;
settings.AdditionalFeePercentage = model.AdditionalFeePercentage;
settings.AdditionalFeePercentage = model.AdditionalFeePercentage;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.AdditionalFeePercentage, model.AdditionalFeePercentage_OverrideForStore, storeScope, clearCache: false);

await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.AdditionalFee, model.AdditionalFee_OverrideForStore, storeScope, clearCache: false);
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.AdditionalFeePercentage, model.AdditionalFeePercentage_OverrideForStore, storeScope, clearCache: false);
}

var xrpAddressChanged = settings.XrplAddress != model.XrplAddress;
if (xrpAddressChanged)
{
settings.XrplAddress = model.XrplAddress;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplAddress, model.XrplAddress_OverrideForStore, storeScope, false);
}
settings.XrplAddress = model.XrplAddress;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplAddress, model.XrplAddress_OverrideForStore, storeScope, clearCache: false);

uint? paymentDestinationTag = null;
if (uint.TryParse(model.XrplPaymentDestinationTag, NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var parsedPaymentDestinationTag))
if ((storeScope == 0 || model.XrplPaymentDestinationTag_OverrideForStore) &&
uint.TryParse(model.XrplPaymentDestinationTag, NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var parsedPaymentDestinationTag))
{
paymentDestinationTag = parsedPaymentDestinationTag;
}

if (settings.XrplPaymentDestinationTag != paymentDestinationTag)
{
settings.XrplPaymentDestinationTag = paymentDestinationTag;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplPaymentDestinationTag, model.XrplPaymentDestinationTag_OverrideForStore, storeScope, false);
}
settings.XrplPaymentDestinationTag = paymentDestinationTag;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplPaymentDestinationTag, model.XrplPaymentDestinationTag_OverrideForStore, storeScope, clearCache: false);

uint? refundDestinationTag = null;
if (uint.TryParse(model.XrplRefundDestinationTag, NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var parsedRefundDestinationTag))
if ((storeScope == 0 || model.XrplRefundDestinationTag_OverrideForStore) &&
uint.TryParse(model.XrplRefundDestinationTag, NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var parsedRefundDestinationTag))
{
refundDestinationTag = parsedRefundDestinationTag;
}

if (settings.XrplRefundDestinationTag != refundDestinationTag)
{
settings.XrplRefundDestinationTag = refundDestinationTag;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplRefundDestinationTag, model.XrplRefundDestinationTag_OverrideForStore, storeScope, false);
}

var (issuer, currency) = IssuerCurrencyExtensions.GetIssuerAndCurrency(model.XrplCurrency);
var currencyChanged = (issuer != null && issuer != settings.XrplIssuer) || (currency != null && currency != settings.XrplCurrency);
var disabledCurrencyOverride = issuer == null && currency == null && storeScope > 0;
if (currencyChanged || disabledCurrencyOverride)
{
// Issuer and Currency will only be saved after a trust line has been set to prevent payment failures.
if (!disabledCurrencyOverride && await _xummService.IsTrustLineRequiredAsync(settings.XrplAddress, issuer, currency))
{
// Issuer and Currency will be saved after signing the TrustSet payload.
var redirectUrl = await _xummService.GetSetTrustLineUrlAsync(storeScope, settings.XrplAddress, issuer, currency);
return Redirect(redirectUrl);
}
settings.XrplRefundDestinationTag = refundDestinationTag;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplRefundDestinationTag, model.XrplRefundDestinationTag_OverrideForStore, storeScope, clearCache: false);

settings.XrplIssuer = issuer;
settings.XrplCurrency = currency;
var (issuer, currency) = storeScope == 0 || model.XrplCurrencyAndIssuer_OverrideForStore ? IssuerCurrencyExtensions.GetIssuerAndCurrency(model.XrplCurrencyAndIssuer) : (null, null);
settings.XrplIssuer = issuer;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplIssuer, model.XrplCurrencyAndIssuer_OverrideForStore, storeScope, clearCache: false);

await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplIssuer, model.XrplCurrency_OverrideForStore, storeScope, clearCache: false);
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplCurrency, model.XrplCurrency_OverrideForStore, storeScope, clearCache: false);
}
else if (xrpAddressChanged)
{
await _xummService.SetFallBackForMissingTrustLineAsync(settings, storeScope);
}
settings.XrplCurrency = currency;
await _settingService.SaveSettingOverridablePerStoreAsync(settings, setting => setting.XrplCurrency, model.XrplCurrencyAndIssuer_OverrideForStore, storeScope, clearCache: false);

await _settingService.ClearCacheAsync();

_notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Plugins.Saved"));
return RedirectToAction("Configure");
}
Expand Down
1 change: 0 additions & 1 deletion src/Controllers/XummController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Nop.Core.Domain.Payments;
using Nop.Plugin.Payments.Xumm.Services;
using Nop.Services.Logging;
using Nop.Services.Payments;
using Nop.Services.Security;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
Expand Down
9 changes: 2 additions & 7 deletions src/Extensions/IssuerCurrencyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ internal static class IssuerCurrencyExtensions
{
internal static string GetFormattedCurrency(this string currency)
{
if (currency == XummDefaults.XRPL.XRP)
{
return currency;
}

return currency.ToFormattedCurrency();
return currency == XummDefaults.XRPL.XRP ? currency : currency.ToFormattedCurrency();
}

internal static string GetCurrencyIdentifier(string issuer, string currency)
Expand All @@ -35,7 +30,7 @@ internal static (string issuer, string currency) GetIssuerAndCurrency(string ide
var split = identifier.Split('-', StringSplitOptions.RemoveEmptyEntries);
if (split.Length != 2)
{
throw new ArgumentException("Identifier {identifier} doesn't contain a valid account and currency code.");
throw new ArgumentException($"Identifier '{identifier}' doesn't contain a valid account and currency code.");
}

return (split[0], split[1]);
Expand Down
9 changes: 7 additions & 2 deletions src/Extensions/OrderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ internal static class OrderExtensions
{
internal static string GetCustomIdentifier(this Order order, XummPayloadType payloadType, int count)
{
return $"{order.OrderGuid.ToString().Replace("-", string.Empty)}-{(int)payloadType}-{count}";
return $"{order.OrderGuid:N}-{(int)payloadType}-{count}";
}

internal static (Guid orderGuid, XummPayloadType payloadType, int count) ParseCustomIdentifier(string customIdentifier)
{
var countIndex = customIdentifier?.LastIndexOf('-') ?? -1;
if (customIdentifier == null)
{
return (default, default, default);
}

var countIndex = customIdentifier.LastIndexOf('-');
var typeIndex = countIndex != -1 ? customIdentifier.LastIndexOf('-', countIndex - 1) : -1;
if (countIndex == -1 || typeIndex == -1)
{
Expand Down
17 changes: 0 additions & 17 deletions src/Infrastructure/NopStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using Nop.Core.Infrastructure;
using Nop.Plugin.Payments.Xumm.Services;
using Nop.Plugin.Payments.Xumm.WebSocket;
using XUMM.NET.SDK;
using XUMM.NET.SDK.Extensions;
using XUMM.NET.SDK.Webhooks;

namespace Nop.Plugin.Payments.Xumm.Infrastructure;
Expand All @@ -27,21 +25,6 @@ public void ConfigureServices(IServiceCollection services, IConfiguration config
services.AddScoped<IXummOrderService, XummOrderService>();
services.AddScoped<IXummMailService, XummMailService>();

var paymentSettings = services.BuildServiceProvider().GetRequiredService<XummPaymentSettings>();

services.AddXummNet(o =>
{
if (paymentSettings.ApiKey.IsValidUuid())
{
o.ApiKey = paymentSettings.ApiKey;
}
if (paymentSettings.ApiSecret.IsValidUuid())
{
o.ApiSecret = paymentSettings.ApiSecret;
}
});

services.AddXummWebhooks<XummWebhookProcessor>();
}

Expand Down
4 changes: 2 additions & 2 deletions src/Infrastructure/RouteProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
area = AreaNames.Admin
});

endpointRouteBuilder.MapControllerRoute(XummDefaults.ProcessPayloadRouteName, "Plugins/Xumm/ProcessPayload/{storeScope}/{customIdentifier}",
endpointRouteBuilder.MapControllerRoute(XummDefaults.ProcessPayloadRouteName, "Plugins/Xumm/ProcessPayload/{storeId}/{customIdentifier}",
new
{
controller = "XummConfiguration",
Expand Down Expand Up @@ -54,7 +54,7 @@ public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
action = "ProcessRefund"
});

endpointRouteBuilder.MapXummControllerRoute(XummDefaults.WebHooks.RouteName, "Plugins/Xumm/Webhook");
endpointRouteBuilder.MapXummControllerRoute(XummDefaults.WebHooks.RouteName, XummDefaults.WebHooks.Pattern);
}

/// <summary>
Expand Down
16 changes: 11 additions & 5 deletions src/Models/ConfigurationModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
using Nop.Web.Framework.Models;
using Nop.Web.Framework.Mvc.ModelBinding;
using System.Collections.Generic;

namespace Nop.Plugin.Payments.Xumm.Models;

Expand All @@ -20,12 +20,18 @@ public record ConfigurationModel : BaseNopModel
[NopResourceDisplayName("Plugins.Payments.Xumm.Fields.ApiKey")]
public string ApiKey { get; set; }

public bool ApiKey_OverrideForStore { get; set; }

/// <summary>
/// Gets or sets an API Secret
/// </summary>
[NopResourceDisplayName("Plugins.Payments.Xumm.Fields.ApiSecret")]
public string ApiSecret { get; set; }

public bool ApiSecret_OverrideForStore { get; set; }

public bool ApiCredentialsProvided => !string.IsNullOrWhiteSpace(ApiKey) && !string.IsNullOrWhiteSpace(ApiSecret);

public bool ValidApiCredentials { get; set; }

/// <summary>
Expand Down Expand Up @@ -66,14 +72,14 @@ public record ConfigurationModel : BaseNopModel
/// Gets or sets an XRPL Currency
/// </summary>
[NopResourceDisplayName("Plugins.Payments.Xumm.Fields.XrplCurrency")]
public string XrplCurrency { get; set; }
public string XrplCurrencyAndIssuer { get; set; }

public bool XrplCurrency_OverrideForStore { get; set; }
public bool XrplCurrencyAndIssuer_OverrideForStore { get; set; }

public IList<SelectListItem> XrplCurrencies { get; set; } = new List<SelectListItem>();

public bool TrustSetRequired { get; set; }

public bool ShopCurrencyRequired { get; set; }

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Nop.Plugin.Payments.Xumm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="XUMM.NET.SDK" Version="1.6.6" />
<PackageReference Include="XUMM.NET.SDK.Webhooks" Version="1.6.6" />
<PackageReference Include="XUMM.NET.SDK" Version="1.7.1" />
<PackageReference Include="XUMM.NET.SDK.Webhooks" Version="1.7.1" />
</ItemGroup>

<!-- This target execute after "Build" target -->
Expand Down
2 changes: 1 addition & 1 deletion src/Services/AsyncLock/AsyncLockRefCount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ internal AsyncLockRefCount(T value)
}

public int RefCount { get; set; }
public T Value { get; private set; }
public T Value { get; }
}
Loading

0 comments on commit 59dd0dc

Please sign in to comment.