Version 4.7.13

This commit is contained in:
Neo committed 2025-12-19 16:14:56 -08:00
1 parent fd22d99e69
commit 7124a02564
954 files changed
+29277 -59308

No files matched your search

@@ -12,6 +12,9 @@
namespace IPS\nexus\Gateway\PayPal;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Settings;
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
@@ -33,9 +36,8 @@ class _Payout extends \IPS\nexus\Payout
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$return = array();
$return[] = new \IPS\Helpers\Form\Text( 'paypal_api_username', isset( $settings['PayPal'] ) ? $settings['PayPal']['api_username'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'paypal_api_password', isset( $settings['PayPal'] ) ? $settings['PayPal']['api_password'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'paypal_api_signature', isset( $settings['PayPal'] ) ? $settings['PayPal']['api_signature'] : '' );
$return[] = new \IPS\Helpers\Form\Text( 'paypal_client_id', ( isset( $settings['PayPal']['client_id'] ) AND isset( $settings['PayPal']['client_id'] ) AND $settings['PayPal']['client_id'] ) ? $settings['PayPal']['client_id'] : '', NULL );
$return[] = new \IPS\Helpers\Form\Text( 'paypal_secret', ( isset( $settings['PayPal']['secret'] ) AND $settings['PayPal']['secret'] ) ? $settings['PayPal']['secret'] : '', NULL );
return $return;
}
@@ -77,28 +79,60 @@ class _Payout extends \IPS\nexus\Payout
*/
public function process()
{
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$response = \IPS\Http\Url::external( \IPS\NEXUS_TEST_GATEWAYS ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp' )->request()->post( array(
'METHOD' => 'MassPay',
'VERSION' => '90',
'USER' => $settings['PayPal']['api_username'],
'PWD' => $settings['PayPal']['api_password'],
'SIGNATURE' => $settings['PayPal']['api_signature'],
'RECEIVERTYPE' => 'EmailAddress',
'CURRENCYCODE' => $this->amount->currency,
'L_EMAIL0' => $this->data,
'L_AMT0' => $this->amount->amountAsString()
) )->decodeQueryString();
static::checkToken();
if ( $response['ACK'] != 'Success' )
$settings = json_decode( \IPS\Settings::i()->nexus_payout, true );
$data = array(
'items' => array(
array(
'amount' => array(
'currency' => $this->amount->currency,
'value' => $this->amount->amountAsString()
),
'receiver' => $this->data,
'recipient_type' => 'EMAIL'
)
),
'sender_batch_header' => array(
'recipient_type' => 'EMAIL',
'sender_batch_id' => "Payout " . \IPS\DateTime::create()->rfc3339()
)
);
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/payments/payouts' )
->request()
->forceTls()
->setHeaders( array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $settings['PayPal']['token']
) )
->post( json_encode( $data ) );
$json = $response->decodeJson();
if( $response->httpResponseCode != 201 )
{
throw new \DomainException( $response['L_LONGMESSAGE0'] );
throw new \IPS\Http\Request\Exception( $json['message'] );
}
else
{
$this->status = static::STATUS_COMPLETE;
$this->completed = new \IPS\DateTime;
if( isset( $json['batch_header'] ) AND $json['batch_header']['payout_batch_id'] )
{
$this->gw_id = $json['batch_header']['payout_batch_id'];
switch( $json['batch_header']['batch_status'] )
{
case 'DENIED':
case 'CANCELED':
$this->status = static::STATUS_CANCELED;
break;
case 'SUCCESS':
$this->status = static::STATUS_COMPLETE;
break;
default:
$this->status = static::STATUS_PROCESSING;
break;
}
$this->save();
}
}
@@ -112,57 +146,167 @@ class _Payout extends \IPS\nexus\Payout
*/
public static function massProcess( \IPS\Patterns\ActiveRecordIterator $payouts )
{
/* Make sure we check the token first so that we have proper authorization to API calls */
static::checkToken();
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$byCurrency = array();
foreach ( $payouts as $payout )
/* Build a batch of payout items */
$payoutIds = $payoutData = [];
foreach( $payouts as $payout )
{
$byCurrency[ $payout->amount->currency ][] = array( 'email' => $payout->data, 'amount' => $payout->amount->amountAsString() );
$payoutIds[ $payout->amount->currency ] = $payout->id;
$payoutData[ $payout->amount->currency ][] = [
'amount' => [
'currency' => $payout->amount->currency,
'value' => $payout->amount->amountAsString()
],
'receiver' => $payout->data,
'recipient_type' => 'EMAIL'
];
}
$batches = array();
foreach ( $byCurrency as $currency => $recipients )
foreach( $payoutData as $currency => $batchData )
{
$baseData = array(
'METHOD' => 'MassPay',
'VERSION' => '90',
'USER' => $settings['PayPal']['api_username'],
'PWD' => $settings['PayPal']['api_password'],
'SIGNATURE' => $settings['PayPal']['api_signature'],
'RECEIVERTYPE' => 'EmailAddress',
'CURRENCYCODE' => $currency,
);
$data = $baseData;
foreach ( $recipients as $i => $recipient )
$requestData = [
'items' => $batchData,
'sender_batch_header' => [
'recipient_type' => 'EMAIL',
'sender_batch_id' => "Payout-{$currency}-" . \IPS\DateTime::create()->rfc3339()
]
];
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/payments/payouts' )
->request()
->forceTls()
->setHeaders( [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $settings['PayPal']['token']
] )
->post( json_encode( $requestData ) );
if( $response->httpResponseCode != 201 )
{
$data["L_EMAIL{$i}"] = $recipient['email'];
$data["L_AMT{$i}"] = $recipient['amount'];
if ( $i >= 249 )
throw new \RuntimeException( (string) $response, $response->httpResponseCode );
}
$response = $response->decodeJson();
if( isset( $response['batch_header'] ) AND $response['batch_header']['payout_batch_id'] )
{
$update = [
'po_gw_id' => $response['batch_header']['payout_batch_id']
];
switch( $response['batch_header']['batch_status'] )
{
$batches[] = $data;
$data = $baseData;
case 'DENIED':
case 'CANCELED':
$update['po_status'] = static::STATUS_CANCELED;
break;
case 'SUCCESS':
$update['po_status'] = static::STATUS_COMPLETE;
break;
default:
$update['po_status'] = static::STATUS_PROCESSING;
break;
}
\IPS\Db::i()->update( 'nexus_payouts', $update, \IPS\Db::i()->in( 'po_id', $payoutIds[ $currency ] ) );
}
$batches[] = $data;
}
$error = NULL;
foreach ( $batches as $batchData )
}
/**
* @brief cache batch results for further lookup
*/
protected static array $_batchCache = [];
/**
* Check the status of a payout batch and update the withdrawal requests accordingly
*
* @param string $batchId
* @return string|NULL
*/
public static function checkStatus( string $batchId ):? string
{
if( isset( static::$_batchCache[ $batchId ] ) )
{
$response = \IPS\Http\Url::external( \IPS\NEXUS_TEST_GATEWAYS ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp' )->request()->post( $batchData )->decodeQueryString();
if ( $response['ACK'] != 'Success' )
return static::$_batchCache[ $batchId ];
}
/* Make sure we check the token first so that we have proper authorization to API calls */
static::checkToken();
$settings = json_decode( \IPS\Settings::i()->nexus_payout, TRUE );
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/payments/payouts/' . $batchId )
->request()
->forceTls()
->setHeaders( array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $settings['PayPal']['token']
) )
->get()
->decodeJson();
if( isset( $response['batch_header'] ) AND $response['batch_header']['payout_batch_id'] )
{
switch( $response['batch_header']['batch_status'] )
{
$error = $response['L_LONGMESSAGE0'];
case 'DENIED':
case 'CANCELED':
return static::$_batchCache[ $batchId ] = static::STATUS_CANCELED;
break;
case 'SUCCESS':
return static::$_batchCache[ $batchId ] = static::STATUS_COMPLETE;
break;
default:
return static::$_batchCache[ $batchId ] = static::STATUS_PROCESSING;
break;
}
}
if ( $error )
return NULL;
}
/**
* Get Token
*
* @return void
* @throws \IPS\Http\Request\Exception
* @throws \UnexpectedValueException
*/
protected static function checkToken()
{
$payoutSettings = json_decode( \IPS\Settings::i()->nexus_payout, true );
$settings = $payoutSettings['PayPal'];
if ( !isset( $settings['token'] ) or $settings['token_expire'] < time() )
{
throw new \DomainException( $error );
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/oauth2/token' )
->request()
->forceTls()
->setHeaders( array(
'Accept' => 'application/json',
'Accept-Language' => 'en_US',
) )
->login( $settings['client_id'], $settings['secret'] )
->post( array( 'grant_type' => 'client_credentials' ) )
->decodeJson();
if ( !isset( $response['access_token'] ) )
{
throw new \UnexpectedValueException( isset( $response['error_description'] ) ? $response['error_description'] : $response );
}
$settings['token'] = $response['access_token'];
$settings['token_expire'] = ( time() + $response['expires_in'] );
$payoutSettings['PayPal'] = $settings;
\IPS\Settings::i()->changeValues( array(
'nexus_payout' => json_encode( $payoutSettings )
) );
}
}
}