Skip to content

Commit 858b531

Browse files
committed
Merge branch 'release/3.1001.0'
2 parents a004920 + ab3a382 commit 858b531

File tree

13 files changed

+286
-97
lines changed

13 files changed

+286
-97
lines changed

.github/workflows/module-ci.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# v3.800.24
2-
# https://virtocommerce.atlassian.net/browse/VCST-4487
1+
# v3.800.25
2+
# https://virtocommerce.atlassian.net/browse/VCST-4391
33
name: Module CI
44

55
on:
@@ -324,12 +324,13 @@ jobs:
324324
if: ${{ ((github.ref == 'refs/heads/dev') && (github.event_name == 'push')) ||
325325
(github.event_name == 'workflow_dispatch') || ((github.base_ref == 'dev') && (github.event_name == 'pull_request')) }}
326326
needs: 'ci'
327-
uses: VirtoCommerce/.github/.github/workflows/ui-autotests.yml@v3.800.24
327+
uses: VirtoCommerce/.github/.github/workflows/ui-autotests.yml@v3.800.25
328328
with:
329329
installModules: 'false'
330330
installCustomModule: 'true'
331331
customModuleId: ${{ needs.ci.outputs.moduleId }}
332332
customModuleUrl: ${{ needs.ci.outputs.artifactUrl }}
333+
requiredModulesListUrl: ${{ needs.ci.outputs.requiredModulesListUrl }}
333334
runTests: ${{ needs.ci.outputs.run-ui-tests }}
334335
vctestingRepoBranch: 'dev'
335336
frontendZipUrl: 'latest'
@@ -342,7 +343,7 @@ jobs:
342343
if: ${{ ((github.ref == 'refs/heads/dev') && (github.event_name == 'push') && (needs.ci.outputs.run-e2e == 'true')) ||
343344
(github.event_name == 'workflow_dispatch') || (github.base_ref == 'dev') && (github.event_name == 'pull_request') }}
344345
needs: 'ci'
345-
uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.24
346+
uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.25
346347
with:
347348
katalonRepo: 'VirtoCommerce/vc-quality-gate-katalon'
348349
katalonRepoBranch: 'dev'
@@ -363,7 +364,7 @@ jobs:
363364
&& github.event_name == 'push'
364365
&& needs.ci.outputs.deployment-folder-exists == 'true'}}
365366
needs: ci
366-
uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.24
367+
uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.25
367368
with:
368369
releaseSource: module
369370
moduleId: ${{ needs.ci.outputs.moduleId }}

.github/workflows/module-release-hotfix.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# v3.800.24
2-
# https://virtocommerce.atlassian.net/browse/VCST-4487
1+
# v3.800.25
2+
# https://virtocommerce.atlassian.net/browse/VCST-4391
33
name: Release hotfix
44

55
on:
@@ -13,12 +13,12 @@ on:
1313

1414
jobs:
1515
test:
16-
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.24
16+
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.25
1717
secrets:
1818
sonarToken: ${{ secrets.SONAR_TOKEN }}
1919

2020
build:
21-
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.24
21+
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.25
2222
with:
2323
uploadPackage: 'true'
2424
uploadDocker: 'false'
@@ -46,7 +46,7 @@ jobs:
4646
publish-github-release:
4747
needs:
4848
[build, test, get-metadata]
49-
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.24
49+
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.25
5050
with:
5151
fullKey: ${{ needs.build.outputs.packageFullKey }}
5252
changeLog: '${{ needs.get-metadata.outputs.changeLog }}'

.github/workflows/publish-nugets.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# v3.800.24
2-
# https://virtocommerce.atlassian.net/browse/VCST-4487
1+
# v3.800.25
2+
# https://virtocommerce.atlassian.net/browse/VCST-4391
33
name: Publish nuget
44

55
on:
@@ -13,12 +13,12 @@ on:
1313

1414
jobs:
1515
test:
16-
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.24
16+
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.25
1717
secrets:
1818
sonarToken: ${{ secrets.SONAR_TOKEN }}
1919

