Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

@@ -9,11 +9,26 @@
* @since 11 Oct 2019
*/
\define('REPORT_EXCEPTIONS', TRUE);
require_once '../../../../init.php';
\IPS\Session\Front::i();
use IPS\core\AdminNotification;
use IPS\Db;
use IPS\nexus\extensions\nexus\Item\MiscellaneousCharge;
use IPS\nexus\Gateway;
use IPS\nexus\Gateway\PayPal;
use IPS\nexus\Gateway\PayPal\BillingAgreement;
use IPS\nexus\Invoice;
use IPS\nexus\Invoice\Item\Renewal;
use IPS\nexus\Money;
use IPS\nexus\Purchase;
use IPS\nexus\Transaction;
use IPS\Output;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Session\Front;
\IPS\Output::setCacheTime( false );
define('REPORT_EXCEPTIONS', TRUE);
require_once '../../../../init.php';
Front::i();
Output::i()->pageCaching = FALSE;
/**
* PayPal Webhook Handler
@@ -23,17 +38,17 @@ class paypalWebhookHandler
/**
* @brief Raw Webhook Data (as a string)
*/
private $body;
private mixed $body;
/**
* @brief Parsed Webhook Data (as an array)
*/
private $data;
private array $data;
/**
* @brief The payment method
*/
private $method;
private Gateway $method;
/**
* Constructor
@@ -41,7 +56,7 @@ class paypalWebhookHandler
* @param string $body The raw body posted to this script
* @return void
*/
public function __construct( $body )
public function __construct( string $body )
{
$this->body = $body;
$this->data = json_decode( $body, TRUE );
@@ -50,9 +65,9 @@ class paypalWebhookHandler
/**
* Sale completed
*
* @return void
* @return string
*/
public function saleCompleted()
public function saleCompleted() : string
{
/* Is this a billing agreement payment? */
if ( isset( $this->data['resource'] ) and isset( $this->data['resource']['id'] ) and isset( $this->data['resource']['billing_agreement_id'] ) )
@@ -60,39 +75,36 @@ class paypalWebhookHandler
/* Have we seen this transaction before? */
try
{
/* @note SELECT_FROM_WRITE_SERVER transaction race condition */
$transaction = \IPS\Db::i()->select( '*', 'nexus_transactions', array( 't_gw_id=? AND t_method=?', $this->data['resource']['id'], $this->method->id ), flags: \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
$transaction = Db::i()->select( '*', 'nexus_transactions', array( 't_gw_id=? AND t_method=?', $this->data['resource']['id'], $this->method->id ), flags: Db::SELECT_FROM_WRITE_SERVER )->first();
return 'ALREADY_PROCESSED';
}
catch ( \UnderflowException $e ) {}
catch (UnderflowException ) {}
/* Get the billing agreement */
try
{
/* @note Possible race condition when the webhook is almost instant */
$billingAgreement = \IPS\nexus\Gateway\PayPal\BillingAgreement::constructFromData( \IPS\Db::i()->select( '*', 'nexus_billing_agreements', array( 'ba_gw_id=? AND ba_method=?', $this->data['resource']['billing_agreement_id'], $this->method->id ), flags: \IPS\Db::SELECT_FROM_WRITE_SERVER )->first() );
$billingAgreement = BillingAgreement::constructFromData( Db::i()->select( '*', 'nexus_billing_agreements', array( 'ba_gw_id=? AND ba_method=?', $this->data['resource']['billing_agreement_id'], $this->method->id ) )->first() );
}
catch ( \UnderflowException $e )
catch (UnderflowException )
{
throw new \DomainException('UNKNOWN_BILLING_AGREEMENT');
throw new DomainException('UNKNOWN_BILLING_AGREEMENT');
}
/* Do it */
try
{
/* @note Possible race condition when the webhook is almost instant */
$transaction = \IPS\nexus\Transaction::constructFromData( \IPS\Db::i()->select( '*', 'nexus_transactions', array( 't_billing_agreement=? AND t_method=? AND t_status=?', $billingAgreement->id, $this->method->id, \IPS\nexus\Transaction::STATUS_GATEWAY_PENDING ), flags: \IPS\Db::SELECT_FROM_WRITE_SERVER )->first() );
$transaction = Transaction::constructFromData( Db::i()->select( '*', 'nexus_transactions', array( 't_billing_agreement=? AND t_method=? AND t_status=?', $billingAgreement->id, $this->method->id, Transaction::STATUS_GATEWAY_PENDING ) )->first() );
$transaction->gw_id = $this->data['resource']['id'];
$transaction->save();
return $this->_processInitialBillingAgreementTransaction( $transaction );
}
catch ( \UnderflowException $e )
catch (UnderflowException )
{
$transaction = new \IPS\nexus\Transaction;
$transaction = new Transaction;
$transaction->member = $billingAgreement->member;
$transaction->method = $this->method;
$transaction->amount = new \IPS\nexus\Money( $this->data['resource']['amount']['total'], $this->data['resource']['amount']['currency'] );
$transaction->amount = new Money( $this->data['resource']['amount']['total'], $this->data['resource']['amount']['currency'] );
$transaction->date = new \IPS\DateTime( $this->data['resource']['create_time'] );
$transaction->extra = array( 'automatic' => TRUE );
$transaction->gw_id = $this->data['resource']['id'];
@@ -110,18 +122,18 @@ class paypalWebhookHandler
/**
* Process the initial transaction for a billing agreement
*
* @param \IPS\nexus\Transaction $transaction The transaction
* @param Transaction $transaction The transaction
* @return string
*/
protected function _processInitialBillingAgreementTransaction( \IPS\nexus\Transaction $transaction )
protected function _processInitialBillingAgreementTransaction( Transaction $transaction ) : string
{
/* Take any fraud action */
$fraudResult = $transaction->fraud_blocked ? $transaction->fraud_blocked->action : NULL;
$fraudResult = $transaction->fraud_blocked?->action;
if ( $fraudResult )
{
$transaction->executeFraudAction( $fraudResult, TRUE );
}
if ( !$fraudResult or $fraudResult === \IPS\nexus\Transaction::STATUS_PAID )
if ( !$fraudResult or $fraudResult === Transaction::STATUS_PAID )
{
$transaction->approve();
}
@@ -136,16 +148,16 @@ class paypalWebhookHandler
/**
* Process a reccuring (i.e. not the intitial) transaction for a billing agreement
*
* @param \IPS\nexus\Transaction $transaction The transaction
* @param Transaction $transaction The transaction
* @return string
*/
protected function _processRecurringBillingAgreementTransaction( \IPS\nexus\Transaction $transaction )
protected function _processRecurringBillingAgreementTransaction( Transaction $transaction ) : string
{
/* Get purchases */
$purchases = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_billing_agreement=?', $transaction->billing_agreement->id ) ), 'IPS\nexus\Purchase' );
$purchases = new ActiveRecordIterator( Db::i()->select( '*', 'nexus_purchases', array( 'ps_billing_agreement=?', $transaction->billing_agreement->id ) ), 'IPS\nexus\Purchase' );
/* Generate an invoice */
$invoice = new \IPS\nexus\Invoice;
$invoice = new Invoice;
$invoice->system = TRUE;
$invoice->date = $transaction->date;
$invoice->currency = $transaction->amount->currency;
@@ -153,15 +165,16 @@ class paypalWebhookHandler
$invoice->billaddress = $transaction->billing_agreement->member->primaryBillingAddress();
foreach ( $purchases as $purchase )
{
/* @var Purchase $purchase */
if( !$purchase->renewals )
{
// Renewals have been cancelled for this purchase since PayPal tried to bill for it.
$amount = new \IPS\nexus\Money( $purchase->renewal_price, $transaction->amount->currency );
$invoice->addItem( new \IPS\nexus\extensions\nexus\Item\MiscellaneousCharge( sprintf( $purchase->member->language()->get('renew_payment_no_ba'), $purchase->name ), $amount ) );
$amount = new Money( $purchase->renewal_price, $transaction->amount->currency );
$invoice->addItem( new MiscellaneousCharge( sprintf( $purchase->member->language()->get('renew_payment_no_ba'), $purchase->name ), $amount ) );
continue;
}
$invoice->addItem( \IPS\nexus\Invoice\Item\Renewal::create( $purchase ) );
$invoice->addItem( Renewal::create( $purchase ) );
}
$invoice->save();
@@ -174,7 +187,7 @@ class paypalWebhookHandler
/* Log */
$invoice->member->log( 'transaction', array(
'type' => 'paid',
'status' => \IPS\nexus\Transaction::STATUS_PAID,
'status' => Transaction::STATUS_PAID,
'id' => $transaction->id,
'invoice_id' => $invoice->id,
'invoice_title' => $invoice->title,
@@ -212,9 +225,9 @@ class paypalWebhookHandler
/**
* Run
*
* @return void
* @return string
*/
public function run( $headers )
public function run() : string
{
/* Do it */
if ( isset( $this->data['event_type'] ) )
@@ -224,9 +237,9 @@ class paypalWebhookHandler
/* Sale completed: Used for automatic billing agreement purchases */
case 'PAYMENT.SALE.COMPLETED':
/* Validate it */
foreach ( \IPS\nexus\Gateway::roots() as $method )
foreach ( Gateway::roots() as $method )
{
if ( $method instanceof \IPS\nexus\Gateway\PayPal )
if ( $method instanceof PayPal )
{
try
{
@@ -246,12 +259,11 @@ class paypalWebhookHandler
$newSettings = $method->testSettings( $settings );
$method->settings = json_encode( $newSettings );
$method->save();
$settings = $newSettings;
}
/* But if that fails, send an AdminCP notification letting the admin know they need to fix it. */
catch ( \Exception $e )
catch (Exception )
{
\IPS\core\AdminNotification::send( 'nexus', 'ConfigurationError', "pm{$method->id}", FALSE );
AdminNotification::send( 'nexus', 'ConfigurationError', "pm{$method->id}", FALSE );
}
}
@@ -268,14 +280,14 @@ class paypalWebhookHandler
}
}
}
catch ( \Exception $e )
catch (Exception )
{
// Do nothing - try the next one
}
}
}
throw new \Exception('COULD_NOT_VALIDATE');
throw new Exception('COULD_NOT_VALIDATE');
// PAYMENTS.PAYMENT.CREATED, PAYMENT.AUTHORIZATION.CREATED and then PAYMENT.CAPTURE.COMPLETED are actually buying something (not a billing agreement payment)
@@ -286,7 +298,7 @@ class paypalWebhookHandler
}
else
{
throw new \Exception('INVALID_HOOK_DATA');
throw new Exception('INVALID_HOOK_DATA');
}
}
}
@@ -302,10 +314,10 @@ try
$headers[ str_replace( '_', '-', mb_substr( $k, 5 ) ) ] = $v;
}
}
$response = $class->run( $headers );
\IPS\Output::i()->sendOutput( $response, 200, 'text/plain' );
$response = $class->run();
Output::i()->sendOutput( $response, 200, 'text/plain' );
}
catch ( \Exception $e )
catch (Exception $e)
{
\IPS\Output::i()->sendOutput( $e->getMessage(), 500, 'text/plain' );
Output::i()->sendOutput( $e->getMessage(), 500, 'text/plain' );
}