Skip to content

Commit 6411ec2

Browse files
authored
Fix DbConcurrencyException when authorize event and capture event are fired at the same time (#5)
1 parent 1fc0ade commit 6411ec2

File tree

2 files changed

+71
-60
lines changed

2 files changed

+71
-60
lines changed

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

Lines changed: 70 additions & 59 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,22 +127,22 @@ 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-
Framed = ctx.Settings.Framed
142-
143-
},
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+
Framed = ctx.Settings.Framed
144+
145+
},
144146
cancellationToken).ConfigureAwait(false);
145147

146148
paymentFormLink = paymentLink.Url;
@@ -172,66 +174,74 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC
172174
};
173175
}
174176

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

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

189-
// Check if payment has been approved
190-
if (operation != null)
191-
{
192-
var totalAmount = operation.Amount;
198+
Operation latestOperation = payment.Operations.LastOrDefault();
199+
if (latestOperation == null)
200+
{
201+
return CallbackResult.BadRequest();
202+
}
193203

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

232-
private bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment)
242+
private static bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment)
233243
{
234-
if (payment.Variables.Count > 0 &&
244+
if (payment.Variables.Count > 0 &&
235245
payment.Variables.TryGetValue("orderReference", out string orderReference))
236246
{
237247
if (order.GenerateOrderReference() == orderReference)
@@ -432,7 +442,8 @@ private async Task<bool> ValidateChecksumAsync(HttpRequestMessage request, strin
432442
var json = await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
433443
var checkSum = request.Headers.GetValues("Quickpay-Checksum-Sha256").FirstOrDefault();
434444

435-
if (string.IsNullOrEmpty(checkSum)) return false;
445+
if (string.IsNullOrEmpty(checkSum))
446+
return false;
436447

437448
var calculatedChecksum = Checksum(json, privateAccountKey);
438449

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": "13.0.0",
3+
"version": "13.0.1",
44
"assemblyVersion": {
55
"precision": "build"
66
},

0 commit comments

Comments
 (0)