|
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,22 +127,22 @@ 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 | | - 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 | + }, |
144 | 146 | cancellationToken).ConfigureAwait(false); |
145 | 147 |
|
146 | 148 | paymentFormLink = paymentLink.Url; |
@@ -172,66 +174,74 @@ public override async Task<PaymentFormResult> GenerateFormAsync(PaymentProviderC |
172 | 174 | }; |
173 | 175 | } |
174 | 176 |
|
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) |
176 | 178 | { |
177 | 179 | try |
178 | 180 | { |
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)) |
180 | 183 | { |
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, |
182 | 190 | cancellationToken).ConfigureAwait(false); |
183 | 191 |
|
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 | + } |
188 | 197 |
|
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 | + } |
193 | 203 |
|
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; |
213 | 213 | } |
214 | | - else |
| 214 | + |
| 215 | + if (newPaymentStatus == PaymentStatus.Captured && currentPaymentStatus != PaymentStatus.Authorized) |
215 | 216 | { |
216 | | - Logger.Warn($"Quickpay [{ctx.Order.OrderNumber}] - Couldn't verify the order"); |
| 217 | + return CallbackResult.BadRequest(); |
217 | 218 | } |
| 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 | + }; |
218 | 230 | } |
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; |
223 | 234 | } |
224 | 235 | catch (Exception ex) |
225 | 236 | { |
226 | 237 | Logger.Error(ex, "Quickpay - ProcessCallback"); |
| 238 | + return CallbackResult.BadRequest(); |
227 | 239 | } |
228 | | - |
229 | | - return CallbackResult.Empty; |
230 | 240 | } |
231 | 241 |
|
232 | | - private bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment) |
| 242 | + private static bool VerifyOrder(OrderReadOnly order, QuickpayPayment payment) |
233 | 243 | { |
234 | | - if (payment.Variables.Count > 0 && |
| 244 | + if (payment.Variables.Count > 0 && |
235 | 245 | payment.Variables.TryGetValue("orderReference", out string orderReference)) |
236 | 246 | { |
237 | 247 | if (order.GenerateOrderReference() == orderReference) |
@@ -432,7 +442,8 @@ private async Task<bool> ValidateChecksumAsync(HttpRequestMessage request, strin |
432 | 442 | var json = await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); |
433 | 443 | var checkSum = request.Headers.GetValues("Quickpay-Checksum-Sha256").FirstOrDefault(); |
434 | 444 |
|
435 | | - if (string.IsNullOrEmpty(checkSum)) return false; |
| 445 | + if (string.IsNullOrEmpty(checkSum)) |
| 446 | + return false; |
436 | 447 |
|
437 | 448 | var calculatedChecksum = Checksum(json, privateAccountKey); |
438 | 449 |
|
|
0 commit comments