Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Specialized;
using System.Threading.Tasks;
using VirtoCommerce.PaymentModule.Model.Requests;

namespace VirtoCommerce.OrdersModule.Core.Services
{
public interface ICustomerOrderPaymentService
{
Task<PostProcessPaymentRequestResult> PostProcessPaymentAsync(NameValueCollection paymentParameters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using FluentValidation.Results;
using VirtoCommerce.PaymentModule.Model.Requests;

namespace VirtoCommerce.OrdersModule.Data.Model;

public class PostProcessPaymentRequestNotValidResult : PostProcessPaymentRequestResult
{
public IList<ValidationFailure> Errors { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using VirtoCommerce.OrdersModule.Core;
using VirtoCommerce.OrdersModule.Core.Model;
using VirtoCommerce.OrdersModule.Core.Model.Search;
using VirtoCommerce.OrdersModule.Core.Services;
using VirtoCommerce.OrdersModule.Data.Model;
using VirtoCommerce.PaymentModule.Model.Requests;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Core.Settings;
using VirtoCommerce.StoreModule.Core.Model;
using VirtoCommerce.StoreModule.Core.Services;

namespace VirtoCommerce.OrdersModule.Data.Services
{
public class CustomerOrderPaymentService(
IStoreService storeService,
ICustomerOrderService customerOrderService,
ICustomerOrderSearchService customerOrderSearchService,
IValidator<CustomerOrder> customerOrderValidator,
ISettingsManager settingsManager)
: ICustomerOrderPaymentService
{
public virtual async Task<PostProcessPaymentRequestResult> PostProcessPaymentAsync(NameValueCollection paymentParameters)
{
var orderId = GetOrderId(paymentParameters);
var paymentMethodCode = GetPaymentMethodCode(paymentParameters);

if (string.IsNullOrEmpty(orderId))
{
throw new InvalidOperationException("The 'orderid' parameter must be passed");
}

//some payment method require customer number to be passed and returned. First search customer order by number
var searchCriteria = AbstractTypeFactory<CustomerOrderSearchCriteria>.TryCreateInstance();
searchCriteria.Number = orderId;
searchCriteria.ResponseGroup = CustomerOrderResponseGroup.Full.ToString();
//if order not found by order number search by order id
var orders = await customerOrderSearchService.SearchAsync(searchCriteria);
var customerOrder = orders.Results.FirstOrDefault() ?? await customerOrderService.GetByIdAsync(orderId, CustomerOrderResponseGroup.Full.ToString());

if (customerOrder == null)
{
throw new InvalidOperationException($"Cannot find order with ID {orderId}");
}

var store = await storeService.GetByIdAsync(customerOrder.StoreId, StoreResponseGroup.StoreInfo.ToString());
if (store == null)
{
throw new InvalidOperationException($"Cannot find store with ID {customerOrder.StoreId}");
}

//Need to use concrete payment method if it code passed otherwise use all order payment methods
foreach (var inPayment in customerOrder.InPayments.Where(x => x.PaymentMethod != null && (string.IsNullOrEmpty(paymentMethodCode) || x.GatewayCode.EqualsIgnoreCase(paymentMethodCode))))
{
//Each payment method must check that these parameters are addressed to it
var result = inPayment.PaymentMethod.ValidatePostProcessRequest(paymentParameters);
if (result.IsSuccess)
{

var request = new PostProcessPaymentRequest
{
OrderId = customerOrder.Id,
Order = customerOrder,
PaymentId = inPayment.Id,
Payment = inPayment,
StoreId = customerOrder.StoreId,
Store = store,
OuterId = result.OuterId,
Parameters = paymentParameters
};
var retVal = inPayment.PaymentMethod.PostProcessPayment(request);
if (retVal != null)
{
var validationResult = await ValidateAsync(customerOrder);
if (!validationResult.IsValid)
{
return new PostProcessPaymentRequestNotValidResult()
{
Errors = validationResult.Errors,
ErrorMessage = string.Join(" ", validationResult.Errors.Select(x => x.ErrorMessage))
};
}
await customerOrderService.SaveChangesAsync(new[] { customerOrder });

// order Number is required
retVal.OrderId = customerOrder.Number;
}
return retVal;
}
}
return new PostProcessPaymentRequestResult { ErrorMessage = "Payment method not found" };
}

protected virtual string GetOrderId(NameValueCollection paymentParameters)
{
return paymentParameters.Get("orderid");
}

protected virtual string GetPaymentMethodCode(NameValueCollection paymentParameters)
{
return paymentParameters.Get("code");
}

protected virtual async Task<ValidationResult> ValidateAsync(CustomerOrder customerOrder)
{
if (await settingsManager.GetValueAsync<bool>(ModuleConstants.Settings.General.CustomerOrderValidation))
{
return await customerOrderValidator.ValidateAsync(customerOrder);
}

return new ValidationResult();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using VirtoCommerce.AssetsModule.Core.Assets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using VirtoCommerce.OrdersModule.Data.Authorization;
using VirtoCommerce.OrdersModule.Data.Caching;
using VirtoCommerce.OrdersModule.Data.Extensions;
using VirtoCommerce.OrdersModule.Data.Model;
using VirtoCommerce.OrdersModule.Web.Model;
using VirtoCommerce.PaymentModule.Core.Model;
using VirtoCommerce.PaymentModule.Data;
Expand Down Expand Up @@ -68,7 +69,8 @@ public class OrderModuleController(
IOptions<HtmlToPdfOptions> htmlToPdfOptions,
IOptions<OutputJsonSerializerSettings> outputJsonSerializerSettings,
IValidator<CustomerOrder> customerOrderValidator,
ISettingsManager settingsManager)
ISettingsManager settingsManager,
ICustomerOrderPaymentService customerOrderPaymentService)
: Controller
{
/// <summary>
Expand Down Expand Up @@ -526,73 +528,52 @@ public async Task<ActionResult<PostProcessPaymentRequestResult>> PostProcessPaym
{
parameters.Add(param.Key, param.Value);
}
var orderId = parameters.Get("orderid");
if (string.IsNullOrEmpty(orderId))
{
throw new InvalidOperationException("the 'orderid' parameter must be passed");
}

//some payment method require customer number to be passed and returned. First search customer order by number
var searchCriteria = AbstractTypeFactory<CustomerOrderSearchCriteria>.TryCreateInstance();
searchCriteria.Number = orderId;
searchCriteria.ResponseGroup = CustomerOrderResponseGroup.Full.ToString();
//if order not found by order number search by order id
var orders = await searchService.SearchAsync(searchCriteria);
var customerOrder = orders.Results.FirstOrDefault() ?? await customerOrderService.GetByIdAsync(orderId, CustomerOrderResponseGroup.Full.ToString());
var result = await customerOrderPaymentService.PostProcessPaymentAsync(parameters);

if (customerOrder == null)
if (result is PostProcessPaymentRequestNotValidResult notValidResult)
{
throw new InvalidOperationException($"Cannot find order with ID {orderId}");
return BadRequest(new
{
Message = notValidResult.ErrorMessage,
notValidResult.Errors
});
}

var store = await storeService.GetByIdAsync(customerOrder.StoreId, StoreResponseGroup.StoreInfo.ToString());
if (store == null)
{
throw new InvalidOperationException($"Cannot find store with ID {customerOrder.StoreId}");
}
return Ok(result);
}

var paymentMethodCode = parameters.Get("code");
/// <summary>
/// Payment callback operation used by external payment services to inform post process payment in our system
/// </summary>
[HttpPost]
[Route("~/api/paymentcallback-raw")]
public async Task<ActionResult<PostProcessPaymentRequestResult>> PostProcessPaymentRaw()
{
var parameters = await PopulatePaymentCallbackParameters();

//Need to use concrete payment method if it code passed otherwise use all order payment methods
foreach (var inPayment in customerOrder.InPayments.Where(x => x.PaymentMethod != null && (string.IsNullOrEmpty(paymentMethodCode) || x.GatewayCode.EqualsIgnoreCase(paymentMethodCode))))
var result = await customerOrderPaymentService.PostProcessPaymentAsync(parameters);

if (result is PostProcessPaymentRequestNotValidResult notValidResult)
{
//Each payment method must check that these parameters are addressed to it
var result = inPayment.PaymentMethod.ValidatePostProcessRequest(parameters);
if (result.IsSuccess)
return BadRequest(new
{
Message = notValidResult.ErrorMessage,
notValidResult.Errors
});
}

var request = new PostProcessPaymentRequest
{
OrderId = customerOrder.Id,
Order = customerOrder,
PaymentId = inPayment.Id,
Payment = inPayment,
StoreId = customerOrder.StoreId,
Store = store,
OuterId = result.OuterId,
Parameters = parameters
};
var retVal = inPayment.PaymentMethod.PostProcessPayment(request);
if (retVal != null)
{
var validationResult = await ValidateAsync(customerOrder);
if (!validationResult.IsValid)
{
return BadRequest(new
{
Message = string.Join(" ", validationResult.Errors.Select(x => x.ErrorMessage)),
validationResult.Errors
});
}
await customerOrderService.SaveChangesAsync(new[] { customerOrder });

// order Number is required
retVal.OrderId = customerOrder.Number;
}
return Ok(retVal);
}
return Ok(result);
}

private async Task<NameValueCollection> PopulatePaymentCallbackParameters()
{
var result = new NameValueCollection();
using (var reader = new System.IO.StreamReader(Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
string requestBody = await reader.ReadToEndAsync();
}
return Ok(new PostProcessPaymentRequestResult { ErrorMessage = "Payment method not found" });
return result;
}

[HttpGet]
Expand Down
1 change: 1 addition & 0 deletions src/VirtoCommerce.OrdersModule.Web/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public void Initialize(IServiceCollection serviceCollection)
serviceCollection.AddTransient<IShipmentSearchService, ShipmentSearchService>();
serviceCollection.AddTransient<ICustomerOrderBuilder, CustomerOrderBuilder>();
serviceCollection.AddTransient<ICustomerOrderTotalsCalculator, DefaultCustomerOrderTotalsCalculator>();
serviceCollection.AddTransient<ICustomerOrderPaymentService, CustomerOrderPaymentService>();
serviceCollection.AddTransient<OrderExportImport>();
serviceCollection.AddTransient<AdjustInventoryOrderChangedEventHandler>();
serviceCollection.AddTransient<CancelPaymentOrderChangedEventHandler>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Moq;
using VirtoCommerce.AssetsModule.Core.Assets;
using VirtoCommerce.CoreModule.Core.Common;
using VirtoCommerce.OrdersModule.Core.Model;
using VirtoCommerce.OrdersModule.Core.Model.Search;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class CustomerOrderServiceImplIntegrationTests
private readonly ICustomerOrderSearchService _customerOrderSearchService;
private readonly Mock<ILogger<PlatformMemoryCache>> _logMock;
private readonly Mock<ILogger<InProcessBus>> _logEventMock;
private readonly Mock<IBlobUrlResolver> _blobUrlResolver;

public CustomerOrderServiceImplIntegrationTests()
{
Expand All @@ -72,6 +74,7 @@ public CustomerOrderServiceImplIntegrationTests()
_changeLogServiceMock = new Mock<IChangeLogService>();
_logMock = new Mock<ILogger<PlatformMemoryCache>>();
_logEventMock = new Mock<ILogger<InProcessBus>>();
_blobUrlResolver = new Mock<IBlobUrlResolver>();
var cachingOptions = new OptionsWrapper<CachingOptions>(new CachingOptions { CacheEnabled = true });
var memoryCache = new MemoryCache(new MemoryCacheOptions()
{
Expand All @@ -97,6 +100,7 @@ public CustomerOrderServiceImplIntegrationTests()
container.AddSingleton(x => _platformMemoryCache);
container.AddSingleton(x => _changeLogServiceMock.Object);
container.AddSingleton(x => _logEventMock.Object);
container.AddSingleton(x => _blobUrlResolver.Object);
container.AddOptions<CrudOptions>();

var serviceProvider = container.BuildServiceProvider();
Expand Down
Loading