Version 4.7.13
This commit is contained in:
1 parent
fd22d99e69
commit
7124a02564
954 files changed
+29277
-59308
No files matched your search
@@ -85,7 +85,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
|
||||
$cardType = 'amex';
|
||||
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 ) )
|
||||
@@ -99,7 +99,7 @@ class _CreditCard extends \IPS\nexus\Customer\CreditCard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Save */
|
||||
$response = $this->method->api( 'vault/credit-card', array(
|
||||
'number' => $card->number,
|
||||
|
||||
@@ -94,7 +94,11 @@ class _Exception extends \DomainException
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( $refund )
|
||||
if( isset( $details ) AND $details[0]['issue'] === 'INSTRUMENT_DECLINED' )
|
||||
{
|
||||
$message = \IPS\Member::loggedIn()->language()->get( 'payment_refused' );
|
||||
}
|
||||
elseif ( $refund )
|
||||
{
|
||||
$message = \IPS\Member::loggedIn()->language()->get( 'refund_failed' );
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
public function checkValidity( \IPS\nexus\Money $amount, \IPS\GeoLocation $billingAddress = NULL, \IPS\nexus\Customer $customer = NULL, $recurrings = array() )
|
||||
{
|
||||
$settings = json_decode( $this->settings, TRUE );
|
||||
|
||||
|
||||
/* Card payments require name and billing address */
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' and ( !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' and ( !$customer->cm_first_name or !$customer->cm_last_name or !$billingAddress ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Check transaction limit */
|
||||
switch ( $amount->currency )
|
||||
{
|
||||
@@ -207,6 +207,22 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
/* Pass to parent */
|
||||
return parent::checkValidity( $amount, $billingAddress, $customer, $recurrings );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Node Description
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__description()
|
||||
{
|
||||
$settings = json_decode( $this->settings, TRUE );
|
||||
if( isset( $settings['type'] ) and $settings['type'] === 'card' )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'gateway_deprecated', FALSE, array( 'sprintf' => 'PayPal Credit Card' ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can store cards?
|
||||
@@ -258,6 +274,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
public function paymentScreen( \IPS\nexus\Invoice $invoice, \IPS\nexus\Money $amount, \IPS\nexus\Customer $member = NULL, $recurrings = array(), $type = 'checkout' )
|
||||
{
|
||||
$settings = json_decode( $this->settings, TRUE );
|
||||
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' )
|
||||
{
|
||||
return array( 'card' => new \IPS\nexus\Form\CreditCard( $this->id . '_card', NULL, TRUE, array(
|
||||
@@ -285,12 +302,13 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
* @throws \LogicException Message will be displayed to user
|
||||
*/
|
||||
public function auth( \IPS\nexus\Transaction $transaction, $values, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, $recurrings = array(), $source = NULL )
|
||||
{
|
||||
{
|
||||
/* We need a transaction ID */
|
||||
$transaction->save();
|
||||
|
||||
|
||||
/* Do it */
|
||||
$settings = json_decode( $this->settings, TRUE );
|
||||
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' )
|
||||
{
|
||||
return $this->_cardAuth( \is_array( $values ) ? $values[ $this->id . '_card' ] : $values, $transaction, $maxMind );
|
||||
@@ -306,24 +324,25 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
return $this->_billingAgreementAuth( $transaction, $maxMind, $recurrance['term'], $recurrance['items'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
return $this->_paypalAuth( $transaction, $maxMind );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Authorize Card Payment
|
||||
*
|
||||
* @param \IPS\nexus\CreditCard|\IPS\nexus\Customer\CreditCard $card The card to charge
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
|
||||
* @param \IPS\nexus\Fraud\MaxMind\Request|NULL $maxMind *If* MaxMind is enabled, the request object will be passed here so gateway can additional data before request is made
|
||||
* @param string|NULL $source 'checkout' if the customer is doing this at a normal checkout, 'renewal' is an automatically generated renewal invoice, 'manual' is admin manually charging. NULL is unknown
|
||||
* @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 _cardAuth( $card, \IPS\nexus\Transaction $transaction, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
|
||||
protected function _cardAuth( $card, \IPS\nexus\Transaction $transaction, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL, ?string $source = NULL )
|
||||
{
|
||||
/* Stored Card */
|
||||
/* Stored Card */
|
||||
if ( $card instanceof \IPS\nexus\Customer\CreditCard )
|
||||
{
|
||||
$payer = array(
|
||||
@@ -346,7 +365,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
\IPS\Session::i()->setMember( $transaction->member );
|
||||
\IPS\Member\Device::loadOrCreate( $transaction->member, FALSE )->updateAfterAuthentication( NULL );
|
||||
}
|
||||
|
||||
|
||||
if ( $maxMind )
|
||||
{
|
||||
$maxMind->setCard( $card );
|
||||
@@ -380,23 +399,31 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
'cvv2' => $card->ccv,
|
||||
'first_name' => $this->_getFirstName( $transaction ),
|
||||
'last_name' => $this->_getLastName( $transaction ),
|
||||
'billing_address' => $this->_getAddress( $transaction->invoice->billaddress, $transaction->member )
|
||||
'billing_address' => $this->_getAddress( $transaction->invoice->billaddress, $transaction->member, 'card' )
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* Send the request */
|
||||
$response = $this->api( 'payments/payment', array(
|
||||
'intent' => 'authorize',
|
||||
'payer' => $payer,
|
||||
'transactions' => array( $this->_getTransactions( $transaction ) ),
|
||||
'redirect_urls' => array(
|
||||
'return_url' => \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/paypal.php?nexusTransactionId=' . $transaction->id,
|
||||
'cancel_url' => (string) $transaction->invoice->checkoutUrl(),
|
||||
)
|
||||
) );
|
||||
|
||||
try
|
||||
{
|
||||
/* Send the request */
|
||||
$response = $this->api( 'payments/payment', array(
|
||||
'intent' => 'authorize',
|
||||
'payer' => $payer,
|
||||
'transactions' => array( $this->_getTransactions( $transaction ) ),
|
||||
'redirect_urls' => array(
|
||||
'return_url' => \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/paypal.php?nexusTransactionId=' . $transaction->id,
|
||||
'cancel_url' => (string) $transaction->invoice->checkoutUrl(),
|
||||
)
|
||||
) );
|
||||
}
|
||||
catch( \IPS\nexus\Gateway\PayPal\Exception $e )
|
||||
{
|
||||
$this->processException( $transaction, $e );
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/* Set transaction data */
|
||||
$transaction->gw_id = $response['transactions'][0]['related_resources'][0]['authorization']['id']; // The transaction ID for the authorization. At capture, it will be updated again to the capture transaction ID
|
||||
@@ -430,15 +457,32 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
protected function _paypalAuth( \IPS\nexus\Transaction $transaction, \IPS\nexus\Fraud\MaxMind\Request $maxMind = NULL )
|
||||
{
|
||||
/* Send the request */
|
||||
$response = $this->api( 'payments/payment', array(
|
||||
'intent' => 'authorize',
|
||||
'payer' => array( 'payment_method' => 'paypal' ),
|
||||
'transactions' => array( $this->_getTransactions( $transaction ) ),
|
||||
'redirect_urls' => array(
|
||||
'return_url' => \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/paypal.php?nexusTransactionId=' . $transaction->id,
|
||||
'cancel_url' => (string) $transaction->invoice->checkoutUrl(),
|
||||
)
|
||||
) );
|
||||
try
|
||||
{
|
||||
$response = $this->api( 'checkout/orders', array(
|
||||
'intent' => 'AUTHORIZE',
|
||||
'purchase_units' => $this->_getPurchaseUnits( $transaction ),
|
||||
'payment_source' => array(
|
||||
'paypal' => array(
|
||||
'experience_context' => array(
|
||||
'return_url' => \IPS\Settings::i()->base_url . 'applications/nexus/interface/gateways/paypal.php?nexusTransactionId=' . $transaction->id,
|
||||
'cancel_url' => (string) $transaction->invoice->checkoutUrl(),
|
||||
),
|
||||
'email_address' => $transaction->member->email,
|
||||
'name' => array(
|
||||
'given_name' => $this->_getFirstName( $transaction ),
|
||||
'surname' => $this->_getLastName( $transaction )
|
||||
),
|
||||
'address' => ( $transaction->invoice->billaddress instanceof \IPS\GeoLocation ) ? $this->_getAddress( $transaction->invoice->billaddress, $transaction->member ) : NULL,
|
||||
)
|
||||
)
|
||||
), 'POST', true, null, md5( (string) $transaction->invoice->checkoutUrl() . ';' . $transaction->id ), 2 );
|
||||
}
|
||||
catch( \IPS\nexus\Gateway\PayPal\Exception $e )
|
||||
{
|
||||
$this->processException( $transaction, $e );
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/* Set transaction data */
|
||||
$transaction->gw_id = $response['id']; // This is a payment ID ("PAY-XXX"). At this time we do not have a real transaction ID
|
||||
@@ -447,11 +491,12 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
/* Redirect */
|
||||
foreach ( $response['links'] as $link )
|
||||
{
|
||||
if ( $link['rel'] === 'approval_url' )
|
||||
if ( $link['rel'] === 'payer-action' )
|
||||
{
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::external( $link['href'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
|
||||
@@ -471,10 +516,17 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
|
||||
/* Work out the name */
|
||||
$titles = array();
|
||||
$initialTerm = null;
|
||||
foreach ( $items as $item )
|
||||
{
|
||||
$titles[] = ( $item->name . ( $item->quantity > 1 ? " x{$item->quantity}" : '' ) );
|
||||
|
||||
if( isset( $item->initialInterval ) AND $item->initialInterval instanceof \DateInterval )
|
||||
{
|
||||
$initialTerm = $item->initialInterval;
|
||||
}
|
||||
}
|
||||
|
||||
$title = implode( ', ', $titles );
|
||||
if ( mb_strlen( $title ) > 127 )
|
||||
{
|
||||
@@ -488,49 +540,51 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
'description' => $description,
|
||||
'type' => 'DIGITAL',
|
||||
) );
|
||||
|
||||
/* Create a plan */
|
||||
|
||||
/* Set up the billing cycles */
|
||||
$billingCycles = array();
|
||||
$sequence = 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(
|
||||
|
||||
/* If we have an initial term that is different from the renewal, start with that */
|
||||
if( $initialTerm instanceof \DateInterval OR $transaction->amount->amount->compare( $renewalAmountIncludingTax ) !== 0 )
|
||||
{
|
||||
$intervalToUse = $initialTerm instanceof \DateInterval ? $initialTerm : $term->interval;
|
||||
$billingCycles[] = array(
|
||||
'pricing_scheme' => array(
|
||||
'fixed_price' => array(
|
||||
'currency_code' => $transaction->amount->currency,
|
||||
'value' => $transaction->amount->amountAsString()
|
||||
),
|
||||
),
|
||||
'frequency' => static::_getFrequencyFromInterval( $intervalToUse ),
|
||||
'tenure_type' => 'TRIAL',
|
||||
'sequence' => $sequence,
|
||||
'total_cycles' => 1
|
||||
);
|
||||
$sequence++;
|
||||
}
|
||||
|
||||
/* And now the regular renewals */
|
||||
$billingCycles[] = array(
|
||||
'pricing_scheme' => array(
|
||||
'fixed_price' => array(
|
||||
'currency_code' => $term->cost->currency,
|
||||
'value' => ( new \IPS\nexus\Money( $renewalAmountIncludingTax, $term->cost->currency ) )->amountAsString()
|
||||
),
|
||||
),
|
||||
'frequency' => array(),
|
||||
'frequency' => static::_getFrequencyFromInterval( $term->interval ),
|
||||
'tenure_type' => 'REGULAR',
|
||||
'sequence' => 1,
|
||||
'sequence' => $sequence,
|
||||
'total_cycles' => 0
|
||||
);
|
||||
if ( $term->interval->y )
|
||||
{
|
||||
$billingCycle['frequency']['interval_unit'] = 'YEAR';
|
||||
$billingCycle['frequency']['interval_count'] = $term->interval->y;
|
||||
}
|
||||
elseif ( $term->interval->m )
|
||||
{
|
||||
$billingCycle['frequency']['interval_unit'] = 'MONTH';
|
||||
$billingCycle['frequency']['interval_count'] = $term->interval->m;
|
||||
}
|
||||
elseif ( $term->interval->d )
|
||||
{
|
||||
if ( $term->interval->d % 7 == 0 )
|
||||
{
|
||||
$billingCycle['frequency']['interval_unit'] = 'WEEK';
|
||||
$billingCycle['frequency']['interval_count'] = $term->interval->d / 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
$billingCycle['frequency']['interval_unit'] = 'DAY';
|
||||
$billingCycle['frequency']['interval_count'] = $term->interval->d;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a plan */
|
||||
$planDetails = array(
|
||||
'product_id' => $product['id'],
|
||||
'name' => $title,
|
||||
'description' => $description,
|
||||
'billing_cycles' => array(),
|
||||
'billing_cycles' => $billingCycles,
|
||||
'payment_preferences' => array(
|
||||
'auto_bill_outstanding' => FALSE,
|
||||
'payment_failure_threshold' => \intval( $settings['billing_agreement_allowed_fails'] )
|
||||
@@ -541,28 +595,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
),
|
||||
'quantity_supported' => FALSE
|
||||
);
|
||||
if ( $transaction->amount->amount->compare( $renewalAmountIncludingTax ) === 0 )
|
||||
{
|
||||
$planDetails['billing_cycles'][] = $billingCycle;
|
||||
}
|
||||
else
|
||||
{
|
||||
$planDetails['billing_cycles'][] = array(
|
||||
'pricing_scheme' => array(
|
||||
'fixed_price' => array(
|
||||
'currency_code' => $transaction->amount->currency,
|
||||
'value' => $transaction->amount->amountAsString()
|
||||
),
|
||||
),
|
||||
'frequency' => $billingCycle['frequency'],
|
||||
'tenure_type' => 'TRIAL',
|
||||
'sequence' => 1,
|
||||
'total_cycles' => 1
|
||||
);
|
||||
|
||||
$billingCycle['sequence'] = 2;
|
||||
$planDetails['billing_cycles'][] = $billingCycle;
|
||||
}
|
||||
|
||||
$plan = $this->api( 'billing/plans', $planDetails );
|
||||
|
||||
/* Create a subscription */
|
||||
@@ -611,12 +644,18 @@ 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
|
||||
/* If this is the initial transaction for a billing agreement which hasn't
|
||||
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();
|
||||
}
|
||||
|
||||
/* If the transaction is not in a 'hold' state, it cannot be voided and must be refunded instead */
|
||||
if( !\in_array( $transaction->status, array( $transaction::STATUS_HELD, $transaction::STATUS_REVIEW ) ) || !$transaction->auth )
|
||||
{
|
||||
return $this->refund( $transaction );
|
||||
}
|
||||
|
||||
/* Try to find the authorization ID */
|
||||
if ( mb_substr( $transaction->gw_id, 0, 4 ) === 'PAY-' )
|
||||
@@ -646,33 +685,26 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
}
|
||||
else
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
if( $transaction->billing_agreement )
|
||||
{
|
||||
try
|
||||
{
|
||||
$sale = $this->api( "payments/sale/{$transaction->gw_id}", NULL, 'get' );
|
||||
return $this->refund( $transaction ); // "Sales" came from Billing Agreements and have already been captured
|
||||
return $this->refund( $transaction );
|
||||
}
|
||||
catch ( \IPS\nexus\Gateway\PayPal\Exception $e )
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
catch ( \IPS\nexus\Gateway\PayPal\Exception $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
/* Void it */
|
||||
return $this->api( "payments/authorization/{$authId}/void" );
|
||||
return $this->api( "payments/authorizations/{$authId}/void", null, 'POST', TRUE, NULL, NULL, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @return void
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function capture( \IPS\nexus\Transaction $transaction )
|
||||
@@ -757,45 +789,34 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
}
|
||||
else
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
if( $transaction->billing_agreement )
|
||||
{
|
||||
try
|
||||
{
|
||||
$sale = $this->api( "payments/sale/{$transaction->gw_id}", NULL, 'get' );
|
||||
$sale = $this->api( "payments/authorizations/{$authId}", NULL, 'get', TRUE, NULL, NULL, 2 );
|
||||
return TRUE; // "Sales" came from Billing Agreements and have already been captured
|
||||
}
|
||||
catch ( \IPS\nexus\Gateway\PayPal\Exception $e )
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$authId = $transaction->gw_id;
|
||||
catch ( \IPS\nexus\Gateway\PayPal\Exception $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture it */
|
||||
try
|
||||
{
|
||||
$response = $this->api( "payments/authorization/{$authId}/capture", array(
|
||||
'amount' => array(
|
||||
'currency' => $transaction->amount->currency,
|
||||
'total' => $transaction->amount->amountAsString(),
|
||||
),
|
||||
'is_final_capture' => TRUE,
|
||||
) );
|
||||
$response = $this->api( "payments/authorizations/{$authId}/capture", NULL, 'POST', TRUE, NULL, NULL, 2 );
|
||||
$transaction->gw_id = $response['id']; // We now set the gateway ID to the capture ID
|
||||
$transaction->save();
|
||||
}
|
||||
catch ( \IPS\nexus\Gateway\PayPal\Exception $e )
|
||||
{
|
||||
if ( $e->getName() == 'AUTHORIZATION_ALREADY_COMPLETED' )
|
||||
if ( $e->getName() == 'ORDER_ALREADY_AUTHORIZED' )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -816,7 +837,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
$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-' )
|
||||
if( $transaction->billing_agreement AND $transaction->gw_id AND mb_substr( $transaction->billing_agreement->gw_id, 0, 2 ) !== 'I-' )
|
||||
{
|
||||
$response = $this->api( "payments/sale/{$transaction->gw_id}/refund", array( 'amount' => array(
|
||||
'currency' => $amount->currency,
|
||||
@@ -855,10 +876,13 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
}
|
||||
|
||||
/* Process Refund */
|
||||
$response = $this->api( "payments/capture/{$captureId}/refund", array( 'amount' => array(
|
||||
'currency' => $amount->currency,
|
||||
'total' => $amount->amountAsString()
|
||||
) ) );
|
||||
$response = $this->api( "payments/captures/{$captureId}/refund", array(
|
||||
'amount' => array(
|
||||
'currency_code' => $amount->currency,
|
||||
'value' => $amount->amountAsString()
|
||||
),
|
||||
'invoice_id' => $transaction->invoice->id
|
||||
), 'POST', TRUE, NULL, NULL, 2 );
|
||||
return $response['id'];
|
||||
}
|
||||
|
||||
@@ -884,9 +908,10 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
public function settings( &$form )
|
||||
{
|
||||
$settings = json_decode( $this->settings, TRUE );
|
||||
|
||||
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' )
|
||||
{
|
||||
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'gateway_deprecated', FALSE, array( 'sprintf' => 'PayPal Credit Card' ) ), 'ipsMessage ipsMessage_warning' );
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'paypal_type', $settings['type'], TRUE, array( 'options' => array( 'paypal' => 'paypal_type_paypal', 'card' => 'paypal_type_card' ), 'toggles' => array( 'paypal' => array( 'paypal_billing_agreements' ), 'card' => array( 'paypal_vault' ) ) ) ) );
|
||||
}
|
||||
|
||||
@@ -911,12 +936,12 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
}, NULL, NULL, 'paypal_billing_agreements' ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Number( 'paypal_billing_agreement_allowed_fails', ( $this->id AND isset( $settings['billing_agreement_allowed_fails'] ) ) ? $settings['billing_agreement_allowed_fails'] : 0, FALSE, array( 'unlimited' => 0, 'min' => 1 ), NULL, \IPS\Member::loggedIn()->language()->addToStack('paypal_billing_agreement_allowed_fails_prefix'), \IPS\Member::loggedIn()->language()->addToStack('paypal_billing_agreement_allowed_fails_suffix'), 'paypal_billing_agreement_allowed_fails' ) );
|
||||
|
||||
|
||||
if ( isset( $settings['type'] ) and $settings['type'] === 'card' )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'paypal_vault', ( $this->id and isset( $settings['vault'] ) ) ? $settings['vault'] : TRUE, FALSE, array(), NULL, NULL, NULL, 'paypal_vault' ) );
|
||||
}
|
||||
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'paypal_client_id', $settings['client_id'], TRUE ) );
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'paypal_secret', $settings['secret'], TRUE ) );
|
||||
}
|
||||
@@ -989,11 +1014,13 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
* @param array $data The data to send
|
||||
* @param string $method Method (get/post)
|
||||
* @param array|NULL $settings Settings (NULL for saved setting)
|
||||
* @param string|Null $requestId
|
||||
* @param int|Null $version API Version
|
||||
* @return array|null
|
||||
* @throws \IPS\Http|Exception
|
||||
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
||||
*/
|
||||
public function api( $uri, $data=NULL, $method='post', $expectResponse=TRUE, $settings=NULL )
|
||||
public function api( $uri, $data=NULL, $method='post', $expectResponse=TRUE, $settings=NULL, $requestId=NULL, $version=NULL )
|
||||
{
|
||||
if ( !$settings )
|
||||
{
|
||||
@@ -1007,14 +1034,18 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
/* Some API calls (e.g. for billing agreements) still use v1 for the REST APIs */
|
||||
$version = $version ?: 1;
|
||||
|
||||
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/' . $uri )
|
||||
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api-m.paypal.com' ) . '/v' . $version . '/' . $uri )
|
||||
->request( \IPS\LONG_REQUEST_TIMEOUT )
|
||||
->forceTls()
|
||||
->setHeaders( array(
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => "Bearer {$settings['token']}",
|
||||
'PayPal-Partner-Attribution-Id' => 'InvisionPower_SP'
|
||||
'PayPal-Partner-Attribution-Id' => 'InvisionPower_SP',
|
||||
'PayPal-Request-Id' => $requestId
|
||||
) )
|
||||
->$method( $data === NULL ? NULL : json_encode( $data ) );
|
||||
|
||||
@@ -1038,14 +1069,14 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
*
|
||||
* @param array|NULL $settings Settings (NULL for saved setting)
|
||||
* @return array
|
||||
* @throws \IPS\Http|Exception
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function getNewToken( $settings = NULL )
|
||||
{
|
||||
$settings = $settings ?: json_decode( $this->settings, TRUE );
|
||||
|
||||
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api.sandbox.paypal.com' : 'api.paypal.com' ) . '/v1/oauth2/token' )
|
||||
$response = \IPS\Http\Url::external( 'https://' . ( \IPS\NEXUS_TEST_GATEWAYS ? 'api-m.sandbox.paypal.com' : 'api-m.paypal.com' ) . '/v1/oauth2/token' )
|
||||
->request()
|
||||
->forceTls()
|
||||
->setHeaders( array(
|
||||
@@ -1063,14 +1094,63 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the PayPal exception and mark the transaction as failed
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction
|
||||
* @param \Exception $e
|
||||
* @return void
|
||||
*/
|
||||
public function processException( \IPS\nexus\Transaction $transaction, \Exception $e )
|
||||
{
|
||||
/* Make sure it's a PayPal exception only. The interface might send us a different exception type */
|
||||
if( ! ( $e instanceof \IPS\nexus\Gateway\PayPal\Exception ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark the transaction as failed */
|
||||
$transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED;
|
||||
|
||||
$details = json_decode( $e->extraLogData(), true );
|
||||
|
||||
/* Log the error message to the transaction history */
|
||||
$extra = $transaction->extra;
|
||||
$extra['history'][] = array( 's' => \IPS\nexus\Transaction::STATUS_REFUSED, 'on' => time(), 'noteRaw' => ( $details['details'][0]['description'] ?? $e->getMessage() ) );
|
||||
|
||||
if( isset( $details['processor_response'] ) )
|
||||
{
|
||||
$extra['processor_response'] = $details['processor_response'];
|
||||
|
||||
// log this failure to the customer history
|
||||
$responseCode = $details['processor_response']['response_code'];
|
||||
if( \is_numeric( $responseCode ) )
|
||||
{
|
||||
$responseCode = (int) $responseCode;
|
||||
}
|
||||
if( $transaction->member->language()->checkKeyExists( 'processor_response_code__' . $responseCode ) )
|
||||
{
|
||||
$failureReason = $transaction->member->language()->get( 'processor_response_code__' . $responseCode );
|
||||
$transaction->member->logHistory( 'nexus', 'custom', array(
|
||||
'message' => \sprintf( $transaction->member->language()->get( 'history_payment_rejected' ), $failureReason )
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
$transaction->extra = $extra;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address for PayPal
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @param \IPS\nexus\Customer $customer
|
||||
* @param string $paymentType
|
||||
* @return array
|
||||
*/
|
||||
protected function _getAddress( \IPS\GeoLocation $address, \IPS\nexus\Customer $customer )
|
||||
protected function _getAddress( \IPS\GeoLocation $address, \IPS\nexus\Customer $customer, $paymentType='paypal' )
|
||||
{
|
||||
/* PayPal requires short codes for states */
|
||||
$state = $address->region;
|
||||
@@ -1085,21 +1165,41 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Construct */
|
||||
$address = array(
|
||||
'line1' => $address->addressLines[0],
|
||||
'line2' => isset( $address->addressLines[1] ) ? $address->addressLines[1] : '',
|
||||
'city' => $address->city,
|
||||
'country_code' => $address->country,
|
||||
'postal_code' => $address->postalCode,
|
||||
'state' => $state,
|
||||
);
|
||||
|
||||
/* Add phone number */
|
||||
if ( $customer->cm_phone )
|
||||
if( $paymentType == 'card' )
|
||||
{
|
||||
$address['phone'] = preg_replace( '/[^\+0-9\s]/', '', $customer->cm_phone );
|
||||
/* Construct */
|
||||
$address = array(
|
||||
'line1' => $address->addressLines[0],
|
||||
'line2' => isset( $address->addressLines[1] ) ? $address->addressLines[1] : '',
|
||||
'city' => $address->city,
|
||||
'country_code' => $address->country,
|
||||
'postal_code' => $address->postalCode,
|
||||
'state' => $state,
|
||||
);
|
||||
|
||||
/* Add phone number */
|
||||
if ( $customer->cm_phone )
|
||||
{
|
||||
$address['phone'] = preg_replace( '/[^\+0-9\s]/', '', $customer->cm_phone );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Construct */
|
||||
$address = array(
|
||||
'address_line_1' => $address->addressLines[0],
|
||||
'address_line_2' => isset( $address->addressLines[1] ) ? $address->addressLines[1] : '',
|
||||
'admin_area_2' => $address->city,
|
||||
'country_code' => $address->country,
|
||||
'postal_code' => $address->postalCode,
|
||||
'admin_area_1' => $state,
|
||||
);
|
||||
|
||||
if( empty( $address['address_line_2'] ) )
|
||||
{
|
||||
unset( $address['address_line_2'] );
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
@@ -1110,7 +1210,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
* Get first name for PayPal
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
protected function _getFirstName( \IPS\nexus\Transaction $transaction )
|
||||
{
|
||||
@@ -1121,12 +1221,102 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
* Get last name for PayPal
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @return array
|
||||
* @return string
|
||||
*/
|
||||
protected function _getLastName( \IPS\nexus\Transaction $transaction )
|
||||
{
|
||||
return $transaction->invoice->member->member_id ? $transaction->invoice->member->cm_last_name : $transaction->invoice->guest_data['member']['cm_last_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data for PayPal
|
||||
*
|
||||
* @param \IPS\nexus\Transaction $transaction Transaction
|
||||
* @return array
|
||||
*/
|
||||
protected function _getPurchaseUnits( \IPS\nexus\Transaction $transaction )
|
||||
{
|
||||
/* Init */
|
||||
$payPalTransactionData = array(
|
||||
'amount' => array(
|
||||
'currency_code' => $transaction->amount->currency,
|
||||
'value' => $transaction->amount->amountAsString(),
|
||||
),
|
||||
'custom_id'=> \IPS\Settings::i()->site_secret_key . '-' . $transaction->id,
|
||||
'invoice_id' => $transaction->invoice->id,
|
||||
'items' => array()
|
||||
);
|
||||
|
||||
/* If we're paying the whole invoice, we can add item data... */
|
||||
if ( $transaction->amount->amount->compare( $transaction->invoice->total->amount ) === 0 )
|
||||
{
|
||||
$summary = $transaction->invoice->summary();
|
||||
|
||||
/* Shipping / Tax */
|
||||
$payPalTransactionData['amount']['breakdown'] = array(
|
||||
'shipping' => array( 'currency_code' => $summary['shippingTotal']->currency, 'value' => $summary['shippingTotal']->amountAsString() ),
|
||||
'item_total' => array( 'currency_code' => $summary['subtotal']->currency, 'value' => $summary['subtotal']->amountAsString() ),
|
||||
'tax_total' => array( 'currency_code' => $summary['taxTotal']->currency, 'value' => $summary['taxTotal']->amountAsString() ),
|
||||
'discount' => array( 'currency_code' => $summary['discount']->currency, 'value' => $summary['discount']->amountAsString() )
|
||||
);
|
||||
|
||||
/* Items */
|
||||
$itemTotal = new \IPS\Math\Number( '0' );
|
||||
foreach ( $summary['items'] as $item )
|
||||
{
|
||||
if( $item instanceof \IPS\nexus\extensions\nexus\Item\CouponDiscount )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemCategory = 'DIGITAL_GOODS';
|
||||
if( $item->physical )
|
||||
{
|
||||
$itemCategory = 'PHYSICAL_GOODS';
|
||||
}
|
||||
elseif( $item instanceof \IPS\nexus\extensions\nexus\Item\Donation )
|
||||
{
|
||||
$itemCategory = 'DONATION';
|
||||
}
|
||||
|
||||
$itemData = array(
|
||||
'name' => mb_strlen( $item->name ) > 127 ? mb_substr( $item->name, 0, 124 ) . '...' : $item->name,
|
||||
'quantity' => $item->quantity,
|
||||
'unit_amount' => array(
|
||||
'currency_code' => $item->price->currency,
|
||||
'value' => $item->price->amountAsString()
|
||||
),
|
||||
'category' => $itemCategory
|
||||
);
|
||||
$itemTotal = $itemTotal->add( $item->price->amount->multiply( new \IPS\Math\Number("{$item->quantity}") ) );
|
||||
|
||||
$payPalTransactionData['items'][] = $itemData;
|
||||
}
|
||||
|
||||
/* PayPal requires that the item total be equal to the pre-discounted amounts. The summary uses the invoice total, which is not accepted. */
|
||||
$itemTotal = new \IPS\nexus\Money( $itemTotal, $transaction->amount->currency );
|
||||
$payPalTransactionData['amount']['breakdown']['item_total']['value'] = $itemTotal->amountAsString();
|
||||
|
||||
/* Shipping Address */
|
||||
if ( $transaction->invoice->shipaddress )
|
||||
{
|
||||
$payPalTransactionData['shipping'] = array(
|
||||
'address' => $this->_getAddress( $transaction->invoice->shipaddress, $transaction->member ),
|
||||
'name' => array(
|
||||
'full_name' => $this->_getFirstName( $transaction ) . ' ' . $this->_getLastName( $transaction )
|
||||
),
|
||||
'type' => 'SHIPPING'
|
||||
);
|
||||
}
|
||||
}
|
||||
/* Otherwise just use a generic description */
|
||||
else
|
||||
{
|
||||
$payPalTransactionData['description'] = sprintf( $transaction->member->language()->get('partial_payment_desc'), $transaction->invoice->id );
|
||||
}
|
||||
|
||||
return array( $payPalTransactionData );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data for PayPal
|
||||
@@ -1174,7 +1364,7 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
{
|
||||
$payPalTransactionData['item_list']['shipping_address'] = array_merge(
|
||||
array( 'recipient_name' => $this->_getFirstName( $transaction ) . ' ' . $this->_getLastName( $transaction ) ),
|
||||
$this->_getAddress( $transaction->invoice->shipaddress, $transaction->invoice->member )
|
||||
$this->_getAddress( $transaction->invoice->shipaddress, $transaction->invoice->member, 'card' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1190,24 +1380,86 @@ class _PayPal extends \IPS\nexus\Gateway
|
||||
/**
|
||||
* Can we handle the renewal terms in a billing agreement?
|
||||
*
|
||||
* @param array $recurrings Details about recurring costs
|
||||
* @return array
|
||||
* @param array $recurrings Details about recurring costs
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _canProcessRecurringsAsBillingAgreement( array $recurrings )
|
||||
{
|
||||
if ( \count( $recurrings ) == 1 )
|
||||
if( \count( $recurrings ) == 1 )
|
||||
{
|
||||
$recurrance = array_pop( $recurrings );
|
||||
|
||||
/* 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 )
|
||||
/* If we only have one item, we're fine */
|
||||
if( \count( $recurrance['items'] ) == 1 )
|
||||
{
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Make sure all the terms match */
|
||||
$initialTermFrequency = null;
|
||||
foreach( $recurrance['items'] as $item )
|
||||
{
|
||||
if( isset( $item->initialInterval ) AND $item->initialInterval instanceof \DateInterval )
|
||||
{
|
||||
$thisTermFrequency = static::_getFrequencyFromInterval( $item->initialInterval );
|
||||
if( $initialTermFrequency !== null AND ( $thisTermFrequency['interval_unit'] != $initialTermFrequency['interval_unit'] OR $thisTermFrequency['interval_count'] != $initialTermFrequency['interval_count'] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$initialTermFrequency = $thisTermFrequency;
|
||||
}
|
||||
elseif( $initialTermFrequency !== null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the billing cycle frequency based on the DateInterval
|
||||
*
|
||||
* @param \DateInterval $term
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function _getFrequencyFromInterval( \DateInterval $term )
|
||||
{
|
||||
if( $term->y )
|
||||
{
|
||||
return array(
|
||||
'interval_unit' => 'YEAR',
|
||||
'interval_count' => $term->y
|
||||
);
|
||||
}
|
||||
|
||||
if( $term->m )
|
||||
{
|
||||
return array(
|
||||
'interval_unit' => 'MONTH',
|
||||
'interval_count' => $term->m
|
||||
);
|
||||
}
|
||||
|
||||
if( $term->d )
|
||||
{
|
||||
if( $term->d % 7 == 0 )
|
||||
{
|
||||
return array(
|
||||
'interval_unit' => 'WEEK',
|
||||
'interval_count' => $term->d / 7
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'interval_unit' => 'DAY',
|
||||
'interval_count' => $term->d
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -781,8 +781,9 @@ class _Stripe extends \IPS\nexus\Gateway
|
||||
|
||||
if ( mb_substr( $transaction->gw_id, 0, 3 ) === 'pi_' )
|
||||
{
|
||||
$response = $this->api( "payment_intents/{$transaction->gw_id}", NULL, 'get' );
|
||||
foreach ( $response['charges']['data'] as $charge )
|
||||
$response = $this->api( "charges?payment_intent={$transaction->gw_id}", NULL, 'get' );
|
||||
|
||||
foreach ( $response['data'] as $charge )
|
||||
{
|
||||
if ( $charge['paid'] )
|
||||
{
|
||||
@@ -829,9 +830,9 @@ class _Stripe extends \IPS\nexus\Gateway
|
||||
{
|
||||
if ( mb_substr( $transaction->gw_id, 0, 3 ) === 'pi_' )
|
||||
{
|
||||
$response = $this->api( "payment_intents/{$transaction->gw_id}", NULL, 'get' );
|
||||
|
||||
foreach ( $response['charges']['data'] as $charge )
|
||||
$response = $this->api( "charges?payment_intent={$transaction->gw_id}", NULL, 'get' );
|
||||
|
||||
foreach ( $response['data'] as $charge )
|
||||
{
|
||||
if ( $charge['paid'] )
|
||||
{
|
||||
@@ -911,8 +912,9 @@ class _Stripe extends \IPS\nexus\Gateway
|
||||
{
|
||||
if ( mb_substr( $transaction->gw_id, 0, 3 ) === 'pi_' )
|
||||
{
|
||||
$response = $this->api( "payment_intents/{$transaction->gw_id}", NULL, 'get' );
|
||||
foreach ( $response['charges']['data'] as $charge )
|
||||
$response = $this->api( "charges?payment_intent={$transaction->gw_id}", NULL, 'get' );
|
||||
|
||||
foreach ( $response['data'] as $charge )
|
||||
{
|
||||
if ( $charge['status'] === 'succeeded' )
|
||||
{
|
||||
@@ -1082,7 +1084,7 @@ class _Stripe extends \IPS\nexus\Gateway
|
||||
|
||||
$response = \IPS\Http\Url::external( 'https://api.stripe.com/v1/' . $uri )
|
||||
->request( \IPS\LONG_REQUEST_TIMEOUT )
|
||||
->setHeaders( array( 'Stripe-Version' => '2019-03-14' ) )
|
||||
->setHeaders( array( 'Stripe-Version' => '2022-11-15' ) )
|
||||
->forceTls()
|
||||
->login( $settings['secret_key'], '' )
|
||||
->$method( $data )
|
||||
|
||||
Reference in new issue
Block a user