Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

@@ -451,7 +451,7 @@ class _Braintree extends \IPS\nexus\Gateway
$summary = $transaction->invoice->summary();
foreach ( $summary['items'] as $item )
{
$unitTaxAmount = $item->tax ? $item->price->amount->multiply( new \IPS\Math\Number("{$item->tax->rate( $transaction->invoice->billaddress )}") ) : new \IPS\Math\Number('0');
$unitTaxAmount = $item->tax ? $item->price->amount->multiply( new \IPS\Math\Number( number_format( "{$item->tax->rate( $transaction->invoice->billaddress )}", \IPS\nexus\Money::numberOfDecimalsForCurrency( $transaction->amount->currency ), '.', '' ) ) ) : new \IPS\Math\Number('0');
$data['lineItems'][] = array(
'description' => mb_strlen( $item->name ) > 127 ? mb_substr( $item->name, 0, 124 ) . '...' : $item->name,
'kind' => $item->price->amount->isGreaterThanZero() ? 'debit' : 'credit',
@@ -36,10 +36,21 @@ class _Payout extends \IPS\nexus\Payout
public static function settings()
{
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$return = array();
$return[] = new \IPS\Helpers\Form\Text( 'manual_name', isset( $settings['Manual'] ) ? $settings['Manual']['name'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'manual_title', isset( $settings['Manual'] ) ? $settings['Manual']['title'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'manual_name', isset( $settings['Manual'] ) ? $settings['Manual']['name'] : '' , NULL, array(), function( $val ) {
if ( !$val AND isset( \IPS\Request::i()->nexus_payout['Manual'] ) AND \IPS\Request::i()->nexus_payout['Manual'] == 1 )
{
throw new \DomainException( 'form_required' );
}
});
$return[] = new \IPS\Helpers\Form\Text( 'manual_title', isset( $settings['Manual'] ) ? $settings['Manual']['title'] : '', NULL, array(), function( $val ) {
if ( !$val AND isset( \IPS\Request::i()->nexus_payout['Manual'] ) AND \IPS\Request::i()->nexus_payout['Manual'] == 1 )
{
throw new \DomainException( 'form_required' );
}
});
return $return;
}
@@ -107,6 +107,51 @@ 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\Url\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');
try
{
$transactions = $this->method->api( $url, NULL, 'get' );
foreach ( array_reverse( $transactions['agreement_transaction_list'] ) as $t )
{
if ( $t['status'] == 'Completed' )
{
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\Url\Exception $e )
{
throw $e;
}
throw new \OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
}
/**
* Suspend
@@ -183,6 +228,11 @@ class _BillingAgreement extends \IPS\nexus\Customer\BillingAgreement
try
{
$this->_payPalData = $this->method->api( "billing/subscriptions/{$this->gw_id}", NULL, 'get' );
if( !isset( $this->_payPalData['plan_id'] ) )
{
throw new \RuntimeException;
}
}
catch ( \Exception $e )
{
@@ -471,7 +471,7 @@ class _PayPal extends \IPS\nexus\Gateway
* @return \IPS\DateTime|NULL Auth is valid until or NULL to indicate auth is good forever
* @throws \LogicException Message will be displayed to user
*/
protected function _billingAgreementAuth( \IPS\nexus\Transaction $transaction, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, \IPS\nexus\Purchase\RenewalTerm $term, $items )
protected function _billingAgreementAuth( \IPS\nexus\Transaction $transaction, ?\IPS\nexus\Fraud\MaxMind\Request $maxMind, \IPS\nexus\Purchase\RenewalTerm $term, $items )
{
$settings = json_decode( $this->settings, TRUE );
@@ -496,7 +496,7 @@ class _PayPal extends \IPS\nexus\Gateway
) );
/* Create a plan */
$renewalAmountIncludingTax = $term->cost->amount->multiply( new \IPS\Math\Number( $term->tax ? ( (string) ( 1 + $term->tax->rate( $transaction->invoice->billaddress ) ) ) : '1' ) );
$renewalAmountIncludingTax = $term->cost->amount->multiply( new \IPS\Math\Number( number_format( $term->tax ? ( ( 1 + $term->tax->rate( $transaction->invoice->billaddress ) ) ) : 1, 4, '.', '' ) ) );
$billingCycle = array(
'pricing_scheme' => array(
'fixed_price' => array(
@@ -676,6 +676,37 @@ class _PayPal extends \IPS\nexus\Gateway
*/
public function capture( \IPS\nexus\Transaction $transaction )
{
/* If this is for a billing agreement, it should have automatically been captured - all we need to do is check its status, and if
it has been captured, record the ID - otherwise return an error explaining what's going on.
The only time we normally get here is if its the initial transaction and PayPal didn't take payment before the user was redirected back
(in which case the status will be STATUS_GATEWAY_PENDING) but if has been an issue (with Maxmind, for example) it could be STATUS_HELD */
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 )
{
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' )
{
$transaction->gw_id = $t['transaction_id'];
$transaction->save();
return TRUE;
}
elseif ( $t['status'] == 'Refunded' )
{
$response = 'RFND';
}
elseif ( $t['status'] == 'Failed' )
{
$response = 'FAIL';
}
}
}
throw new \RuntimeException( $response );
}
/* Try to find the authorization ID */
if ( mb_substr( $transaction->gw_id, 0, 4 ) === 'PAY-' )
{
@@ -760,6 +760,13 @@ class _Stripe extends \IPS\nexus\Gateway
{
return;
}
/* PaymentIntent returns a different error for an already captured payment */
elseif( $e->details['code'] == 'payment_intent_unexpected_state'
AND mb_strpos( $e->details['message'], 'already been captured' ) !== FALSE
AND $e->details['payment_intent']['amount_capturable'] === 0 )
{
return;
}
throw $e;
}