mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Cancel shopify order when invoice payment fails (#6027)
* Cancel shopify order when invoice payment fails * void correctly and invoice logs
This commit is contained in:
parent
4307fa24a7
commit
c5aca1b7f7
3 changed files with 61 additions and 9 deletions
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Plugins.Shopify.ApiModels;
|
||||
|
||||
namespace BTCPayServer.Plugins.Shopify
|
||||
|
@ -15,8 +17,9 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
}
|
||||
|
||||
private static string[] _keywords = new[] { "bitcoin", "btc", "btcpayserver", "btcpay server" };
|
||||
public async Task<TransactionsCreateResp> Process(string orderId, string invoiceId, string currency, string amountCaptured, bool success)
|
||||
public async Task<InvoiceLogs> Process(string orderId, string invoiceId, string currency, string amountCaptured, bool success)
|
||||
{
|
||||
var result = new InvoiceLogs();
|
||||
currency = currency.ToUpperInvariant().Trim();
|
||||
var existingShopifyOrderTransactions = (await _client.TransactionsList(orderId)).transactions;
|
||||
|
||||
|
@ -24,7 +27,8 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
var baseParentTransaction = existingShopifyOrderTransactions.FirstOrDefault(holder => _keywords.Any(a => holder.gateway.Contains(a, StringComparison.InvariantCultureIgnoreCase)));
|
||||
if (baseParentTransaction is null)
|
||||
{
|
||||
return null;
|
||||
result.Write("Couldn't find the order on Shopify.", InvoiceEventData.EventSeverity.Error);
|
||||
return result;
|
||||
}
|
||||
|
||||
//technically, this exploit should not be possible as we use internal invoice tags to verify that the invoice was created by our controlled, dedicated endpoint.
|
||||
|
@ -34,7 +38,8 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
// malicious attacker could potentially exploit this by creating invoice
|
||||
// in different currency and paying that one, registering order on Shopify as paid
|
||||
// so if currency is supplied and is different from parent transaction currency we just won't register
|
||||
return null;
|
||||
result.Write("Currency mismatch on Shopify.", InvoiceEventData.EventSeverity.Error);
|
||||
return result;
|
||||
}
|
||||
|
||||
var kind = "capture";
|
||||
|
@ -60,11 +65,20 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
kind = "void";
|
||||
parentId = successfulCaptures.Last().id;
|
||||
status = "success";
|
||||
result.Write("A transaction was previously recorded against the Shopify order. Creating a void transaction.", InvoiceEventData.EventSeverity.Warning);
|
||||
|
||||
}else if (!success)
|
||||
{
|
||||
|
||||
kind = "void";
|
||||
status = "success";
|
||||
result.Write("Attempting to void the payment on Shopify order due to failure in payment.", InvoiceEventData.EventSeverity.Warning);
|
||||
}
|
||||
//if we are working with a success registration, but can see that we have already had a successful transaction saved, get outta here
|
||||
else if (success && successfulCaptures.Length > 0 && (successfulCaptures.Length - refunds.Length) > 0)
|
||||
{
|
||||
return null;
|
||||
result.Write("A transaction was previously recorded against the Shopify order. Skipping.", InvoiceEventData.EventSeverity.Warning);
|
||||
return result;
|
||||
}
|
||||
var createTransaction = new TransactionsCreateReq
|
||||
{
|
||||
|
@ -81,7 +95,33 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
}
|
||||
};
|
||||
var createResp = await _client.TransactionCreate(orderId, createTransaction);
|
||||
return createResp;
|
||||
|
||||
if (createResp.transaction is null)
|
||||
{
|
||||
result.Write("Failed to register the transaction on Shopify.", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Write($"Successfully registered the transaction on Shopify. tx status:{createResp.transaction.status}, kind: {createResp.transaction.kind}, order id:{createResp.transaction.order_id}", InvoiceEventData.EventSeverity.Info);
|
||||
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
await _client.CancelOrder(orderId);
|
||||
result.Write("Cancelling the Shopify order.", InvoiceEventData.EventSeverity.Warning);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.Write($"Failed to cancel the Shopify order. {e.Message}", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
}
|
||||
|
||||
private HttpRequestMessage CreateRequest(string shopName, HttpMethod method, string action,
|
||||
string relativeUrl = null)
|
||||
string relativeUrl = null, string apiVersion = "2020-07")
|
||||
{
|
||||
var url =
|
||||
$"https://{(shopName.Contains('.', StringComparison.InvariantCulture) ? shopName : $"{shopName}.myshopify.com")}/{relativeUrl ?? ("admin/api/2020-07/" + action)}";
|
||||
$"https://{(shopName.Contains('.', StringComparison.InvariantCulture) ? shopName : $"{shopName}.myshopify.com")}/{relativeUrl ?? ($"admin/api/{apiVersion}/" + action)}";
|
||||
var req = new HttpRequestMessage(method, url);
|
||||
return req;
|
||||
}
|
||||
|
@ -115,6 +115,15 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
|
||||
return JObject.Parse(strResp)["order"].ToObject<ShopifyOrder>();
|
||||
}
|
||||
public async Task<ShopifyOrder> CancelOrder(string orderId)
|
||||
{
|
||||
var req = CreateRequest(_credentials.ShopName, HttpMethod.Post,
|
||||
$"orders/{orderId}/close.json", null, "2024-04");
|
||||
|
||||
var strResp = await SendRequest(req);
|
||||
|
||||
return JObject.Parse(strResp)["order"].ToObject<ShopifyOrder>();
|
||||
}
|
||||
|
||||
public async Task<long> OrdersCount()
|
||||
{
|
||||
|
|
|
@ -19,14 +19,17 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
public class ShopifyOrderMarkerHostedService : EventHostedServiceBase
|
||||
{
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public ShopifyOrderMarkerHostedService(EventAggregator eventAggregator,
|
||||
StoreRepository storeRepository,
|
||||
InvoiceRepository invoiceRepository,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
Logs logs) : base(eventAggregator, logs)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
|
@ -93,8 +96,8 @@ namespace BTCPayServer.Plugins.Shopify
|
|||
invoice.Price.ToString(CultureInfo.InvariantCulture), success);
|
||||
if (resp != null)
|
||||
{
|
||||
Logs.PayServer.LogInformation($"Registered order transaction {invoice.Price}{invoice.Currency} on Shopify. " +
|
||||
$"Triggered by invoiceId: {invoice.Id}, Shopify orderId: {shopifyOrderId}, Success: {success}");
|
||||
await _invoiceRepository.AddInvoiceLogs(invoice.Id, resp);
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
Loading…
Add table
Reference in a new issue