2024-05-13 18:33:15 +02:00
< ? php
/**
* Invoice Ninja ( https :// invoiceninja . com ) .
*
* @ link https :// github . com / invoiceninja / invoiceninja source repository
*
* @ copyright Copyright ( c ) 2021. Invoice Ninja LLC ( https :// invoiceninja . com )
*
* @ license https :// opensource . org / licenses / AAL
*/
namespace App\PaymentDrivers ;
2024-06-07 13:30:11 +02:00
use App\Models\Client ;
2024-08-30 03:10:18 +02:00
use App\Models\Invoice ;
use App\Models\Payment ;
use App\Models\SystemLog ;
use App\Models\GatewayType ;
use App\Models\PaymentHash ;
2024-06-07 13:30:11 +02:00
use App\Models\PaymentType ;
2024-08-30 03:10:18 +02:00
use App\Utils\Traits\MakesHash ;
2024-05-13 18:33:15 +02:00
use BTCPayServer\Client\Webhook ;
2024-08-30 03:10:18 +02:00
use App\Exceptions\PaymentFailed ;
use App\PaymentDrivers\BTCPay\BTCPay ;
use App\Jobs\Mail\PaymentFailedMailer ;
2024-06-07 13:30:11 +02:00
use App\Http\Requests\Payments\PaymentWebhookRequest ;
2024-05-13 18:33:15 +02:00
class BTCPayPaymentDriver extends BaseDriver
{
use MakesHash ;
public $refundable = true ; //does this gateway support refunds?
public $token_billing = false ; //does this gateway support token billing?
public $can_authorise_credit_card = false ; //does this gateway support authorizations?
public $gateway ; //initialized gateway
public $payment_method ; //initialized payment method
public static $methods = [
GatewayType :: CRYPTO => BTCPay :: class , //maps GatewayType => Implementation class
];
2024-06-14 09:09:44 +02:00
public const SYSTEM_LOG_TYPE = SystemLog :: TYPE_CHECKOUT ; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
2024-05-13 18:33:15 +02:00
public $btcpay_url = " " ;
public $api_key = " " ;
public $store_id = " " ;
public $webhook_secret = " " ;
public $btcpay ;
public function init ()
{
$this -> btcpay_url = $this -> company_gateway -> getConfigField ( 'btcpayUrl' );
$this -> api_key = $this -> company_gateway -> getConfigField ( 'apiKey' );
$this -> store_id = $this -> company_gateway -> getConfigField ( 'storeId' );
$this -> webhook_secret = $this -> company_gateway -> getConfigField ( 'webhookSecret' );
return $this ; /* This is where you boot the gateway with your auth credentials*/
}
/* Returns an array of gateway types for the payment gateway */
public function gatewayTypes () : array
{
$types = [];
$types [] = GatewayType :: CRYPTO ;
return $types ;
}
public function setPaymentMethod ( $payment_method_id )
{
$class = self :: $methods [ $payment_method_id ];
$this -> payment_method = new $class ( $this );
return $this ;
}
public function processPaymentView ( array $data )
{
return $this -> payment_method -> paymentView ( $data ); //this is your custom implementation from here
}
2024-05-30 15:34:43 +02:00
public function processPaymentResponse ( $request )
{
return $this -> payment_method -> paymentResponse ( $request );
}
2024-05-13 18:33:15 +02:00
public function processWebhookRequest ()
{
2024-08-19 11:17:58 +02:00
sleep ( 2 );
2024-05-13 18:33:15 +02:00
$webhook_payload = file_get_contents ( 'php://input' );
2024-06-07 13:30:11 +02:00
2024-06-09 01:08:31 +02:00
/** @var \stdClass $btcpayRep */
2024-05-13 18:33:15 +02:00
$btcpayRep = json_decode ( $webhook_payload );
if ( $btcpayRep == null ) {
throw new PaymentFailed ( 'Empty data' );
}
if ( true === empty ( $btcpayRep -> invoiceId )) {
throw new PaymentFailed (
'Invalid BTCPayServer payment notification- did not receive invoice ID.'
);
}
2024-06-07 13:30:11 +02:00
if (
str_starts_with ( $btcpayRep -> invoiceId , " __test__ " )
|| $btcpayRep -> type == " InvoiceProcessing "
|| $btcpayRep -> type == " InvoiceCreated "
) {
2024-05-13 18:33:15 +02:00
return ;
}
2024-06-09 01:08:31 +02:00
$sig = '' ;
2024-05-13 18:33:15 +02:00
$headers = getallheaders ();
foreach ( $headers as $key => $value ) {
if ( strtolower ( $key ) === 'btcpay-sig' ) {
$sig = $value ;
}
}
$this -> init ();
$webhookClient = new Webhook ( $this -> btcpay_url , $this -> api_key );
if ( ! $webhookClient -> isIncomingWebhookRequestValid ( $webhook_payload , $sig , $this -> webhook_secret )) {
throw new \RuntimeException (
'Invalid BTCPayServer payment notification message received - signature did not match.'
);
}
2024-06-07 13:30:11 +02:00
$this -> setPaymentMethod ( GatewayType :: CRYPTO );
2024-08-19 11:17:58 +02:00
$this -> payment_hash = PaymentHash :: where ( 'hash' , $btcpayRep -> metadata -> InvoiceNinjaPaymentHash ) -> firstOrFail ();
2024-06-07 13:30:11 +02:00
$StatusId = Payment :: STATUS_PENDING ;
2024-08-19 11:17:58 +02:00
2024-06-07 13:30:11 +02:00
if ( $this -> payment_hash -> payment_id == null ) {
2024-06-27 06:52:38 +02:00
2024-08-19 11:17:58 +02:00
$_invoice = $this -> payment_hash -> fee_invoice ;
2024-06-27 06:52:38 +02:00
$this -> client = $_invoice -> client ;
2024-06-07 13:30:11 +02:00
$dataPayment = [
'payment_method' => $this -> payment_method ,
'payment_type' => PaymentType :: CRYPTO ,
'amount' => $_invoice -> amount ,
'gateway_type_id' => GatewayType :: CRYPTO ,
'transaction_reference' => $btcpayRep -> invoiceId
];
$payment = $this -> createPayment ( $dataPayment , $StatusId );
2024-08-19 11:17:58 +02:00
2024-06-07 13:30:11 +02:00
} else {
2024-06-09 01:08:31 +02:00
/** @var \App\Models\Payment $payment */
2024-06-27 06:52:38 +02:00
$payment = Payment :: withTrashed () -> find ( $this -> payment_hash -> payment_id );
2024-06-07 13:30:11 +02:00
$StatusId = $payment -> status_id ;
}
2024-05-13 18:33:15 +02:00
switch ( $btcpayRep -> type ) {
case " InvoiceExpired " :
2024-08-30 03:10:18 +02:00
if ( $payment -> status_id == Payment :: STATUS_PENDING ) {
$payment -> service () -> deletePayment ();
$this -> failedPaymentNotification ( $payment );
}
2024-06-07 13:30:11 +02:00
$StatusId = Payment :: STATUS_CANCELLED ;
2024-05-13 18:33:15 +02:00
break ;
case " InvoiceInvalid " :
2024-08-30 03:10:18 +02:00
if ( $payment -> status_id == Payment :: STATUS_PENDING ) {
$payment -> service () -> deletePayment ();
$this -> failedPaymentNotification ( $payment );
}
2024-06-07 13:30:11 +02:00
$StatusId = Payment :: STATUS_FAILED ;
2024-05-13 18:33:15 +02:00
break ;
case " InvoiceSettled " :
2024-06-07 13:30:11 +02:00
$StatusId = Payment :: STATUS_COMPLETED ;
2024-05-13 18:33:15 +02:00
break ;
}
2024-08-30 03:10:18 +02:00
2024-06-07 13:30:11 +02:00
if ( $payment -> status_id != $StatusId ) {
$payment -> status_id = $StatusId ;
$payment -> save ();
}
2024-05-13 18:33:15 +02:00
}
2024-08-30 03:10:18 +02:00
private function failedPaymentNotification ( Payment $payment ) : void
{
$error = ctrans ( 'texts.client_payment_failure_body' , [
'invoice' => implode ( ',' , $payment -> invoices -> pluck ( 'number' ) -> toArray ()),
'amount' => array_sum ( array_column ( $this -> payment_hash -> invoices (), 'amount' )) + $this -> payment_hash -> fee_total , ]);
PaymentFailedMailer :: dispatch (
$this -> payment_hash ,
$payment -> client -> company ,
$payment -> client ,
$error
);
}
2024-05-13 18:33:15 +02:00
public function refund ( Payment $payment , $amount , $return_client_response = false )
{
$this -> setPaymentMethod ( GatewayType :: CRYPTO );
return $this -> payment_method -> refund ( $payment , $amount ); //this is your custom implementation from here
}
}