2020
build:
21-
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.24
21+
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.25
2222
with:
2323
uploadPackage: 'true'
2424
uploadDocker: 'false'
@@ -29,7 +29,7 @@ jobs:
2929
publish-nuget:
3030
needs:
3131
[build, test]
32-
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.24
32+
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.25
3333
with:
3434
fullKey: ${{ needs.build.outputs.packageFullKey }}
3535
forceGithub: false

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# v3.800.24
2-
# https://virtocommerce.atlassian.net/browse/VCST-4487
1+
# v3.800.25
2+
# https://virtocommerce.atlassian.net/browse/VCST-4391
33
name: Release
44

55
on:
66
workflow_dispatch:
77

88
jobs:
99
release:
10-
uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.24
10+
uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.25
1111
secrets:
1212
envPAT: ${{ secrets.REPO_TOKEN }}

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33
<!-- These properties will be shared for all projects -->
44
<PropertyGroup>
5-
<VersionPrefix>3.1000.0</VersionPrefix>
5+
<VersionPrefix>3.1001.0</VersionPrefix>
66
<VersionSuffix>
77
</VersionSuffix>
88
<VersionSuffix Condition=" '$(VersionSuffix)' != '' AND '$(BuildNumber)' != '' ">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Specialized;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Hangfire;
7+
using VirtoCommerce.OrdersModule.Core.Events;
8+
using VirtoCommerce.OrdersModule.Core.Model;
9+
using VirtoCommerce.OrdersModule.Core.Services;
10+
using VirtoCommerce.PaymentModule.Core.Model;
11+
using VirtoCommerce.PaymentModule.Model.Requests;
12+
using VirtoCommerce.Platform.Core.Common;
13+
using VirtoCommerce.Platform.Core.Events;
14+
using VirtoCommerce.StoreModule.Core.Model;
15+
using VirtoCommerce.StoreModule.Core.Services;
16+
17+
namespace VirtoCommerce.OrdersModule.Data.Handlers
18+
{
19+
public class RefundChangedOrderChangedEventHandler : IEventHandler<OrderChangedEvent>
20+
{
21+
private readonly ICustomerOrderService _orderService;
22+
private readonly IStoreService _storeService;
23+
24+
public RefundChangedOrderChangedEventHandler(
25+
ICustomerOrderService orderService,
26+
IStoreService storeService)
27+
{
28+
_orderService = orderService;
29+
_storeService = storeService;
30+
}
31+
32+
public virtual Task Handle(OrderChangedEvent message)
33+
{
34+
var jobArguments = message.ChangedEntries
35+
.Where(x => x.EntryState == EntryState.Modified)
36+
.SelectMany(GetJobArgumentsForChangedEntry)
37+
.ToArray();
38+
39+
if (jobArguments.Length > 0)
40+
{
41+
BackgroundJob.Enqueue(() => ProcessRefundChangesAsync(jobArguments));
42+
}
43+
44+
return Task.CompletedTask;
45+
}
46+
47+
public virtual async Task ProcessRefundChangesAsync(RefundChangedJobArgument[] jobArguments)
48+
{
49+
var orderIds = jobArguments.Select(x => x.CustomerOrderId).Distinct().ToArray();
50+
var ordersByIdDict = (await _orderService.GetAsync(orderIds))
51+
.ToDictionary(x => x.Id)
52+
.WithDefaultValue(null);
53+
54+
var changedOrders = new List<CustomerOrder>();
55+
56+
foreach (var arg in jobArguments)
57+
{
58+
var order = ordersByIdDict[arg.CustomerOrderId];
59+
if (order == null)
60+
{
61+
continue;
62+
}
63+
64+
var payment = order.InPayments?.FirstOrDefault(p => p.Id.EqualsIgnoreCase(arg.PaymentId));
65+
if (payment?.PaymentMethod == null || payment.PaymentMethod is not ISupportRefundFlow)
66+
{
67+
continue;
68+
}
69+
70+
var refund = payment.Refunds?.FirstOrDefault(r => r.Id.EqualsIgnoreCase(arg.RefundId));
71+
if (refund == null || refund.Status == nameof(RefundStatus.Processed))
72+
{
73+
continue;
74+
}
75+
76+
var store = await _storeService.GetByIdAsync(order.StoreId, nameof(StoreResponseGroup.StoreInfo));
77+
78+
var refundRequest = AbstractTypeFactory<RefundPaymentRequest>.TryCreateInstance();
79+
refundRequest.PaymentId = payment.Id;
80+
refundRequest.Payment = payment;
81+
refundRequest.OrderId = order.Id;
82+
refundRequest.Order = order;
83+
refundRequest.StoreId = store?.Id;
84+
refundRequest.Store = store;
85+
refundRequest.AmountToRefund = refund.Amount;
86+
refundRequest.Reason = refund.ReasonCode.ToString();
87+
refundRequest.Notes = refund.ReasonMessage;
88+
refundRequest.OuterId = refund.OuterId;
89+
refundRequest.Parameters = new NameValueCollection
90+
{
91+
{ nameof(refund.TransactionId), refund.TransactionId ?? string.Empty },
92+
{ "IsUpdate", "true" },
93+
};
94+
95+
try
96+
{
97+
var result = await payment.PaymentMethod.RefundProcessPaymentAsync(refundRequest);
98+
if (result.IsSuccess)
99+
{
100+
refund.Status = result.NewRefundStatus.ToString();
101+
}
102+
else
103+
{
104+
refund.Status = nameof(RefundStatus.Rejected);
105+
refund.RejectReasonMessage = result.ErrorMessage;
106+
}
107+
108+
if (!changedOrders.Contains(order))
109+
{
110+
changedOrders.Add(order);
111+
}
112+
}
113+
catch (Exception)
114+
{
115+
// Log error but don't throw -- this is a background job
116+
}
117+
}
118+
119+
if (changedOrders.Count > 0)
120+
{
121+
await _orderService.SaveChangesAsync(changedOrders.ToArray());
122+
}
123+
}
124+
125+
protected virtual RefundChangedJobArgument[] GetJobArgumentsForChangedEntry(GenericChangedEntry<CustomerOrder> changedEntry)
126+
{
127+
var result = new List<RefundChangedJobArgument>();
128+
129+
var newPayments = changedEntry.NewEntry.InPayments ?? new List<PaymentIn>();
130+
var oldPayments = changedEntry.OldEntry.InPayments ?? new List<PaymentIn>();
131+
132+
foreach (var newPayment in newPayments)
133+
{
134+
var oldPayment = oldPayments.FirstOrDefault(p => p.Id.EqualsIgnoreCase(newPayment.Id));
135+
if (oldPayment == null)
136+
{
137+
continue;
138+
}
139+
140+
var newRefunds = newPayment.Refunds ?? new List<Refund>();
141+
var oldRefunds = oldPayment.Refunds ?? new List<Refund>();
142+
143+
foreach (var newRefund in newRefunds)
144+
{
145+
var oldRefund = oldRefunds.FirstOrDefault(r => r.Id.EqualsIgnoreCase(newRefund.Id));
146+
// Skip newly added refunds - they are handled by PaymentFlowService
147+
if (oldRefund == null)
148+
{
149+
continue;
150+
}
151+
152+
if (HasRefundFieldChanges(oldRefund, newRefund))
153+
{
154+
result.Add(new RefundChangedJobArgument
155+
{
156+
CustomerOrderId = changedEntry.NewEntry.Id,
157+
PaymentId = newPayment.Id,
158+
RefundId = newRefund.Id,
159+
});
160+
}
161+
}
162+
}
163+
164+
return result.ToArray();
165+
}
166+
167+
protected virtual bool HasRefundFieldChanges(Refund oldRefund, Refund newRefund)
168+
{
169+
return oldRefund.Amount != newRefund.Amount
170+
|| oldRefund.ReasonCode != newRefund.ReasonCode
171+
|| oldRefund.ReasonMessage != newRefund.ReasonMessage
172+
|| oldRefund.OuterId != newRefund.OuterId;
173+
}
174+
}
175+
176+
public class RefundChangedJobArgument
177+
{
178+
public string CustomerOrderId { get; set; }
179+
public string PaymentId { get; set; }
180+
public string RefundId { get; set; }
181+
}
182+
}

