Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

@@ -134,8 +134,6 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
*/
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\nexus\Customer $member = NULL, $recurrings = array() )
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_gateways.js', 'nexus', 'global' ) );
$return = array();
$settings = json_decode( $this->settings, TRUE );
@@ -308,7 +306,7 @@ class _AuthorizeNet extends \IPS\nexus\Gateway
$storedCard->card = $card;
$storedCard->save();
}
catch ( \Exception $e ) { }
catch ( \Exception $e ) { /* If there's any issue with saving (which may happen for a duplicate card) we can just carry on since we already auth'd */ }
}
}
@@ -113,7 +113,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
$this->member->cm_profiles = $profiles;
$this->member->save();
}
/* Add the card */
$xml = $this->createAuthenticationXml( 'createCustomerPaymentProfileRequest' );
$xml->addChild( 'customerProfileId', $profiles[ $this->method->id ] );
@@ -133,6 +133,12 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
throw new \UnexpectedValueException;
}
/* Check it doesn't already exist */
if ( count( \IPS\Db::i()->select( 'card_data', 'nexus_customer_cards', array( 'card_member=? AND card_method=? AND card_data=?', $this->member->member_id, $this->method->id, (string) $response->customerPaymentProfileId ) ) ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('card_is_duplicate') );
}
/* Save */
$this->data = (string) $response->customerPaymentProfileId;
$this->save();
+79 -5
View File
@@ -280,12 +280,18 @@ class _Gateway extends \IPS\Node\Model
*/
protected static function recountCardStorageGateways()
{
foreach ( array( 'card_storage_gateways' => count( static::cardStorageGateways() ), 'billing_agreement_gateways' => count( static::billingAgreementGateways() ) ) as $k => $v )
$counts = array();
foreach ( static::roots() as $gateway )
{
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => $v ), array( 'conf_key=?', $k ) );
\IPS\Settings::i()->$k = $v;
if ( !isset( $counts[ $gateway->gateway ] ) )
{
$counts[ $gateway->gateway ] = 0;
}
$counts[ $gateway->gateway ]++;
}
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->changeValues( array( 'card_storage_gateways' => count( static::cardStorageGateways() ), 'billing_agreement_gateways' => count( static::billingAgreementGateways() ), 'gateways_counts' => json_encode( $counts ) ) );
}
/* !Features (Each gateway will override) */
@@ -351,6 +357,16 @@ class _Gateway extends \IPS\Node\Model
/* !Payment Gateway */
/**
* Should the submit button show when this payment method is shown?
*
* @return bool
*/
public function showSubmitButton()
{
return true;
}
/**
* Payment Screen Fields
*
@@ -364,6 +380,18 @@ class _Gateway extends \IPS\Node\Model
{
return array();
}
/**
* Manual Payment Instructions
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @param string|NULL $email If this is for the email, will be 'html' or 'plaintext'. If for UI, will be NULL.
* @return array
*/
public function manualPaymentInstructions( \IPS\nexus\Transaction $transaction, $email = NULL )
{
return $transaction->member->language()->addToStack( "nexus_gateway_{$this->id}_ins" );
}
/**
* Authorize
@@ -417,6 +445,16 @@ class _Gateway extends \IPS\Node\Model
throw new \Exception;
}
/**
* Refund Reasons that the gateway understands, if the gateway supports this
*
* @return array
*/
public static function refundReasons()
{
return array();
}
/**
* Extra data to show on the ACP transaction page
*
@@ -428,14 +466,50 @@ class _Gateway extends \IPS\Node\Model
return '';
}
/**
* Extra data to show on the ACP transaction page for a dispute
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @param string $ref Dispute reference ID
* @return string
*/
public function disputeData( \IPS\nexus\Transaction $transaction, $ref )
{
return '';
}
/**
* Run any gateway-specific anti-fraud checks and return status for transaction
* This is only called if our local anti-fraud rules have not matched
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @return string
*/
public function fraudCheck( \IPS\nexus\Transaction $transaction )
{
return $transaction::STATUS_PAID;
}
/**
* URL to view transaction in gateway
*
* @param \IPS\nexus\Transaction $transaction Transaction
* @return \IPS\Http\Url|NULL
*/
public function gatewayUrl( \IPS\nexus\Transaction $transaction )
{
return NULL;
}
/**
* Get output for API
*
* @param \IPS\Member|NULL $authorizedMember The member making the API request or NULL for API Key / client_credentials
* @return array
* @apiresponse int id ID number
* @apiresponse string name Name
*/
public function apiOutput()
public function apiOutput( \IPS\Member $authorizedMember = NULL )
{
return array(
'id' => $this->id,
@@ -96,4 +96,16 @@ class _Manual extends \IPS\nexus\Gateway
{
return $settings;
}
/**
* Refund
*
* @param \IPS\nexus\Transaction $transaction Transaction to be refunded
* @param float|NULL $amount Amount to refund (NULL for full amount - always in same currency as transaction)
* @return mixed Gateway reference ID for refund, if applicable
*/
public function refund( \IPS\nexus\Transaction $transaction, $amount = NULL )
{
}
}
@@ -83,40 +83,49 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
return new \IPS\DateTime( $data['agreement_details']['next_billing_date'] );
}
/**
* Get latest unclaimed transaction (only gets transactions within the last 31 days which do not yet have a matching transaction)
*
* @return \IPS\nexus\Transaction
* @throws \IPS\nexus\Gateway\PayPal\Exception
* @throws \OutOfRangeException
* @throws \IPS\Http\Exception
*/
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' );
$transactions = $this->method->api( $url, NULL, 'get' );
foreach ( array_reverse( $transactions['agreement_transaction_list'] ) as $t )
$url = "payments/billing-agreements/{$this->gw_id}/transactions?start_date=" . date('Y-m-d', time() - (86400 * 31)) . '&end_date=' . date('Y-m-d');
try
{
if ( $t['status'] == 'Completed' )
$transactions = $this->method->api( $url, NULL, 'get' );
foreach ( array_reverse( $transactions['agreement_transaction_list'] ) as $t )
{
try
if ( $t['status'] == 'Completed' )
{
$existingTransaction = \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 )
{
$transaction = new \IPS\nexus\Transaction;
$transaction->member = $this->member;
$transaction->method = $this->method;
$transaction->amount = new \IPS\nexus\Money( $t['amount']['value'], $t['amount']['currency'] );
$transaction->date = new \IPS\DateTime( $t['time_stamp'] );
$transaction->extra = array( 'automatic' => TRUE );
$transaction->gw_id = $t['transaction_id'];
$transaction->billing_agreement = $this;
return $transaction;
try
{
$existingTransaction = \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 )
{
$transaction = new \IPS\nexus\Transaction;
$transaction->member = $this->member;
$transaction->method = $this->method;
$transaction->amount = new \IPS\nexus\Money( $t['amount']['value'], $t['amount']['currency'] );
$transaction->date = new \IPS\DateTime( $t['time_stamp'] );
$transaction->extra = array( 'automatic' => TRUE );
$transaction->gw_id = $t['transaction_id'];
$transaction->billing_agreement = $this;
return $transaction;
}
}
}
}
catch ( \IPS\Http\Exception $e )
{
throw $e;
}
throw new \OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
}
@@ -86,6 +86,21 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
break;
}
/* Check it doesn't already exist */
$otherCards = \IPS\Db::i()->select( 'card_data', 'nexus_customer_cards', array( 'card_member=? AND card_method=?', $this->member->member_id, $this->method->id ) );
if ( count( $otherCards ) )
{
foreach ( $otherCards as $otherCardId )
{
$otherCardData = $this->method->api( 'vault/credit-card/' . $otherCardId, NULL, 'get' );
if ( $otherCardData['state'] === 'ok' and mb_substr( $otherCardData['number'], -4 ) === mb_substr( $card->number, -4 ) and intval( $otherCardData['expire_month'] ) === intval( $card->expMonth ) and intval( $otherCardData['expire_year'] ) === intval( $card->expYear ) )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('card_is_duplicate') );
}
}
}
/* Save */
$response = $this->method->api( 'vault/credit-card', array(
'number' => $card->number,
'type' => $cardType,
@@ -95,7 +110,6 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
'first_name' => $this->member->cm_first_name,
'last_name' => $this->member->cm_last_name,
) );
$this->data = $response['id'];
$this->save();
}
@@ -266,7 +266,7 @@ class _PayPal extends \IPS\nexus\Gateway
$settings = json_decode( $this->settings, TRUE );
if ( $settings['type'] === 'card' )
{
return array( 'card' => new \IPS\nexus\Form\CreditCard( $this->id . '_card', NULL, FALSE, array(
return array( 'card' => new \IPS\nexus\Form\CreditCard( $this->id . '_card', NULL, TRUE, array(
'types' => array( \IPS\nexus\CreditCard::TYPE_VISA, \IPS\nexus\CreditCard::TYPE_MASTERCARD, \IPS\nexus\CreditCard::TYPE_DISCOVER, \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS ),
'save' => $settings['vault'] ? $this : NULL,
'member' => $member
@@ -417,7 +417,7 @@ class _PayPal extends \IPS\nexus\Gateway
$storedCard->card = $card;
$storedCard->save();
}
catch ( \Exception $e ) { }
catch ( \Exception $e ) { /* If there's any issue with saving (which may happen for a duplicate card) we can just carry on since we already auth'd */ }
}
/* And return */
@@ -1008,7 +1008,7 @@ class _PayPal extends \IPS\nexus\Gateway
{
$payPalTransactionData['item_list']['items'][] = array(
'quantity' => $item->quantity,
'name' => $item->name,
'name' => mb_strlen( $item->name ) > 127 ? mb_substr( $item->name, 0, 124 ) . '...' : $item->name,
'price' => $item->price->amountAsString(),
'currency' => $transaction->amount->currency,
);
@@ -36,40 +36,73 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
public function get_card()
{
if ( !$this->_card )
{
$profiles = $this->member->cm_profiles;
if ( !isset( $profiles[ $this->method->id ] ) )
{
if ( mb_substr( $this->data, 0, 4 ) === 'src_' )
{
throw new \UnexpectedValueException;
$response = $this->method->api( "sources/{$this->data}", NULL, 'get' );
$this->_card = new \IPS\nexus\CreditCard;
$this->_card->lastFour = $response['card']['last4'];
switch ( $response['card']['brand'] )
{
case 'Visa':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_VISA;
break;
case 'American Express':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS;
break;
case 'MasterCard':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_MASTERCARD;
break;
case 'Discover':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DISCOVER;
break;
case 'JCB':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_JCB;
break;
case 'Diners Club':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DINERS_CLUB;
break;
}
$this->_card->expMonth = $response['card']['exp_month'];
$this->_card->expYear = $response['card']['exp_year'];
}
$response = $this->method->api( "customers/{$profiles[ $this->method->id ]}/cards/{$this->data}", NULL, 'get' );
$this->_card = new \IPS\nexus\CreditCard;
$this->_card->lastFour = $response['last4'];
switch ( $response['type'] )
{
case 'Visa':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_VISA;
break;
case 'American Express':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS;
break;
case 'MasterCard':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_MASTERCARD;
break;
case 'Discover':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DISCOVER;
break;
case 'JCB':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_JCB;
break;
case 'Diners Club':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DINERS_CLUB;
break;
else
{
$profiles = $this->member->cm_profiles;
if ( !isset( $profiles[ $this->method->id ] ) )
{
throw new \UnexpectedValueException;
}
$response = $this->method->api( "customers/{$profiles[ $this->method->id ]}/cards/{$this->data}", NULL, 'get' );
$this->_card = new \IPS\nexus\CreditCard;
$this->_card->lastFour = $response['last4'];
switch ( $response['type'] )
{
case 'Visa':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_VISA;
break;
case 'American Express':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_AMERICAN_EXPRESS;
break;
case 'MasterCard':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_MASTERCARD;
break;
case 'Discover':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DISCOVER;
break;
case 'JCB':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_JCB;
break;
case 'Diners Club':
$this->_card->type = \IPS\nexus\CreditCard::TYPE_DINERS_CLUB;
break;
}
$this->_card->expMonth = $response['exp_month'];
$this->_card->expYear = $response['exp_year'];
}
$this->_card->expMonth = $response['exp_month'];
$this->_card->expYear = $response['exp_year'];
}
return $this->_card;
}
@@ -82,8 +115,8 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
*/
public function set_card( \IPS\nexus\CreditCard $card )
{
/* Create a customer object if we don't have one */
$profiles = $this->member->cm_profiles;
if ( !isset( $profiles[ $this->method->id ] ) )
{
$response = $this->method->api( 'customers', array(
@@ -98,11 +131,37 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
$this->member->save();
}
$response = $this->method->api( "customers/{$profiles[ $this->method->id ]}/cards", array(
'card' => isset( $card->token ) ? $card->token : NULL
/* If the source is a 3D Secure Authorization, get the actual card */
$sourceId = $card->token;
$response = $this->method->api( "sources/{$card->token}", NULL, 'get' );
if ( $response['type'] === 'three_d_secure' )
{
$sourceId = $response['three_d_secure']['card'];
}
/* Check it doesn't already exist */
$otherCards = \IPS\Db::i()->select( 'card_data', 'nexus_customer_cards', array( 'card_member=? AND card_method=?', $this->member->member_id, $this->method->id ) );
if ( count( $otherCards ) )
{
$sourceData = $this->method->api( "sources/{$sourceId}", NULL, 'get' );
if ( isset( $sourceData['card']['fingerprint'] ) )
{
foreach ( $otherCards as $otherCardId )
{
$otherCardData = $this->method->api( "sources/{$otherCardId}", NULL, 'get' );
if ( isset( $otherCardData['card']['fingerprint'] ) and $otherCardData['card']['fingerprint'] === $sourceData['card']['fingerprint'] )
{
throw new \DomainException( \IPS\Member::loggedIn()->language()->addToStack('card_is_duplicate') );
}
}
}
}
/* Save the card */
$response = $this->method->api( "customers/{$profiles[ $this->method->id ]}/sources", array(
'source' => $sourceId
) );
$this->data = $response['id'];
$this->save();
}
@@ -41,10 +41,6 @@ class _Exception extends \DomainException
{
parent::__construct( $response['message'] );
}
elseif ( $response['type'] == 'invalid_request_error' )
{
parent::__construct( \IPS\Member::loggedIn()->language()->get( 'card_number_invalid' ) );
}
else
{
parent::__construct( \IPS\Member::loggedIn()->language()->get( 'gateway_err' ) );
File diff suppressed because it is too large. Load diff