286 lines
7.7 KiB
PHP
286 lines
7.7 KiB
PHP
<?php
|
|
/**
|
|
* @brief PayPal Billing Agreement
|
|
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
|
* @copyright (c) Invision Power Services, Inc.
|
|
* @license https://www.invisioncommunity.com/legal/standards/
|
|
* @package Invision Community
|
|
* @subpackage Nexus
|
|
* @since 16 Dec 2015
|
|
*/
|
|
|
|
namespace IPS\nexus\Gateway\PayPal;
|
|
|
|
/* To prevent PHP errors (extending class does not exist) revealing path */
|
|
|
|
use DateInterval;
|
|
use DomainException;
|
|
use Exception;
|
|
use IPS\DateTime;
|
|
use IPS\Db;
|
|
use IPS\nexus\Customer\BillingAgreement as NexusBillingAgreement;
|
|
use IPS\nexus\Money;
|
|
use IPS\nexus\Purchase\RenewalTerm;
|
|
use IPS\nexus\Transaction;
|
|
use OutOfRangeException;
|
|
use RuntimeException;
|
|
use UnderflowException;
|
|
use function defined;
|
|
|
|
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
|
{
|
|
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* PayPal Billing Agreement
|
|
*/
|
|
class BillingAgreement extends NexusBillingAgreement
|
|
{
|
|
/**
|
|
* Get status
|
|
*
|
|
* @return string See STATUS_* constants
|
|
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
|
*/
|
|
public function status() : string
|
|
{
|
|
$data = $this->_getData();
|
|
|
|
switch ( mb_strtoupper( $data['status'] ?? $data['state'] ) )
|
|
{
|
|
case 'ACTIVE':
|
|
case 'PENDING':
|
|
case 'APPROVAL_PENDING':
|
|
case 'APPROVED':
|
|
case 'REACTIVATE':
|
|
return static::STATUS_ACTIVE;
|
|
break;
|
|
case 'SUSPEND':
|
|
case 'SUSPENDED':
|
|
return static::STATUS_SUSPENDED;
|
|
break;
|
|
case 'EXPIRED':
|
|
case 'CANCEL':
|
|
case 'CANCELLED':
|
|
return static::STATUS_CANCELED;
|
|
break;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Get term
|
|
*
|
|
* @return RenewalTerm
|
|
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
|
*/
|
|
public function term() : RenewalTerm
|
|
{
|
|
$data = $this->_getData();
|
|
|
|
if ( isset( $data['plan_id'] ) )
|
|
{
|
|
$plan = $this->method->api( "billing/plans/{$data['plan_id']}", NULL, 'get' );
|
|
$cycle = array_pop( $plan['billing_cycles'] );
|
|
|
|
return new RenewalTerm(
|
|
new Money( $cycle['pricing_scheme']['fixed_price']['value'], $cycle['pricing_scheme']['fixed_price']['currency_code'] ),
|
|
new DateInterval( 'P' . $cycle['frequency']['interval_count'] . mb_substr( $cycle['frequency']['interval_unit'], 0, 1 ) )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
$amount = $data['plan']['payment_definitions'][0]['amount']['value'];
|
|
if ( isset( $data['plan']['payment_definitions'][0]['charge_models'][0] ) )
|
|
{
|
|
$amount += $data['plan']['payment_definitions'][0]['charge_models'][0]['amount']['value'];
|
|
}
|
|
|
|
return new RenewalTerm(
|
|
new Money( $amount, $data['plan']['payment_definitions'][0]['amount']['currency'] ),
|
|
new DateInterval( 'P' . $data['plan']['payment_definitions'][0]['frequency_interval'] . mb_substr( $data['plan']['payment_definitions'][0]['frequency'], 0, 1 ) )
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get next payment date
|
|
*
|
|
* @return DateTime
|
|
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
|
*/
|
|
public function nextPaymentDate() : DateTime
|
|
{
|
|
$data = $this->_getData();
|
|
if ( isset( $data['billing_info'] ) )
|
|
{
|
|
return new DateTime( $data['billing_info']['next_billing_time'] );
|
|
}
|
|
else
|
|
{
|
|
return new 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 Transaction
|
|
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
|
* @throws OutOfRangeException
|
|
* @throws \IPS\Http\Url\Exception
|
|
*/
|
|
public function latestUnclaimedTransaction() : Transaction
|
|
{
|
|
$data = $this->_getData();
|
|
/* PayPal Subscriptions */
|
|
if ( isset( $data['plan_id'] ) )
|
|
{
|
|
$transactions = $this->method->api( "billing/subscriptions/{$this->gw_id}/transactions?start_time=" . DateTime::create()->sub( new DateInterval( 'P31D' ) )->rfc3339() . '&end_time=' . DateTime::create()->rfc3339(), NULL, 'get' );
|
|
foreach ( array_reverse( $transactions['transactions'] ?? [] ) as $t )
|
|
{
|
|
if ( $t['status'] == 'COMPLETED' )
|
|
{
|
|
try
|
|
{
|
|
Db::i()->select( 't_id', 'nexus_transactions', [ 't_gw_id=? AND t_method=?', $t['id'], $this->method->id ], flags: Db::SELECT_FROM_WRITE_SERVER )->first();
|
|
}
|
|
catch ( UnderflowException )
|
|
{
|
|
$transaction = new Transaction;
|
|
$transaction->member = $this->member;
|
|
$transaction->method = $this->method;
|
|
$transaction->amount = new Money( $t['amount_with_breakdown']['gross_amount']['value'], $t['amount_with_breakdown']['gross_amount']['currency_code'] );
|
|
$transaction->date = new 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 )
|
|
{
|
|
if ( $t['status'] == 'Completed' )
|
|
{
|
|
try
|
|
{
|
|
Db::i()->select( 't_id', 'nexus_transactions', [ 't_gw_id=? AND t_method=?', $t['transaction_id'], $this->method->id ], flags: Db::SELECT_FROM_WRITE_SERVER )->first();
|
|
}
|
|
catch ( UnderflowException )
|
|
{
|
|
$transaction = new Transaction;
|
|
$transaction->member = $this->member;
|
|
$transaction->method = $this->method;
|
|
$transaction->amount = new Money( $t['amount']['value'], $t['amount']['currency'] );
|
|
$transaction->date = new DateTime( $t['time_stamp'] );
|
|
$transaction->extra = array( 'automatic' => TRUE );
|
|
$transaction->gw_id = $t['transaction_id'];
|
|
$transaction->billing_agreement = $this;
|
|
return $transaction;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new OutOfRangeException( "{$this->id} ({$this->gw_id})\n{$url}\n\n" . print_r( $transactions, TRUE ) );
|
|
}
|
|
|
|
/**
|
|
* Suspend
|
|
*
|
|
* @return void
|
|
* @throws DomainException
|
|
*/
|
|
public function doSuspend() : void
|
|
{
|
|
$data = $this->_getData();
|
|
if ( isset( $data['plan_id'] ) )
|
|
{
|
|
$this->method->api( "billing/subscriptions/{$this->gw_id}/suspend", array( 'reason' => 'Suspend' ) );
|
|
}
|
|
else
|
|
{
|
|
$this->method->api( "payments/billing-agreements/{$this->gw_id}/suspend", array( 'note' => 'Suspend' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reactivate
|
|
*
|
|
* @return void
|
|
* @throws DomainException
|
|
*/
|
|
public function doReactivate() : void
|
|
{
|
|
$data = $this->_getData();
|
|
if ( isset( $data['plan_id'] ) )
|
|
{
|
|
$this->method->api( "billing/subscriptions/{$this->gw_id}/activate", array( 'reason' => 'Reactivate' ) );
|
|
}
|
|
else
|
|
{
|
|
$this->method->api( "payments/billing-agreements/{$this->gw_id}/re-activate", array( 'note' => 'Reactivate' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel
|
|
*
|
|
* @return void
|
|
* @throws DomainException
|
|
*/
|
|
public function doCancel() : void
|
|
{
|
|
$data = $this->_getData();
|
|
if ( isset( $data['plan_id'] ) )
|
|
{
|
|
$this->method->api( "billing/subscriptions/{$this->gw_id}/cancel", array( 'reason' => 'Cancel' ) );
|
|
}
|
|
else
|
|
{
|
|
$this->method->api( "payments/billing-agreements/{$this->gw_id}/cancel", array( 'note' => 'Cancel' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Cached data
|
|
*/
|
|
protected ?array $_payPalData = NULL;
|
|
|
|
/**
|
|
* Get data
|
|
*
|
|
* @return array
|
|
* @throws \IPS\nexus\Gateway\PayPal\Exception
|
|
*/
|
|
public function _getData() : array
|
|
{
|
|
if ( $this->_payPalData === NULL )
|
|
{
|
|
try
|
|
{
|
|
$this->_payPalData = $this->method->api( "billing/subscriptions/{$this->gw_id}", NULL, 'get' );
|
|
|
|
if( !isset( $this->_payPalData['plan_id'] ) )
|
|
{
|
|
throw new RuntimeException;
|
|
}
|
|
}
|
|
catch ( Exception )
|
|
{
|
|
$this->_payPalData = $this->method->api( "payments/billing-agreements/{$this->gw_id}", NULL, 'get' );
|
|
}
|
|
}
|
|
return $this->_payPalData;
|
|
}
|
|
} |