Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -209,7 +209,7 @@ class _Braintree extends \IPS\nexus\Gateway
'line1' => isset( $invoice->shipaddress->addressLines[0] ) ? $invoice->shipaddress->addressLines[0] : NULL,
'line2' => isset( $invoice->shipaddress->addressLines[1] ) ? $invoice->shipaddress->addressLines[1] : NULL,
'city' => $invoice->shipaddress->city,
'state' => $invoice->shipaddress->region,
'state' => $settings['type'] === 'paypal' ? $this->_payPalState( $invoice->shipaddress ) : $invoice->shipaddress->region,
'postalCode' => $invoice->shipaddress->postalCode,
'countryCode' => $invoice->shipaddress->country,
'phone' => $invoice->member->cm_phone,
@@ -259,7 +259,7 @@ class _Braintree extends \IPS\nexus\Gateway
'lastName' => $invoice->member->cm_last_name,
'streetAddress' => $invoice->billaddress ? implode( ', ', $invoice->billaddress->addressLines ) : NULL,
'locality' => $invoice->billaddress ? $invoice->billaddress->city : NULL,
'region' => $invoice->billaddress ? $invoice->billaddress->region : NULL,
'region' => $invoice->billaddress ? ( $settings['type'] === 'paypal' ? $this->_payPalState( $invoice->billaddress ) : $invoice->billaddress->region ) : NULL,
'postalCode' => $invoice->billaddress ? $invoice->billaddress->postalCode : NULL,
'countryCodeAlpha2' => $invoice->billaddress ? $invoice->billaddress->country : NULL,
'phoneNumber' => preg_replace( '/[^0-9]/', '', $invoice->member->cm_phone ),
@@ -358,7 +358,7 @@ class _Braintree extends \IPS\nexus\Gateway
'lastName' => $transaction->invoice->member->cm_last_name,
'locality' => $transaction->invoice->billaddress->city,
'postalCode' => $transaction->invoice->billaddress->postalCode,
'region' => $transaction->invoice->billaddress->region,
'region' => $settings['type'] === 'paypal' ? $this->_payPalState( $transaction->invoice->billaddress ) : $transaction->invoice->billaddress->region,
'streetAddress' => implode( ', ', $transaction->invoice->billaddress->addressLines )
);
@@ -392,7 +392,7 @@ class _Braintree extends \IPS\nexus\Gateway
'lastName' => $transaction->invoice->member->cm_last_name,
'locality' => $transaction->invoice->shipaddress->city,
'postalCode' => $transaction->invoice->shipaddress->postalCode,
'region' => $transaction->invoice->shipaddress->region,
'region' => $settings['type'] === 'paypal' ? $this->_payPalState( $transaction->invoice->billaddress ) : $transaction->invoice->billaddress->region,
'streetAddress' => implode( ', ', $transaction->invoice->shipaddress->addressLines )
);
@@ -985,5 +985,28 @@ class _Braintree extends \IPS\nexus\Gateway
return $errorMessage;
}
/**
* Get short code for PayPal State
*
* @param \IPS\GeoLocation $address
* @return string
*/
protected function _payPalState( \IPS\GeoLocation $address ): string
{
/* PayPal requires short codes for states */
$state = $address->region;
if ( isset( \IPS\nexus\Customer\Address::$stateCodes[ $address->country ] ) )
{
if ( !array_key_exists( $state, \IPS\nexus\Customer\Address::$stateCodes[ $address->country ] ) )
{
$_state = array_search( $address->region, \IPS\nexus\Customer\Address::$stateCodes[ $address->country ] );
if ( $_state !== FALSE )
{
$state = $_state;
}
}
}
return $state;
}
}
+50 -3
View File
@@ -183,7 +183,7 @@ class _Gateway extends \IPS\Node\Model
*/
public function form( &$form )
{
$form->add( new \IPS\Helpers\Form\Translatable( 'paymethod_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => "nexus_paymethod_{$this->id}" ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'paymethod_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_paymethod_{$this->id}" : NULL ) ) );
$this->settings( $form );
$form->add( new \IPS\Helpers\Form\Select( 'paymethod_countries', ( $this->id and $this->countries !== '*' ) ? explode( ',', $this->countries ) : '*', FALSE, array( 'options' => array_map( function( $val )
{
@@ -210,6 +210,8 @@ class _Gateway extends \IPS\Node\Model
$values['countries'] = \is_array( $values['paymethod_countries'] ) ? implode( ',', $values['paymethod_countries'] ) : $values['paymethod_countries'];
}
$values['validationfile'] = (string) $values['m_validationfile'];
$settings = array();
foreach ( $values as $k => $v )
{
@@ -217,7 +219,7 @@ class _Gateway extends \IPS\Node\Model
{
$settings[ mb_substr( $k, mb_strlen( $this->gateway ) + 1 ) ] = $v;
}
if( $k != "countries" )
if( $k != "countries" AND $k != 'validationfile' )
{
unset( $values[$k] );
}
@@ -360,6 +362,21 @@ class _Gateway extends \IPS\Node\Model
}
}
/**
* [Node] Dissalow gateways with validation files to be copyable ( the file can't /shouldn't be copied and reusing the existing value will delete the source file when the new copy is deleted )
*
* @return bool
*/
public function canCopy()
{
if ( $this->deleteOrMoveQueued() === TRUE )
{
return FALSE;
}
return ( !$this->validationfile );
}
/**
* [ActiveRecord] Delete
*
@@ -370,7 +387,14 @@ class _Gateway extends \IPS\Node\Model
/* Delete cards and billing agreements */
\IPS\Db::i()->delete( 'nexus_customer_cards', array( 'card_method=?', $this->id ) );
\IPS\Db::i()->delete( 'nexus_billing_agreements', array( 'ba_method=?', $this->id ) );
try
{
\IPS\File::get( 'nexus_Gateways', $this->validationfile )->delete();
}
catch( \Exception $ex ) { }
/* Delete */
parent::delete();
@@ -635,4 +659,27 @@ class _Gateway extends \IPS\Node\Model
'name' => $this->_title
);
}
/**
* Returns the Apple Pay domain verification file if there's one.
*
* @return void|null
*/
public static function getStripeAppleVerificationFile()
{
foreach ( static::roots() as $gateway )
{
if ( $gateway->gateway == 'Stripe' AND $gateway->validationfile )
{
try
{
return \IPS\File::get( 'nexus_Gateways', $gateway->validationfile );
}
catch ( \Exception $e )
{
return NULL;
}
}
}
}
}
@@ -118,9 +118,38 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
*/
public function latestUnclaimedTransaction()
{
$url = "payments/billing-agreements/{$this->gw_id}/transactions?start_date=" . date('Y-m-d', time() - (86400 * 31)) . '&end_date=' . date('Y-m-d');
try
$data = $this->_getData();
/* PayPal Subscriptions */
if ( isset( $data['plan_id'] ) )
{
$transactions = $this->method->api( "billing/subscriptions/{$this->gw_id}/transactions?start_time=" . \IPS\DateTime::create()->sub( new \DateInterval( 'P31D' ) )->rfc3339() . '&end_time=' . \IPS\DateTime::create()->rfc3339(), NULL, 'get' );
foreach ( array_reverse( $transactions['transactions'] ) as $t )
{
if ( $t['status'] == 'COMPLETED' )
{
try
{
\IPS\Db::i()->select( 't_id', 'nexus_transactions', array( 't_method=? AND t_gw_id=?', $this->method->id, $t['id'] ) )->first();
}
catch ( \UnderflowException $e )
{
$transaction = new \IPS\nexus\Transaction;
$transaction->member = $this->member;
$transaction->method = $this->method;
$transaction->amount = new \IPS\nexus\Money( $t['amount_with_breakdown']['gross_amount']['value'], $t['amount_with_breakdown']['gross_amount']['currency_code'] );
$transaction->date = new \IPS\DateTime( $t['time'] );
$transaction->extra = array( 'automatic' => TRUE );
$transaction->gw_id = $t['id'];
$transaction->billing_agreement = $this;
return $transaction;
}
}
}
}
/* Legacy Billing Agreement */
else
{
$url = "payments/billing-agreements/{$this->gw_id}/transactions?start_date=" . date( 'Y-m-d', time() - ( 86400 * 31 ) ) . '&end_date=' . date( 'Y-m-d' );
$transactions = $this->method->api( $url, NULL, 'get' );
foreach ( array_reverse( $transactions['agreement_transaction_list'] ) as $t )
{
@@ -128,7 +157,7 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
{
try
{
$existingTransaction = \IPS\Db::i()->select( 't_id', 'nexus_transactions', array( 't_method=? AND t_gw_id=?', $this->method->id, $t['transaction_id'] ) )->first();
\IPS\Db::i()->select( 't_id', 'nexus_transactions', array( 't_method=? AND t_gw_id=?', $this->method->id, $t['transaction_id'] ) )->first();
}
catch ( \UnderflowException $e )
{
@@ -145,10 +174,6 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
}
}
}
catch ( \IPS\Http\Url\Exception $e )
{
throw $e;
}
throw new \OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
}
@@ -47,12 +47,6 @@ class _PayPal extends \IPS\nexus\Gateway
return FALSE;
}
/* Billing agreements require a billing address */
elseif ( static::_canProcessRecurringsAsBillingAgreement( $recurrings ) and ( isset( $settings['billing_agreements'] ) and $settings['billing_agreements'] == 'required' ) and !$billingAddress )
{
return FALSE;
}
/* Check transaction limit */
switch ( $amount->currency )
{
@@ -538,7 +532,7 @@ class _PayPal extends \IPS\nexus\Gateway
'description' => $description,
'billing_cycles' => array(),
'payment_preferences' => array(
'auto_bill_outstanding' => TRUE,
'auto_bill_outstanding' => FALSE,
'payment_failure_threshold' => \intval( $settings['billing_agreement_allowed_fails'] )
),
'taxes' => array(
@@ -618,29 +612,36 @@ class _PayPal extends \IPS\nexus\Gateway
public function void( \IPS\nexus\Transaction $transaction )
{
/* If this is is the intitial transaction for a billing agreement which hasn't
been processed yet, cancel the billing agreement */
been processed yet, cancel the billing agreement. If it's a subscription it may still be the first payment */
if ( $transaction->status === $transaction::STATUS_GATEWAY_PENDING and $transaction->billing_agreement )
{
$transaction->billing_agreement->cancel();
return;
}
/* Try to find the authorization ID */
if ( mb_substr( $transaction->gw_id, 0, 4 ) === 'PAY-' )
{
$authId = NULL;
$payment = $this->api( "payments/payment/{$transaction->gw_id}", NULL, 'get' );
foreach ( $payment['transactions'][0]['related_resources'] as $rr )
try
{
if ( isset( $rr['authorization'] ) )
$payment = $this->api( "payments/payment/{$transaction->gw_id}", NULL, 'get' );
foreach ( $payment['transactions'][0]['related_resources'] as $rr )
{
$authId = $rr['authorization']['id'];
if ( isset( $rr['authorization'] ) )
{
$authId = $rr['authorization']['id'];
}
}
if ( !$authId )
{
throw new \RuntimeException;
}
}
if ( !$authId )
/* Let's try the gateway id as the auth id */
catch ( \IPS\nexus\Gateway\PayPal\Exception $e )
{
throw new \RuntimeException;
$authId = $transaction->gw_id;
}
}
else
@@ -683,24 +684,53 @@ class _PayPal extends \IPS\nexus\Gateway
if ( $billingAgreement = $transaction->billing_agreement )
{
$response = 'WAIT';
$transactions = $this->api( "payments/billing-agreements/{$transaction->billing_agreement->gw_id}/transactions?start_date=" . date( 'Y-m-d', $transaction->date->getTimestamp() - 86400 ) . '&end_date=' . date( 'Y-m-d' ), NULL, 'get' );
foreach ( $transactions['agreement_transaction_list'] as $t )
/* Subscriptions API */
if( $data = $billingAgreement->_getData() AND isset( $data['plan_id'] ) )
{
if ( ( !$transaction->gw_id or $transaction->gw_id == $t['transaction_id'] ) and $transaction->amount->currency == $t['amount']['currency'] and $transaction->amount->amountAsString() == $t['amount']['value'] )
{
if ( $t['status'] == 'Completed' )
$transactions = $this->api( "billing/subscriptions/{$transaction->billing_agreement->gw_id}/transactions?start_time=" . \IPS\DateTime::ts( $transaction->date->getTimestamp() - 86400 )->rfc3339() . '&end_time=' . \IPS\DateTime::ts( time() )->rfc3339(), NULL, 'get' );
foreach ( $transactions['transactions'] as $t )
{
if ( ( !$transaction->gw_id or $transaction->gw_id == $t['id'] ) and $transaction->amount->currency == $t['amount_with_breakdown']['gross_amount']['currency_code'] and $transaction->amount->amountAsString() == $t['amount_with_breakdown']['gross_amount']['value'] )
{
$transaction->gw_id = $t['transaction_id'];
$transaction->save();
return TRUE;
if ( $t['status'] == 'COMPLETED' )
{
$transaction->gw_id = $t['id'];
$transaction->save();
return TRUE;
}
elseif ( $t['status'] == 'REFUNDED' )
{
$response = 'RFND';
}
elseif ( $t['status'] == 'DECLINED' )
{
$response = 'FAIL';
}
}
elseif ( $t['status'] == 'Refunded' )
}
}
/* Legacy Billing Agreements */
else
{
$transactions = $this->api( "payments/billing-agreements/{$transaction->billing_agreement->gw_id}/transactions?start_date=" . date( 'Y-m-d', $transaction->date->getTimestamp() - 86400 ) . '&end_date=' . date( 'Y-m-d' ), NULL, 'get' );
foreach ( $transactions['agreement_transaction_list'] as $t )
{
if ( ( !$transaction->gw_id or $transaction->gw_id == $t['transaction_id'] ) and $transaction->amount->currency == $t['amount']['currency'] and $transaction->amount->amountAsString() == $t['amount']['value'] )
{
$response = 'RFND';
}
elseif ( $t['status'] == 'Failed' )
{
$response = 'FAIL';
if ( $t['status'] == 'Completed' )
{
$transaction->gw_id = $t['transaction_id'];
$transaction->save();
return TRUE;
}
elseif ( $t['status'] == 'Refunded' )
{
$response = 'RFND';
}
elseif ( $t['status'] == 'Failed' )
{
$response = 'FAIL';
}
}
}
}
@@ -781,9 +811,21 @@ class _PayPal extends \IPS\nexus\Gateway
{
/* The capture ID is *normally* the gateway transaction ID */
$captureId = $transaction->gw_id;
/* Refund Amount */
$amount = $amount ? new \IPS\nexus\Money( $amount, $transaction->currency ) : $transaction->amount;
/* If it's a billing agreement and the gateway ID isn't prefixed with an I, it's likely to be a subscription */
if( $transaction->billing_agreement AND $transaction->gw_id AND mb_substr( $transaction->gw_id, 0, 2 ) !== 'I-' )
{
$response = $this->api( "payments/sale/{$transaction->gw_id}/refund", array( 'amount' => array(
'currency' => $amount->currency,
'total' => $amount->amountAsString()
) ) );
return $response['id'];
}
/* But if it starts with I- (or is a blank but known to be a billing agreement payment) - that's a billing agreement */
if ( ( $transaction->billing_agreement and !$transaction->gw_id ) or ( mb_substr( $transaction->gw_id, 0, 2 ) === 'I-' ) )
elseif ( ( $transaction->billing_agreement and !$transaction->gw_id ) or ( mb_substr( $transaction->gw_id, 0, 2 ) === 'I-' ) )
{
$transactions = $this->api( "payments/billing-agreements/{$transaction->billing_agreement->gw_id}/transactions?start_date=" . $transaction->date->sub( new \DateInterval('P1D') )->format('Y-m-d') . '&end_date=' . $transaction->date->format('Y-m-d'), NULL, 'get' );
foreach ( $transactions['agreement_transaction_list'] as $t )
@@ -813,7 +855,6 @@ class _PayPal extends \IPS\nexus\Gateway
}
/* Process Refund */
$amount = $amount ? new \IPS\nexus\Money( $amount, $transaction->currency ) : $transaction->amount;
$response = $this->api( "payments/capture/{$captureId}/refund", array( 'amount' => array(
'currency' => $amount->currency,
'total' => $amount->amountAsString()
@@ -1157,8 +1198,11 @@ class _PayPal extends \IPS\nexus\Gateway
if ( \count( $recurrings ) == 1 )
{
$recurrance = array_pop( $recurrings );
if ( $recurrance['dueDate']->getTimestamp() === \IPS\DateTime::create()->add( $recurrance['term']->interval )->getTimestamp() )
/* We need to allow for some minor deviation than checking for the exact second, this can happen if the request is at just the exact time where
the clock ticks over to the next second */
$difference = \IPS\DateTime::create()->add( $recurrance['term']->interval )->getTimestamp() - $recurrance['dueDate']->getTimestamp();
if ( $difference >= 0 AND $difference < 10 )
{
return TRUE;
}
@@ -314,14 +314,6 @@ class _Stripe extends \IPS\nexus\Gateway
'member' => $member,
) ) );
}
elseif ( $settings['type'] === 'amex' )
{
return array( 'card' => new \IPS\Helpers\Form\Custom( $this->id . '_card', NULL, FALSE, array(
'rowHtml' => function( $field ) use ( $settings ) {
return \IPS\Theme::i()->getTemplate( 'forms', 'nexus', 'global' )->amexExpressCheckout( $field, $this, $settings['amex_client_id'] );
}
) ) );
}
elseif ( $settings['type'] === 'sofort' and \IPS\NEXUS_TEST_GATEWAYS )
{
return array(
@@ -608,12 +600,6 @@ class _Stripe extends \IPS\nexus\Gateway
'phone' => isset( $transaction->invoice->member->cm_phone ) ? $transaction->invoice->member->cm_phone : NULL
);
}
/* Amex Express Checkout */
if ( $settings['type'] === 'amex' )
{
$data['card'] = $values[ $this->id . '_card' ];
}
/* Source-based */
elseif ( \is_string( $card ) )
@@ -851,9 +837,9 @@ class _Stripe extends \IPS\nexus\Gateway
{
$response = $charge;
$response['source'] = $response['payment_method_details'];
foreach ( array( 'cvc_check', 'address_line1_check', 'address_zip_check' ) as $k )
foreach ( array( 'cvc_check', 'address_line1_check', 'address_postal_code_check' ) as $k )
{
$response['source']['card'][ $k ] = isset( $response['payment_method_details']['checks'][ $k ] ) ? $response['payment_method_details']['checks'][ $k ] : 'unavailable';
$response['source']['card'][ $k ] = isset( $response['payment_method_details']['card']['checks'][ $k ] ) ? $response['payment_method_details']['card']['checks'][ $k ] : 'unavailable';
}
break;
}
@@ -975,7 +961,7 @@ class _Stripe extends \IPS\nexus\Gateway
public function form( &$form )
{
$form->addHeader('stripe_basic_settings');
$form->add( new \IPS\Helpers\Form\Translatable( 'paymethod_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => "nexus_paymethod_{$this->id}" ) ) );
$form->add( new \IPS\Helpers\Form\Translatable( 'paymethod_name', NULL, TRUE, array( 'app' => 'nexus', 'key' => $this->id ? "nexus_paymethod_{$this->id}" : NULL ) ) );
$form->add( new \IPS\Helpers\Form\Select( 'paymethod_countries', ( $this->id and $this->countries !== '*' ) ? explode( ',', $this->countries ) : '*', FALSE, array( 'options' => array_map( function( $val )
{
return "country-{$val}";
@@ -1002,7 +988,6 @@ class _Stripe extends \IPS\nexus\Gateway
'card' => 'stripe_type_card',
'native' => 'stripe_type_native',
'alipay' => 'stripe_type_alipay',
'amex' => 'stripe_type_amex',
'bancontact'=> 'stripe_type_bancontact',
'giropay' => 'stripe_type_giropay',
'ideal' => 'stripe_type_ideal',
@@ -1010,11 +995,17 @@ class _Stripe extends \IPS\nexus\Gateway
),
'toggles' => array(
'card' => array( 'stripe_cards' ),
'amex' => array( 'stripe_amex_client_id' )
'native' => array( 'stripe_apple_pay_file')
)
) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'stripe_cards', $settings ? $settings['cards'] : TRUE, FALSE, array(), NULL, NULL, NULL, 'stripe_cards' ) );
$form->add( new \IPS\Helpers\Form\Text( 'stripe_amex_client_id', ( $settings and isset( $settings['amex_client_id'] ) ) ? $settings['amex_client_id'] : NULL, NULL, array(), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('stripe_amex_client_id_gen'), 'stripe_amex_client_id' ) );
if ( \IPS\CIC )
{
$form->add( new \IPS\Helpers\Form\Upload( 'm_validationfile', $this->validationfile ? \IPS\File::get( 'nexus_Gateways', $this->validationfile ) : '', FALSE, array( 'storageExtension' => 'nexus_Gateways' ), NULL, NULL, NULL, 'stripe_apple_pay_file' ) );
\IPS\Member::loggedIn()->language()->words[ 'm_validationfile'] = \IPS\Member::loggedIn()->language()->addToStack('stripe_apple_verificationfile');
}
$form->addHeader('stripe_webhook');
$form->addMessage('stripe_webhook_blurb');
\IPS\Member::loggedIn()->language()->words["stripe_webhook_blurb"] = sprintf( \IPS\Member::loggedIn()->language()->get('stripe_webhook_blurb'), (string) \IPS\Http\Url::internal( 'applications/nexus/interface/gateways/stripe.php', 'interface' ) );
@@ -1058,7 +1049,7 @@ class _Stripe extends \IPS\nexus\Gateway
}
if ( !$webhookId )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack( 'stripe_webhook_invalid', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $this->webhookEvents ) ) ) ) );
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'stripe_webhook_invalid', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $this->webhookEvents ) ) ) ) );
}
$settings['webhook_id'] = $webhookId;