|
1 | | -using Newtonsoft.Json; |
2 | 1 | using System; |
3 | 2 | using System.Collections.Generic; |
4 | 3 | using System.IO; |
5 | 4 | using System.Linq; |
6 | 5 | using System.Net.Http; |
7 | 6 | using System.Security.Cryptography; |
8 | 7 | using System.Text; |
| 8 | +using System.Threading; |
9 | 9 | using System.Threading.Tasks; |
| 10 | +using Newtonsoft.Json; |
10 | 11 | using Umbraco.Commerce.Common.Logging; |
11 | | -using Umbraco.Commerce.PaymentProviders.Quickpay.Api; |
12 | | -using Umbraco.Commerce.PaymentProviders.Quickpay.Api.Models; |
13 | 12 | using Umbraco.Commerce.Core.Api; |
14 | 13 | using Umbraco.Commerce.Core.Models; |
15 | 14 | using Umbraco.Commerce.Core.PaymentProviders; |
16 | 15 | using Umbraco.Commerce.Extensions; |
17 | | -using System.Threading; |
| 16 | +using Umbraco.Commerce.PaymentProviders.Quickpay.Api; |
| 17 | +using Umbraco.Commerce.PaymentProviders.Quickpay.Api.Models; |
18 | 18 |
|
19 | 19 | namespace Umbraco.Commerce.PaymentProviders.Quickpay |
20 | 20 | { |
21 | 21 | [PaymentProvider("quickpay-v10-checkout", "Quickpay V10", "Quickpay V10 payment provider for one time payments")] |
22 | 22 | public class QuickpayCheckoutPaymentProvider : QuickpayPaymentProviderBase<QuickpayCheckoutPaymentProvider, QuickpayCheckoutSettings> |
23 | 23 | { |
| 24 | + private const string QuickpayStatusCodeApproved = "20000"; |
| 25 | + |
24 | 26 | public QuickpayCheckoutPaymentProvider(UmbracoCommerceContext ctx, ILogger<QuickpayCheckoutPaymentProvider> logger) |
25 | 27 | : base(ctx, logger) |
26 | 28 | { } |
@@ -74,7 +76,7 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC |
74 | 76 |
|
75 | 77 | var clientConfig = GetQuickpayClientConfig(ctx.Settings); |
76 | 78 | var client = new QuickpayClient(clientConfig); |
77 | | - |
| 79 | + |
78 | 80 | var reference = ctx.Order.OrderNumber; |
79 | 81 |
|
80 | 82 | // Quickpay has a limit of order id between 4-20 characters. |
@@ -125,20 +127,20 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC |
125 | 127 | Variables = metaData |
126 | 128 | }, |
127 | 129 | cancellationToken).ConfigureAwait(false); |
128 | | - |
| 130 | + |
129 | 131 | quickPayPaymentId = GetTransactionId(payment); |
130 | 132 |
|
131 | 133 | 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 | + }, |
142 | 144 | cancellationToken).ConfigureAwait(false); |
143 | 145 |
|
144 | 146 | paymentFormLink = paymentLink.Url; |
@@ -170,66 +172,74 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC |
170 | 172 | }; |
171 | 173 | } |
172 | 174 |
|
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) |
174 | 176 | { |
175 | 177 | try |
176 | 178 | { |
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)) |
178 | 181 | { |
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, |
180 | 188 | cancellationToken).ConfigureAwait(false); |
181 | 189 |
|
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 | + } |
186 | 195 |
|
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 | + } |
191 | 201 |
|
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; |
211 | 211 | } |
212 | | - else |
| 212 | + |
| 213 | + if (newPaymentStatus == PaymentStatus.Captured && currentPaymentStatus != PaymentStatus.Authorized) |
213 | 214 | { |
214 | | - Logger.Warn($"Quickpay [{ctx.Order.OrderNumber}] - Couldn't verify the order"); |
| 215 | + return CallbackResult.BadRequest(); |
215 | 216 | } |
| 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 | + }; |
216 | 228 | } |
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; |
221 | 232 | } |
222 | 233 | catch (Exception ex) |
223 | 234 | { |
224 | 235 | Logger.Error(ex, "Quickpay - ProcessCallback"); |
| 236 | + return CallbackResult.BadRequest(); |
225 | 237 | } |
226 | | - |
227 | | - return CallbackResult.Empty; |
228 | 238 | } |
229 | 239 |
|
230 | | - private bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment) |
| 240 | + private static bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment) |
231 | 241 | { |
232 | | - if (payment.Variables.Count > 0 && |
| 242 | + if (payment.Variables.Count > 0 && |
233 | 243 | payment.Variables.TryGetValue("orderReference", out string orderReference)) |
234 | 244 | { |
235 | 245 | if (order.GenerateOrderReference() == orderReference) |
@@ -430,7 +440,8 @@ private async Task<bool> ValidateChecksumAsync(HttpRequestMessage request, strin |
430 | 440 | var json = await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); |
431 | 441 | var checkSum = request.Headers.GetValues("Quickpay-Checksum-Sha256").FirstOrDefault(); |
432 | 442 |
|
433 | | - if (string.IsNullOrEmpty(checkSum)) return false; |
| 443 | + if (string.IsNullOrEmpty(checkSum)) |
| 444 | + return false; |
434 | 445 |
|
435 | 446 | var calculatedChecksum = Checksum(json, privateAccountKey); |
436 | 447 |
|
|
0 commit comments