2020-09-28 08:42:14 +02:00
using System ;
2020-09-17 07:12:03 +02:00
using System.Linq ;
2020-09-14 00:14:36 +02:00
using System.Threading.Tasks ;
2021-04-08 06:37:05 +02:00
using BTCPayServer.Plugins.Shopify.ApiModels ;
2020-09-14 00:14:36 +02:00
2021-04-08 06:37:05 +02:00
namespace BTCPayServer.Plugins.Shopify
2020-09-14 00:14:36 +02:00
{
public class OrderTransactionRegisterLogic
{
private readonly ShopifyApiClient _client ;
public OrderTransactionRegisterLogic ( ShopifyApiClient client )
{
_client = client ;
}
2020-09-30 00:23:42 +02:00
private static string [ ] _keywords = new [ ] { "bitcoin" , "btc" , "btcpayserver" , "btcpay server" } ;
2020-09-21 12:40:45 +02:00
public async Task < TransactionsCreateResp > Process ( string orderId , string invoiceId , string currency , string amountCaptured , bool success )
2020-09-14 00:14:36 +02:00
{
2020-09-21 12:40:45 +02:00
currency = currency . ToUpperInvariant ( ) . Trim ( ) ;
var existingShopifyOrderTransactions = ( await _client . TransactionsList ( orderId ) ) . transactions ;
2020-09-30 00:23:42 +02:00
var baseParentTransaction = existingShopifyOrderTransactions . FirstOrDefault ( ) ;
if ( baseParentTransaction is null | |
! _keywords . Any ( a = > baseParentTransaction . gateway . Contains ( a , StringComparison . InvariantCultureIgnoreCase ) ) )
2020-09-21 15:34:41 +02:00
{
return null ;
}
2020-09-30 00:23:42 +02:00
2020-09-28 08:30:50 +02:00
//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.
2020-09-21 15:34:41 +02:00
if ( currency . ToUpperInvariant ( ) . Trim ( ) ! = baseParentTransaction . currency . ToUpperInvariant ( ) . Trim ( ) )
{
// because of parent_id present, currency will always be the one from parent transaction
// 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 ;
}
2020-09-14 00:14:36 +02:00
2020-09-21 15:34:41 +02:00
var kind = "capture" ;
var parentId = baseParentTransaction . id ;
var status = success ? "success" : "failure" ;
2020-09-28 08:30:50 +02:00
//find all existing transactions recorded around this invoice id
2020-09-21 15:34:41 +02:00
var existingShopifyOrderTransactionsOnSameInvoice =
existingShopifyOrderTransactions . Where ( holder = > holder . authorization = = invoiceId ) ;
2020-09-30 00:23:42 +02:00
2020-09-28 08:30:50 +02:00
//filter out the successful ones
2020-09-21 15:34:41 +02:00
var successfulActions =
existingShopifyOrderTransactionsOnSameInvoice . Where ( holder = > holder . status = = "success" ) . ToArray ( ) ;
2020-09-14 00:14:36 +02:00
2020-09-28 08:30:50 +02:00
//of the successful ones, get the ones we registered as a valid payment
2020-09-21 15:34:41 +02:00
var successfulCaptures = successfulActions . Where ( holder = > holder . kind = = "capture" ) . ToArray ( ) ;
2020-09-30 00:23:42 +02:00
2020-09-28 08:30:50 +02:00
//of the successful ones, get the ones we registered as a voiding of a previous successful payment
2020-09-21 15:34:41 +02:00
var refunds = successfulActions . Where ( holder = > holder . kind = = "refund" ) . ToArray ( ) ;
2020-09-30 00:23:42 +02:00
2020-09-28 08:30:50 +02:00
//if we are working with a non-success registration, but see that we have previously registered this invoice as a success, we switch to creating a "void" transaction, which in shopify terms is a refund.
2020-09-21 15:34:41 +02:00
if ( ! success & & successfulCaptures . Length > 0 & & ( successfulCaptures . Length - refunds . Length ) > 0 )
{
kind = "void" ;
parentId = successfulCaptures . Last ( ) . id ;
status = "success" ;
2020-09-14 00:14:36 +02:00
}
2020-09-28 08:30:50 +02:00
//if we are working with a success registration, but can see that we have already had a successful transaction saved, get outta here
2020-09-30 00:23:42 +02:00
else if ( success & & successfulCaptures . Length > 0 & & ( successfulCaptures . Length - refunds . Length ) > 0 )
2020-09-21 15:34:41 +02:00
{
return null ;
}
var createTransaction = new TransactionsCreateReq
{
transaction = new TransactionsCreateReq . DataHolder
{
parent_id = parentId ,
currency = currency ,
amount = amountCaptured ,
kind = kind ,
gateway = "BTCPayServer" ,
source = "external" ,
authorization = invoiceId ,
status = status
}
} ;
var createResp = await _client . TransactionCreate ( orderId , createTransaction ) ;
return createResp ;
2020-09-14 00:14:36 +02:00
}
}
}