ver. 0.270 - Apilo payment/status sync hardening
This commit is contained in:
@@ -3,6 +3,8 @@ namespace shop;
|
||||
|
||||
class Order implements \ArrayAccess
|
||||
{
|
||||
private const APILO_SYNC_QUEUE_FILE = '/temp/apilo-sync-queue.json';
|
||||
|
||||
public $id;
|
||||
public $products;
|
||||
public $statuses;
|
||||
@@ -89,31 +91,9 @@ class Order implements \ArrayAccess
|
||||
file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND );
|
||||
}
|
||||
|
||||
if ( $this -> apilo_order_id )
|
||||
if ( $this -> apilo_order_id and !$this -> sync_apilo_payment() )
|
||||
{
|
||||
$payment_date = new \DateTime( $this -> date_order );
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
|
||||
'amount' => str_replace( ',', '.', $this -> summary ),
|
||||
'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'),
|
||||
'type' => 1
|
||||
] ) );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer " . $access_token,
|
||||
"Accept: application/json"
|
||||
] );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$apilo_response = curl_exec( $ch );
|
||||
|
||||
// put response to log
|
||||
if ( $config['debug']['apilo'] )
|
||||
file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $apilo_response, true ) . "\n\n", FILE_APPEND );
|
||||
|
||||
curl_close( $ch );
|
||||
self::queue_apilo_sync( (int)$this -> id, true, null, 'payment_sync_failed' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,31 +147,9 @@ class Order implements \ArrayAccess
|
||||
file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND );
|
||||
}
|
||||
|
||||
if ( $this -> apilo_order_id )
|
||||
if ( $this -> apilo_order_id and !$this -> sync_apilo_status( (int)$status ) )
|
||||
{
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
|
||||
'id' => $this -> apilo_order_id,
|
||||
'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $status )
|
||||
] ) );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer " . $access_token,
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json"
|
||||
] );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$apilo_result = curl_exec( $ch );
|
||||
|
||||
// put response to log
|
||||
if ( $config['debug']['apilo'] )
|
||||
file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $apilo_result, true ) . "\n\n", FILE_APPEND );
|
||||
|
||||
curl_close( $ch );
|
||||
self::queue_apilo_sync( (int)$this -> id, false, (int)$status, 'status_sync_failed' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,4 +319,238 @@ class Order implements \ArrayAccess
|
||||
{
|
||||
unset( $this -> $offset );
|
||||
}
|
||||
}
|
||||
|
||||
private function sync_apilo_payment(): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
if ( !(int)$this -> apilo_order_id )
|
||||
return true;
|
||||
|
||||
$payment_type = (int)\front\factory\ShopPaymentMethod::get_apilo_payment_method_id( (int)$this -> payment_method_id );
|
||||
if ( $payment_type <= 0 )
|
||||
$payment_type = 1;
|
||||
|
||||
$payment_date = new \DateTime( $this -> date_order );
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
|
||||
'amount' => str_replace( ',', '.', $this -> summary ),
|
||||
'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'),
|
||||
'type' => $payment_type
|
||||
] ) );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer " . $access_token,
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json"
|
||||
] );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 );
|
||||
curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
|
||||
$apilo_response = curl_exec( $ch );
|
||||
$http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$curl_error = curl_errno( $ch ) ? curl_error( $ch ) : '';
|
||||
curl_close( $ch );
|
||||
|
||||
if ( $config['debug']['apilo'] )
|
||||
{
|
||||
self::append_apilo_log( "PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_response, true ) . "\n" );
|
||||
}
|
||||
|
||||
if ( $curl_error !== '' )
|
||||
return false;
|
||||
|
||||
if ( $http_code < 200 or $http_code >= 300 )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sync_apilo_status( int $status ): bool
|
||||
{
|
||||
global $config;
|
||||
|
||||
if ( !(int)$this -> apilo_order_id )
|
||||
return true;
|
||||
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
|
||||
'id' => $this -> apilo_order_id,
|
||||
'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $status )
|
||||
] ) );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer " . $access_token,
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json"
|
||||
] );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 );
|
||||
curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
|
||||
$apilo_result = curl_exec( $ch );
|
||||
$http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$curl_error = curl_errno( $ch ) ? curl_error( $ch ) : '';
|
||||
curl_close( $ch );
|
||||
|
||||
if ( $config['debug']['apilo'] )
|
||||
{
|
||||
self::append_apilo_log( "STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_result, true ) . "\n" );
|
||||
}
|
||||
|
||||
if ( $curl_error !== '' )
|
||||
return false;
|
||||
|
||||
if ( $http_code < 200 or $http_code >= 300 )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function process_apilo_sync_queue( int $limit = 10 ): int
|
||||
{
|
||||
$queue = self::load_apilo_sync_queue();
|
||||
if ( !\S::is_array_fix( $queue ) )
|
||||
return 0;
|
||||
|
||||
$processed = 0;
|
||||
|
||||
foreach ( $queue as $key => $task )
|
||||
{
|
||||
if ( $processed >= $limit )
|
||||
break;
|
||||
|
||||
$order_id = (int)( $task['order_id'] ?? 0 );
|
||||
if ( $order_id <= 0 )
|
||||
{
|
||||
unset( $queue[$key] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$order = new self( $order_id );
|
||||
if ( !(int)$order -> id )
|
||||
{
|
||||
unset( $queue[$key] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$sync_failed = false;
|
||||
|
||||
$payment_pending = !empty( $task['payment'] ) and (int)$order -> paid === 1;
|
||||
if ( $payment_pending and (int)$order -> apilo_order_id )
|
||||
{
|
||||
if ( !$order -> sync_apilo_payment() )
|
||||
{
|
||||
$sync_failed = true;
|
||||
$error = 'payment_sync_failed';
|
||||
}
|
||||
}
|
||||
|
||||
$status_pending = isset( $task['status'] ) and $task['status'] !== null and $task['status'] !== '';
|
||||
if ( !$sync_failed and $status_pending and (int)$order -> apilo_order_id )
|
||||
{
|
||||
if ( !$order -> sync_apilo_status( (int)$task['status'] ) )
|
||||
{
|
||||
$sync_failed = true;
|
||||
$error = 'status_sync_failed';
|
||||
}
|
||||
}
|
||||
|
||||
if ( $sync_failed )
|
||||
{
|
||||
$task['attempts'] = (int)( $task['attempts'] ?? 0 ) + 1;
|
||||
$task['last_error'] = $error;
|
||||
$task['updated_at'] = date( 'Y-m-d H:i:s' );
|
||||
$queue[$key] = $task;
|
||||
}
|
||||
else
|
||||
{
|
||||
unset( $queue[$key] );
|
||||
}
|
||||
|
||||
$processed++;
|
||||
}
|
||||
|
||||
self::save_apilo_sync_queue( $queue );
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
private static function queue_apilo_sync( int $order_id, bool $payment, ?int $status, string $error ): void
|
||||
{
|
||||
if ( $order_id <= 0 )
|
||||
return;
|
||||
|
||||
$queue = self::load_apilo_sync_queue();
|
||||
$key = (string)$order_id;
|
||||
$row = is_array( $queue[$key] ?? null ) ? $queue[$key] : [];
|
||||
|
||||
$row['order_id'] = $order_id;
|
||||
$row['payment'] = !empty( $row['payment'] ) || $payment ? 1 : 0;
|
||||
if ( $status !== null )
|
||||
$row['status'] = $status;
|
||||
|
||||
$row['attempts'] = (int)( $row['attempts'] ?? 0 ) + 1;
|
||||
$row['last_error'] = $error;
|
||||
$row['updated_at'] = date( 'Y-m-d H:i:s' );
|
||||
|
||||
$queue[$key] = $row;
|
||||
self::save_apilo_sync_queue( $queue );
|
||||
}
|
||||
|
||||
private static function apilo_sync_queue_path(): string
|
||||
{
|
||||
return dirname( __DIR__, 2 ) . self::APILO_SYNC_QUEUE_FILE;
|
||||
}
|
||||
|
||||
private static function load_apilo_sync_queue(): array
|
||||
{
|
||||
$path = self::apilo_sync_queue_path();
|
||||
if ( !file_exists( $path ) )
|
||||
return [];
|
||||
|
||||
$content = file_get_contents( $path );
|
||||
if ( !$content )
|
||||
return [];
|
||||
|
||||
$decoded = json_decode( $content, true );
|
||||
if ( !is_array( $decoded ) )
|
||||
return [];
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
private static function save_apilo_sync_queue( array $queue ): void
|
||||
{
|
||||
$path = self::apilo_sync_queue_path();
|
||||
$dir = dirname( $path );
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir, 0777, true );
|
||||
|
||||
file_put_contents( $path, json_encode( $queue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOCK_EX );
|
||||
}
|
||||
|
||||
private static function append_apilo_log( string $message ): void
|
||||
{
|
||||
$base = isset( $_SERVER['DOCUMENT_ROOT'] ) && $_SERVER['DOCUMENT_ROOT']
|
||||
? rtrim( $_SERVER['DOCUMENT_ROOT'], '/\\' )
|
||||
: dirname( __DIR__, 2 );
|
||||
|
||||
$dir = $base . '/logs';
|
||||
if ( !is_dir( $dir ) )
|
||||
mkdir( $dir, 0777, true );
|
||||
|
||||
file_put_contents(
|
||||
$dir . '/apilo.txt',
|
||||
date( 'Y-m-d H:i:s' ) . ' --- ' . $message . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user