Skip to content

Commit 08ec506

Browse files
authored
Fix DbConccurencyException when authorize event and capture event are fired at the same time (#6)
1 parent 3370d75 commit 08ec506

File tree

3 files changed

+70
-58
lines changed

3 files changed

+70
-58
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,4 @@ $RECYCLE.BIN/
479479
##
480480
## Umbraco Commerce specific
481481
##
482+
/src/Umbraco.Commerce.PaymentProviders.Quickpay/packages.lock.json

src/Umbraco.Commerce.PaymentProviders.Quickpay/QuickpayCheckoutPaymentProvider.cs

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
using Newtonsoft.Json;
21
using System;
32
using System.Collections.Generic;
43
using System.IO;
54
using System.Linq;
65
using System.Net.Http;
76
using System.Security.Cryptography;
87
using System.Text;
8+
using System.Threading;
99
using System.Threading.Tasks;
10+
using Newtonsoft.Json;
1011
using Umbraco.Commerce.Common.Logging;
11-
using Umbraco.Commerce.PaymentProviders.Quickpay.Api;
12-
using Umbraco.Commerce.PaymentProviders.Quickpay.Api.Models;
1312
using Umbraco.Commerce.Core.Api;
1413
using Umbraco.Commerce.Core.Models;
1514
using Umbraco.Commerce.Core.PaymentProviders;
1615
using Umbraco.Commerce.Extensions;
17-
using System.Threading;
16+
using Umbraco.Commerce.PaymentProviders.Quickpay.Api;
17+
using Umbraco.Commerce.PaymentProviders.Quickpay.Api.Models;
1818

1919
namespace Umbraco.Commerce.PaymentProviders.Quickpay
2020
{
2121
[PaymentProvider("quickpay-v10-checkout", "Quickpay V10", "Quickpay V10 payment provider for one time payments")]
2222
public class QuickpayCheckoutPaymentProvider : QuickpayPaymentProviderBase<QuickpayCheckoutPaymentProvider, QuickpayCheckoutSettings>
2323
{
24+
private const string QuickpayStatusCodeApproved = "20000";
25+
2426
public QuickpayCheckoutPaymentProvider(UmbracoCommerceContext ctx, ILogger<QuickpayCheckoutPaymentProvider> logger)
2527
: base(ctx, logger)
2628
{ }
@@ -74,7 +76,7 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC
7476

7577
var clientConfig = GetQuickpayClientConfig(ctx.Settings);
7678
var client = new QuickpayClient(clientConfig);
77-
79+
7880
var reference = ctx.Order.OrderNumber;
7981

8082
// Quickpay has a limit of order id between 4-20 characters.
@@ -125,20 +127,20 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC
125127
Variables = metaData
126128
},
127129
cancellationToken).ConfigureAwait(false);
128-
130+
129131
quickPayPaymentId = GetTransactionId(payment);
130132

131133
var paymentLink = await client.CreatePaymentLinkAsync(payment.Id.ToString(), new QuickpayPaymentLinkRequest
132-
{
133-
Amount = orderAmount,
134-
Language = lang.ToString(),
135-
ContinueUrl = ctx.Urls.ContinueUrl,
136-
CancelUrl = ctx.Urls.CancelUrl,
137-
CallbackUrl = ctx.Urls.CallbackUrl,
138-
PaymentMethods = paymentMethods?.Length > 0 ? string.Join(",", paymentMethods) : null,
139-
AutoFee = ctx.Settings.AutoFee,
140-
AutoCapture = ctx.Settings.AutoCapture
141-
},
134+
{
135+
Amount = orderAmount,
136+
Language = lang.ToString(),
137+
ContinueUrl = ctx.Urls.ContinueUrl,
138+
CancelUrl = ctx.Urls.CancelUrl,
139+
CallbackUrl = ctx.Urls.CallbackUrl,
140+
PaymentMethods = paymentMethods?.Length > 0 ? string.Join(",", paymentMethods) : null,
141+
AutoFee = ctx.Settings.AutoFee,
142+
AutoCapture = ctx.Settings.AutoCapture
143+
},
142144
cancellationToken).ConfigureAwait(false);
143145

144146
paymentFormLink = paymentLink.Url;
@@ -170,66 +172,74 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC
170172
};
171173
}
172174