src/VirtoCommerce.OrdersModule.Data/Services/PaymentFlowService.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ protected virtual async Task<RefundOrderPaymentResult> RefundPaymentInternalAsyn
105105
}
106106
catch (Exception ex)
107107
{
108+
// Save refund as Rejected so the document reflects the failure
109+
var rejectedResult = new RefundPaymentRequestResult { IsSuccess = false, ErrorMessage = ex.Message };
110+
try
111+
{
112+
await dbConcurrencyRetryPolicy.ExecuteAsync(async () =>
113+
await SaveResultToRefundDocument(request, rejectedResult));
114+
}
115+
catch
116+
{
117+
// Best-effort: don't mask the original error
118+
}
119+
108120
result.Succeeded = false;
109121
result.ErrorCode = PaymentFlowErrorCodes.PaymentFailed;
110122
result.ErrorMessage = ex.Message;
@@ -373,6 +385,18 @@ protected virtual async Task<CaptureOrderPaymentResult> CapturePaymentInternalAs
373385
}
374386
catch (Exception ex)
375387
{
388+
// Save capture as Rejected so the document reflects the failure
389+
var rejectedResult = new CapturePaymentRequestResult { IsSuccess = false, ErrorMessage = ex.Message };
390+
try
391+
{
392+
await dbConcurrencyRetryPolicy.ExecuteAsync(async () =>
393+
await SaveResultToCaptureDocument(request, rejectedResult));
394+
}
395+
catch
396+
{
397+
// Best-effort: don't mask the original error
398+
}
399+
376400
result.Succeeded = false;
377401
result.ErrorCode = PaymentFlowErrorCodes.PaymentFailed;
378402
result.ErrorMessage = ex.Message;

src/VirtoCommerce.OrdersModule.Web/Content/css/orderModule.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@
200200
.vc-order .order-brand {
201201
border-bottom: 1px solid #eee;
202202
padding: 20px 0;
203-
overflow: hidden;
204203
}
205204

206205
.order-brand .brand-image {

src/VirtoCommerce.OrdersModule.Web/Module.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public void Initialize(IServiceCollection serviceCollection)
9696
serviceCollection.AddTransient<OrderExportImport>();
9797
serviceCollection.AddTransient<AdjustInventoryOrderChangedEventHandler>();
9898
serviceCollection.AddTransient<CancelPaymentOrderChangedEventHandler>();
99+
serviceCollection.AddTransient<RefundChangedOrderChangedEventHandler>();
99100
serviceCollection.AddTransient<LogChangesOrderChangedEventHandler>();
100101
serviceCollection.AddTransient<IndexCustomerOrderChangedEventHandler>();
101102
serviceCollection.AddTransient<IPaymentFlowService, PaymentFlowService>();
@@ -191,6 +192,7 @@ public void PostInitialize(IApplicationBuilder appBuilder)
191192

192193
appBuilder.RegisterEventHandler<OrderChangedEvent, AdjustInventoryOrderChangedEventHandler>();
193194
appBuilder.RegisterEventHandler<OrderChangedEvent, CancelPaymentOrderChangedEventHandler>();
195+
appBuilder.RegisterEventHandler<OrderChangedEvent, RefundChangedOrderChangedEventHandler>();
194196
appBuilder.RegisterEventHandler<OrderChangedEvent, LogChangesOrderChangedEventHandler>();
195197
appBuilder.RegisterEventHandler<OrderChangedEvent, SendNotificationsOrderChangedEventHandler>();
196198
appBuilder.RegisterEventHandler<OrderChangedEvent, IndexCustomerOrderChangedEventHandler>();

src/VirtoCommerce.OrdersModule.Web/Scripts/blades/capture-add.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@ angular.module('virtoCommerce.orderModule')
66
function ($scope, bladeNavigationService, customerOrders) {
77
var blade = $scope.blade;
88

9-
var operationsCount = blade.payment.childrenOperations ? (blade.payment.childrenOperations.length + 1) : 1;
10-
var newTransactionId = `${blade.payment.number}-${operationsCount.toString().padStart(3, '0')}`;
9+
var paymentPrefix = blade.payment.number + '-';
10+
var maxSuffix = 0;
11+
if (blade.payment.childrenOperations) {
12+
blade.payment.childrenOperations.forEach(function (op) {
13+
if (op.transactionId && op.transactionId.indexOf(paymentPrefix) === 0) {
14+
var suffixNum = parseInt(op.transactionId.substring(paymentPrefix.length), 10);
15+
if (!isNaN(suffixNum) && suffixNum > maxSuffix) {
16+
maxSuffix = suffixNum;
17+
}
18+
}
19+
});
20+
}
21+
var newTransactionId = `${blade.payment.number}-${(maxSuffix + 1).toString().padStart(3, '0')}`;
1122

1223
blade.title = 'orders.blades.capture-add.title';
1324
blade.subtitle = newTransactionId;

0 commit comments

Comments
 (0)