173-
public override async Task<CallbackResult> ProcessCallbackAsync(PaymentProviderContext<QuickpayCheckoutSettings> ctx, CancellationToken cancellationToken = default)
175+
public override async Task<CallbackResult> ProcessCallbackAsync(PaymentProviderContext<QuickpayCheckoutSettings> context, CancellationToken cancellationToken = default)
174176
{
175177
try
176178
{
177-
if (await ValidateChecksumAsync(ctx.Request, ctx.Settings.PrivateKey, cancellationToken).ConfigureAwait(false))
179+
ArgumentNullException.ThrowIfNull(context);
180+
if (!await ValidateChecksumAsync(context.Request, context.Settings.PrivateKey, cancellationToken).ConfigureAwait(false))
178181
{
179-
var payment = await ParseCallbackAsync(ctx.Request,
182+
Logger.Warn($"Quickpay [{context.Order.OrderNumber}] - Checksum validation failed");
183+
return CallbackResult.BadRequest();
184+
}
185+
186+
QuickpayPayment payment = await ParseCallbackAsync(
187+
context.Request,
180188
cancellationToken).ConfigureAwait(false);
181189

182-
if (VerifyOrder(ctx.Order, payment))
183-
{
184-
// Get operations to check if payment has been approved
185-
var operation = payment.Operations.LastOrDefault();
190+
if (!VerifyOrder(context.Order, payment))
191+
{
192+
Logger.Warn($"Quickpay [{context.Order.OrderNumber}] - Couldn't verify the order");
193+
return CallbackResult.Empty;
194+
}
186195

187-
// Check if payment has been approved
188-
if (operation != null)
189-
{
190-
var totalAmount = operation.Amount;
196+
Operation latestOperation = payment.Operations.LastOrDefault();
197+
if (latestOperation == null)
198+
{
199+
return CallbackResult.BadRequest();
200+
}
191201

192-
if (operation.QuickpayStatusCode == "20000" || operation.AcquirerStatusCode == "000")
193-
{
194-
var paymentStatus = GetPaymentStatus(operation);
195-
196-
return new CallbackResult
197-
{
198-
TransactionInfo = new TransactionInfo
199-
{
200-
AmountAuthorized = AmountFromMinorUnits(totalAmount),
201-
TransactionId = GetTransactionId(payment),
202-
PaymentStatus = paymentStatus
203-
}
204-
};
205-
}
206-
else
207-
{
208-
Logger.Warn($"Quickpay [{ctx.Order.OrderNumber}] - Payment not approved. Quickpay status code: {operation.QuickpayStatusCode} ({operation.QuickpayStatusMessage}). Acquirer status code: {operation.AcquirerStatusCode} ({operation.AcquirerStatusMessage}).");
209-
}
210-
}
202+
if (latestOperation.QuickpayStatusCode == QuickpayStatusCodeApproved || latestOperation.AcquirerStatusCode == "000")
203+
{
204+
PaymentStatus? currentPaymentStatus = context.Order?.TransactionInfo?.PaymentStatus;
205+
PaymentStatus newPaymentStatus = GetPaymentStatus(latestOperation);
206+
207+
Logger.Debug($"ProcessCallbackAsync - current payment status: {currentPaymentStatus}, new payment status: {newPaymentStatus}, operations: {System.Text.Json.JsonSerializer.Serialize(payment.Operations)}.");
208+
if (newPaymentStatus == PaymentStatus.Authorized && currentPaymentStatus != PaymentStatus.Initialized)
209+
{
210+
return CallbackResult.Empty;
211211
}
212-
else
212+
213+
if (newPaymentStatus == PaymentStatus.Captured && currentPaymentStatus != PaymentStatus.Authorized)
213214
{
214-
Logger.Warn($"Quickpay [{ctx.Order.OrderNumber}] - Couldn't verify the order");
215+
return CallbackResult.BadRequest();
215216
}
217+
218+
int totalAmount = latestOperation.Amount;
219+
return new CallbackResult
220+
{
221+
TransactionInfo = new TransactionInfo
222+
{
223+
AmountAuthorized = AmountFromMinorUnits(totalAmount),
224+
TransactionId = GetTransactionId(payment),
225+
PaymentStatus = newPaymentStatus,
226+
},
227+
};
216228
}
217-
else
218-
{
219-
Logger.Warn($"Quickpay [{ctx.Order.OrderNumber}] - Checksum validation failed");
220-
}
229+
230+
Logger.Warn($"Quickpay [{context.Order.OrderNumber}] - Payment not approved. Quickpay status code: {latestOperation.QuickpayStatusCode} ({latestOperation.QuickpayStatusMessage}). Acquirer status code: {latestOperation.AcquirerStatusCode} ({latestOperation.AcquirerStatusMessage}).");
231+
return CallbackResult.Empty;
221232
}
222233
catch (Exception ex)
223234
{
224235
Logger.Error(ex, "Quickpay - ProcessCallback");
236+
return CallbackResult.BadRequest();
225237
}
226-
227-
return CallbackResult.Empty;
228238
}
229239

230-
private bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment)
240+
private static bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment)
231241
{
232-
if (payment.Variables.Count > 0 &&
242+
if (payment.Variables.Count > 0 &&
233243
payment.Variables.TryGetValue("orderReference", out string orderReference))
234244
{
235245
if (order.GenerateOrderReference() == orderReference)
@@ -430,7 +440,8 @@ private async Task<bool> ValidateChecksumAsync(HttpRequestMessage request, strin
430440
var json = await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
431441
var checkSum = request.Headers.GetValues("Quickpay-Checksum-Sha256").FirstOrDefault();
432442

433-
if (string.IsNullOrEmpty(checkSum)) return false;
443+
if (string.IsNullOrEmpty(checkSum))
444+
return false;
434445

435446
var calculatedChecksum = Checksum(json, privateAccountKey);
436447

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "12.0.0",
3+
"version": "12.0.1",
44
"assemblyVersion": {
55
"precision": "build"
66
},

0 commit comments

Comments
 (0)