Files
PayConnect-by-SallTech/class-bankily-admin.php
2025-08-23 21:16:45 +00:00

1102 lines
42 KiB
PHP

<?php
/**
* Interface d'administration pour la gestion des transactions B-PAY
* Compatible avec HPOS (High Performance Order Storage)
*
* @package Bankily_BPay
* @version 1.0.1
*/
// Empêcher l'accès direct
if (!defined('ABSPATH')) {
exit;
}
// Hooks d'initialisation de l'administration
add_action('admin_menu', 'bankily_bpay_admin_menu');
add_action('admin_enqueue_scripts', 'bankily_bpay_admin_scripts');
// Hooks AJAX
add_action('wp_ajax_bankily_check_transaction', 'bankily_ajax_check_transaction');
add_action('wp_ajax_bankily_bulk_check', 'bankily_ajax_bulk_check');
add_action('wp_ajax_bankily_bulk_mark_failed', 'bankily_ajax_bulk_mark_failed');
add_action('wp_ajax_bankily_export_transactions', 'bankily_ajax_export_transactions');
add_action('wp_ajax_bankily_clean_old_transactions', 'bankily_ajax_clean_old_transactions');
add_action('wp_ajax_bankily_ping', 'bankily_ajax_ping');
// Hooks pour les fonctionnalités supplémentaires
add_action('wp_dashboard_setup', 'bankily_add_dashboard_widget');
add_action('admin_bar_menu', 'bankily_admin_bar_notification', 100);
// Hooks pour les colonnes WooCommerce (compatibilité HPOS)
if (class_exists('\Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) {
// HPOS activé - utiliser les nouveaux hooks
add_filter('manage_woocommerce_page_wc-orders_columns', 'bankily_add_order_column');
add_action('manage_woocommerce_page_wc-orders_custom_column', 'bankily_show_order_column_content_hpos', 10, 2);
} else {
// HPOS désactivé - utiliser les anciens hooks
add_filter('manage_edit-shop_order_columns', 'bankily_add_order_column');
add_action('manage_shop_order_posts_custom_column', 'bankily_show_order_column_content', 10, 2);
}
add_action('admin_head', 'bankily_admin_order_list_css');
// Hook pour la vérification automatique
add_action('bankily_auto_check_transactions', 'bankily_auto_check_pending_transactions');
add_action('bankily_daily_report', 'bankily_generate_daily_report');
/**
* Fonctions utilitaires pour la compatibilité HPOS
*/
/**
* Obtenir les détails d'une commande de manière compatible HPOS
*/
function bankily_get_order_data($order_id) {
$order = wc_get_order($order_id);
if (!$order) {
return null;
}
return array(
'id' => $order->get_id(),
'status' => $order->get_status(),
'date' => $order->get_date_created(),
'total' => $order->get_total()
);
}
/**
* Obtenir les transactions avec les détails des commandes (compatible HPOS)
*/
function bankily_get_transactions_with_orders($where_clause = '', $limit_clause = '') {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
// Récupérer d'abord les transactions
$sql = "SELECT * FROM $table_name";
if ($where_clause) {
$sql .= " WHERE $where_clause";
}
$sql .= " ORDER BY created_at DESC";
if ($limit_clause) {
$sql .= " $limit_clause";
}
$transactions = $wpdb->get_results($sql);
// Enrichir avec les données des commandes via WooCommerce
foreach ($transactions as $transaction) {
$order_data = bankily_get_order_data($transaction->order_id);
if ($order_data) {
$transaction->order_status = $order_data['status'];
$transaction->order_date = $order_data['date'];
$transaction->order_total = $order_data['total'];
} else {
$transaction->order_status = 'not_found';
$transaction->order_date = null;
$transaction->order_total = 0;
}
}
return $transactions;
}
/**
* Créer le menu d'administration
*/
function bankily_bpay_admin_menu() {
add_menu_page(
__('B-PAY Transactions', 'bankily-bpay'),
__('B-PAY Transactions', 'bankily-bpay'),
'manage_woocommerce',
'bankily-transactions',
'bankily_bpay_admin_page',
'dashicons-money-alt',
56
);
add_submenu_page(
'bankily-transactions',
__('Tableau de bord', 'bankily-bpay'),
__('Tableau de bord', 'bankily-bpay'),
'manage_woocommerce',
'bankily-transactions',
'bankily_bpay_admin_page'
);
add_submenu_page(
'bankily-transactions',
__('Transactions en attente', 'bankily-bpay'),
__('En attente', 'bankily-bpay'),
'manage_woocommerce',
'bankily-pending',
'bankily_bpay_pending_page'
);
add_submenu_page(
'bankily-transactions',
__('Paramètres avancés', 'bankily-bpay'),
__('Paramètres', 'bankily-bpay'),
'manage_woocommerce',
'bankily-settings',
'bankily_bpay_settings_page'
);
}
/**
* Charger les scripts d'administration
*/
function bankily_bpay_admin_scripts($hook) {
if (strpos($hook, 'bankily') === false) {
return;
}
wp_enqueue_script('jquery');
wp_enqueue_script('bankily-admin', BANKILY_BPAY_PLUGIN_URL . 'assets/bankily-admin.js', array('jquery'), BANKILY_BPAY_VERSION, true);
wp_enqueue_style('bankily-admin', BANKILY_BPAY_PLUGIN_URL . 'assets/bankily-admin.css', array(), BANKILY_BPAY_VERSION);
wp_localize_script('bankily-admin', 'bankily_admin_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bankily_admin_nonce'),
'strings' => array(
'checking' => __('Vérification en cours...', 'bankily-bpay'),
'success' => __('Succès', 'bankily-bpay'),
'failed' => __('Échec', 'bankily-bpay'),
'pending' => __('En attente', 'bankily-bpay'),
'error' => __('Erreur', 'bankily-bpay'),
'confirm_bulk' => __('Êtes-vous sûr de vouloir vérifier toutes les transactions sélectionnées ?', 'bankily-bpay')
)
));
}
/**
* Page principale du tableau de bord
*/
function bankily_bpay_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
// Statistiques générales
$stats = $wpdb->get_row("
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'TS' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN status = 'TF' THEN 1 ELSE 0 END) as failed,
SUM(CASE WHEN status = 'TA' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'TS' THEN amount ELSE 0 END) as total_amount
FROM $table_name
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
");
// Transactions récentes (compatible HPOS)
$recent_transactions = bankily_get_transactions_with_orders('', 'LIMIT 20');
?>
<div class="wrap">
<h1><?php _e('Tableau de bord B-PAY', 'bankily-bpay'); ?></h1>
<!-- Statistiques -->
<div class="bankily-stats-grid">
<div class="bankily-stat-card total">
<h3><?php echo number_format($stats->total); ?></h3>
<p><?php _e('Total transactions (30j)', 'bankily-bpay'); ?></p>
</div>
<div class="bankily-stat-card success">
<h3><?php echo number_format($stats->success); ?></h3>
<p><?php _e('Succès', 'bankily-bpay'); ?></p>
</div>
<div class="bankily-stat-card pending">
<h3><?php echo number_format($stats->pending); ?></h3>
<p><?php _e('En attente', 'bankily-bpay'); ?></p>
</div>
<div class="bankily-stat-card failed">
<h3><?php echo number_format($stats->failed); ?></h3>
<p><?php _e('Échecs', 'bankily-bpay'); ?></p>
</div>
<div class="bankily-stat-card amount">
<h3><?php echo number_format($stats->total_amount, 0, ',', ' '); ?> MRU</h3>
<p><?php _e('Montant total (succès)', 'bankily-bpay'); ?></p>
</div>
</div>
<!-- Actions rapides -->
<div class="bankily-quick-actions">
<h2><?php _e('Actions rapides', 'bankily-bpay'); ?></h2>
<div class="bankily-action-buttons">
<button class="button button-primary" onclick="bankilyCheckAllPending()">
<?php _e('Vérifier toutes les transactions en attente', 'bankily-bpay'); ?>
</button>
<a href="<?php echo admin_url('admin.php?page=bankily-pending'); ?>" class="button">
<?php _e('Voir les transactions en attente', 'bankily-bpay'); ?>
</a>
<button class="button" onclick="bankilyExportTransactions()">
<?php _e('Exporter les données', 'bankily-bpay'); ?>
</button>
</div>
</div>
<!-- Transactions récentes -->
<div class="bankily-recent-transactions">
<h2><?php _e('Transactions récentes', 'bankily-bpay'); ?></h2>
<?php bankily_render_transactions_table($recent_transactions, false); ?>
</div>
</div>
<?php
}
/**
* Page des transactions en attente
*/
function bankily_bpay_pending_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
// Pagination
$per_page = 50;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$offset = ($current_page - 1) * $per_page;
// Filtres
$where_clause = "WHERE status = 'TA'";
$search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
if ($search) {
$where_clause .= $wpdb->prepare(" AND (operation_id LIKE %s OR client_phone LIKE %s OR order_id LIKE %s)",
'%' . $search . '%', '%' . $search . '%', '%' . $search . '%');
}
// Compter le total
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM $table_name $where_clause");
$total_pages = ceil($total_items / $per_page);
// Récupérer les transactions (compatible HPOS)
$pending_transactions = bankily_get_transactions_with_orders(
"status = 'TA'" . ($search ? $wpdb->prepare(" AND (operation_id LIKE %s OR client_phone LIKE %s OR order_id LIKE %s)",
'%' . $search . '%', '%' . $search . '%', '%' . $search . '%') : ''),
$wpdb->prepare("LIMIT %d OFFSET %d", $per_page, $offset)
);
?>
<div class="wrap">
<h1><?php _e('Transactions en attente', 'bankily-bpay'); ?></h1>
<!-- Barre de recherche et filtres -->
<div class="bankily-filters">
<form method="get" action="">
<input type="hidden" name="page" value="bankily-pending">
<div class="bankily-search-box">
<input type="text" name="search" value="<?php echo esc_attr($search); ?>"
placeholder="<?php _e('Rechercher par ID opération, téléphone ou commande...', 'bankily-bpay'); ?>">
<button type="submit" class="button"><?php _e('Rechercher', 'bankily-bpay'); ?></button>
<?php if ($search): ?>
<a href="<?php echo admin_url('admin.php?page=bankily-pending'); ?>" class="button">
<?php _e('Effacer', 'bankily-bpay'); ?>
</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- Actions en lot -->
<div class="bankily-bulk-actions">
<div class="alignleft actions">
<select name="bulk-action" id="bulk-action-selector-top">
<option value=""><?php _e('Actions groupées', 'bankily-bpay'); ?></option>
<option value="check"><?php _e('Vérifier les transactions', 'bankily-bpay'); ?></option>
<option value="mark-failed"><?php _e('Marquer comme échouées', 'bankily-bpay'); ?></option>
</select>
<button type="button" class="button action" onclick="bankilyBulkAction()">
<?php _e('Appliquer', 'bankily-bpay'); ?>
</button>
</div>
<div class="alignright">
<span class="displaying-num">
<?php printf(__('%d éléments', 'bankily-bpay'), $total_items); ?>
</span>
</div>
</div>
<!-- Tableau des transactions -->
<?php bankily_render_transactions_table($pending_transactions, true); ?>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="bankily-pagination">
<?php
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => __('&laquo; Précédent', 'bankily-bpay'),
'next_text' => __('Suivant &raquo;', 'bankily-bpay'),
'current' => $current_page,
'total' => $total_pages,
));
?>
</div>
<?php endif; ?>
</div>
<?php
}
/**
* Afficher le tableau des transactions
*/
function bankily_render_transactions_table($transactions, $show_bulk = false) {
?>
<form method="post" id="bankily-transactions-form">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<?php if ($show_bulk): ?>
<td class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all">
</td>
<?php endif; ?>
<th><?php _e('ID Opération', 'bankily-bpay'); ?></th>
<th><?php _e('Commande', 'bankily-bpay'); ?></th>
<th><?php _e('Téléphone', 'bankily-bpay'); ?></th>
<th><?php _e('Montant', 'bankily-bpay'); ?></th>
<th><?php _e('Statut', 'bankily-bpay'); ?></th>
<th><?php _e('Créé le', 'bankily-bpay'); ?></th>
<th><?php _e('Dernière vérif.', 'bankily-bpay'); ?></th>
<th><?php _e('Tentatives', 'bankily-bpay'); ?></th>
<th><?php _e('Actions', 'bankily-bpay'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($transactions)): ?>
<tr>
<td colspan="<?php echo $show_bulk ? '9' : '8'; ?>" class="no-items">
<?php _e('Aucune transaction trouvée.', 'bankily-bpay'); ?>
</td>
</tr>
<?php else: ?>
<?php foreach ($transactions as $transaction): ?>
<tr id="transaction-<?php echo $transaction->id; ?>" class="transaction-row">
<?php if ($show_bulk): ?>
<th class="check-column">
<input type="checkbox" name="transaction_ids[]" value="<?php echo $transaction->id; ?>">
</th>
<?php endif; ?>
<td class="operation-id">
<strong><?php echo esc_html($transaction->operation_id); ?></strong>
<?php if ($transaction->transaction_id): ?>
<br><small>TXN: <?php echo esc_html($transaction->transaction_id); ?></small>
<?php endif; ?>
</td>
<td>
<a href="<?php echo admin_url('post.php?post=' . $transaction->order_id . '&action=edit'); ?>">
#<?php echo $transaction->order_id; ?>
</a>
<br><small class="order-status-<?php echo $transaction->order_status; ?>">
<?php echo ucfirst($transaction->order_status); ?>
</small>
</td>
<td><?php echo esc_html($transaction->client_phone); ?></td>
<td><?php echo number_format($transaction->amount, 0, ',', ' '); ?> MRU</td>
<td>
<span class="bankily-status bankily-status-<?php echo strtolower($transaction->status); ?>">
<?php echo bankily_get_status_label($transaction->status); ?>
</span>
<?php if ($transaction->error_message): ?>
<br><small class="error-message" title="<?php echo esc_attr($transaction->error_message); ?>">
<?php echo esc_html(substr($transaction->error_message, 0, 50)) . '...'; ?>
</small>
<?php endif; ?>
</td>
<td>
<?php echo date_i18n('d/m/Y H:i', strtotime($transaction->created_at)); ?>
</td>
<td>
<?php
if ($transaction->last_checked != $transaction->created_at) {
echo date_i18n('d/m/Y H:i', strtotime($transaction->last_checked));
} else {
echo '-';
}
?>
</td>
<td><?php echo $transaction->check_count; ?></td>
<td class="actions">
<button type="button" class="button button-small"
onclick="bankilyCheckTransaction('<?php echo $transaction->operation_id; ?>', <?php echo $transaction->id; ?>)">
<?php _e('Vérifier', 'bankily-bpay'); ?>
</button>
<?php if ($transaction->status == 'TA' && $transaction->check_count > 3): ?>
<br>
<button type="button" class="button button-small button-link-delete"
onclick="bankilyMarkAsFailed(<?php echo $transaction->id; ?>)">
<?php _e('Marquer échec', 'bankily-bpay'); ?>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</form>
<?php
}
/**
* Obtenir le libellé du statut
*/
function bankily_get_status_label($status) {
$labels = array(
'TS' => __('Succès', 'bankily-bpay'),
'TF' => __('Échec', 'bankily-bpay'),
'TA' => __('En attente', 'bankily-bpay'),
);
return isset($labels[$status]) ? $labels[$status] : $status;
}
/**
* Page des paramètres avancés
*/
function bankily_bpay_settings_page() {
// Traitement de la sauvegarde
if (isset($_POST['submit'])) {
update_option('bankily_auto_check_interval', intval($_POST['auto_check_interval']));
update_option('bankily_max_check_attempts', intval($_POST['max_check_attempts']));
update_option('bankily_auto_mark_failed', isset($_POST['auto_mark_failed']));
echo '<div class="notice notice-success"><p>' . __('Paramètres sauvegardés.', 'bankily-bpay') . '</p></div>';
}
$auto_check_interval = get_option('bankily_auto_check_interval', 15);
$max_check_attempts = get_option('bankily_max_check_attempts', 5);
$auto_mark_failed = get_option('bankily_auto_mark_failed', false);
?>
<div class="wrap">
<h1><?php _e('Paramètres avancés B-PAY', 'bankily-bpay'); ?></h1>
<form method="post" action="">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Intervalle de vérification automatique', 'bankily-bpay'); ?></th>
<td>
<input type="number" name="auto_check_interval" value="<?php echo $auto_check_interval; ?>" min="5" max="60">
<?php _e('minutes', 'bankily-bpay'); ?>
<p class="description">
<?php _e('Fréquence de vérification automatique des transactions en attente (via cron).', 'bankily-bpay'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Nombre maximum de tentatives', 'bankily-bpay'); ?></th>
<td>
<input type="number" name="max_check_attempts" value="<?php echo $max_check_attempts; ?>" min="3" max="20">
<p class="description">
<?php _e('Nombre de tentatives de vérification avant d\'abandonner.', 'bankily-bpay'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Marquage automatique des échecs', 'bankily-bpay'); ?></th>
<td>
<label>
<input type="checkbox" name="auto_mark_failed" <?php checked($auto_mark_failed); ?>>
<?php _e('Marquer automatiquement comme échouées après le nombre maximum de tentatives', 'bankily-bpay'); ?>
</label>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<h2><?php _e('Maintenance', 'bankily-bpay'); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php _e('Nettoyage des données', 'bankily-bpay'); ?></th>
<td>
<button type="button" class="button" onclick="bankilyCleanOldTransactions()">
<?php _e('Supprimer les transactions de plus de 6 mois', 'bankily-bpay'); ?>
</button>
<p class="description">
<?php _e('Supprime les enregistrements de transactions anciennes pour optimiser les performances.', 'bankily-bpay'); ?>
</p>
</td>
</tr>
</table>
</div>
<?php
}
// ============================================================================
// FONCTIONS AJAX
// ============================================================================
/**
* AJAX: Vérifier une transaction individuelle
*/
function bankily_ajax_check_transaction() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_die(__('Permissions insuffisantes.', 'bankily-bpay'));
}
$operation_id = sanitize_text_field($_POST['operation_id']);
$transaction_id = intval($_POST['transaction_id']);
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
// Obtenir les détails de la transaction
$transaction = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$transaction_id
));
if (!$transaction) {
wp_send_json_error(__('Transaction non trouvée.', 'bankily-bpay'));
}
// Créer une instance de la gateway pour utiliser check_transaction
$gateway = new WC_Bankily_BPay_Gateway();
$result = $gateway->check_transaction($operation_id);
if ($result && $result['errorCode'] == '0') {
// Mettre à jour la transaction
$wpdb->update(
$table_name,
array(
'status' => $result['status'],
'transaction_id' => $result['transactionId'],
'last_checked' => current_time('mysql'),
'check_count' => $transaction->check_count + 1,
'error_code' => $result['errorCode'],
'error_message' => isset($result['errorMessage']) ? $result['errorMessage'] : ''
),
array('id' => $transaction_id)
);
// Si transaction réussie, mettre à jour la commande WooCommerce
if ($result['status'] == 'TS') {
$order = wc_get_order($transaction->order_id);
if ($order) {
$order->payment_complete($result['transactionId']);
$order->add_order_note(__('Paiement B-PAY confirmé via vérification manuelle. ID Transaction: ' . $result['transactionId'], 'bankily-bpay'));
}
}
wp_send_json_success(array(
'status' => $result['status'],
'status_label' => bankily_get_status_label($result['status']),
'transaction_id' => $result['transactionId'],
'message' => __('Transaction vérifiée avec succès.', 'bankily-bpay')
));
} else {
// Mettre à jour le compteur même en cas d'erreur
$wpdb->update(
$table_name,
array(
'last_checked' => current_time('mysql'),
'check_count' => $transaction->check_count + 1,
'error_code' => isset($result['errorCode']) ? $result['errorCode'] : '1',
'error_message' => isset($result['errorMessage']) ? $result['errorMessage'] : __('Erreur de vérification', 'bankily-bpay')
),
array('id' => $transaction_id)
);
wp_send_json_error(isset($result['errorMessage']) ? $result['errorMessage'] : __('Erreur lors de la vérification.', 'bankily-bpay'));
}
}
/**
* AJAX: Vérification en lot
*/
function bankily_ajax_bulk_check() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_die(__('Permissions insuffisantes.', 'bankily-bpay'));
}
$transaction_ids = array_map('intval', $_POST['transaction_ids']);
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$gateway = new WC_Bankily_BPay_Gateway();
$results = array();
foreach ($transaction_ids as $transaction_id) {
$transaction = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$transaction_id
));
if ($transaction) {
$result = $gateway->check_transaction($transaction->operation_id);
if ($result && $result['errorCode'] == '0') {
$wpdb->update(
$table_name,
array(
'status' => $result['status'],
'transaction_id' => $result['transactionId'],
'last_checked' => current_time('mysql'),
'check_count' => $transaction->check_count + 1,
'error_code' => $result['errorCode'],
'error_message' => isset($result['errorMessage']) ? $result['errorMessage'] : ''
),
array('id' => $transaction_id)
);
if ($result['status'] == 'TS') {
$order = wc_get_order($transaction->order_id);
if ($order) {
$order->payment_complete($result['transactionId']);
$order->add_order_note(__('Paiement B-PAY confirmé via vérification groupée.', 'bankily-bpay'));
}
}
$results[] = array(
'id' => $transaction_id,
'status' => 'success',
'new_status' => $result['status']
);
} else {
$results[] = array(
'id' => $transaction_id,
'status' => 'error',
'message' => isset($result['errorMessage']) ? $result['errorMessage'] : __('Erreur de vérification', 'bankily-bpay')
);
}
}
}
wp_send_json_success($results);
}
/**
* AJAX: Marquer comme échouées en lot
*/
function bankily_ajax_bulk_mark_failed() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_die(__('Permissions insuffisantes.', 'bankily-bpay'));
}
$transaction_ids = array_map('intval', $_POST['transaction_ids']);
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$updated = 0;
foreach ($transaction_ids as $transaction_id) {
$result = $wpdb->update(
$table_name,
array(
'status' => 'TF',
'last_checked' => current_time('mysql'),
'error_message' => __('Marqué comme échoué manuellement', 'bankily-bpay')
),
array('id' => $transaction_id)
);
if ($result) {
$updated++;
// Mettre à jour la commande WooCommerce
$transaction = $wpdb->get_row($wpdb->prepare(
"SELECT order_id FROM $table_name WHERE id = %d",
$transaction_id
));
if ($transaction) {
$order = wc_get_order($transaction->order_id);
if ($order && $order->get_status() == 'on-hold') {
$order->update_status('failed', __('Paiement B-PAY marqué comme échoué manuellement.', 'bankily-bpay'));
}
}
}
}
wp_send_json_success(array(
'updated' => $updated,
'message' => sprintf(__('%d transactions marquées comme échouées.', 'bankily-bpay'), $updated)
));
}
/**
* AJAX: Exporter les transactions (compatible HPOS)
*/
function bankily_ajax_export_transactions() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_die(__('Permissions insuffisantes.', 'bankily-bpay'));
}
// Récupérer les données avec compatibilité HPOS
$transactions = bankily_get_transactions_with_orders();
// Headers pour le téléchargement CSV
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="bankily-transactions-' . date('Y-m-d') . '.csv"');
$output = fopen('php://output', 'w');
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF)); // BOM UTF-8
// En-têtes du CSV
fputcsv($output, array(
'ID Transaction',
'ID Commande',
'ID Opération',
'Téléphone Client',
'Montant (MRU)',
'Statut Transaction',
'Statut Commande',
'Code Erreur',
'Message Erreur',
'Créé le',
'Dernière Vérification',
'Nombre de Vérifications'
));
// Données
foreach ($transactions as $transaction) {
fputcsv($output, array(
$transaction->transaction_id,
$transaction->order_id,
$transaction->operation_id,
$transaction->client_phone,
number_format($transaction->amount, 2),
bankily_get_status_label($transaction->status),
ucfirst($transaction->order_status ?: 'N/A'),
$transaction->error_code,
$transaction->error_message,
$transaction->created_at,
$transaction->last_checked,
$transaction->check_count
));
}
fclose($output);
exit;
}
/**
* AJAX: Nettoyer les anciennes transactions
*/
function bankily_ajax_clean_old_transactions() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_die(__('Permissions insuffisantes.', 'bankily-bpay'));
}
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$deleted = $wpdb->query("
DELETE FROM $table_name
WHERE created_at < DATE_SUB(NOW(), INTERVAL 6 MONTH)
");
wp_send_json_success(array(
'deleted' => $deleted,
'message' => sprintf(__('%d anciennes transactions supprimées.', 'bankily-bpay'), $deleted)
));
}
/**
* AJAX: Ping de connexion
*/
function bankily_ajax_ping() {
check_ajax_referer('bankily_admin_nonce', 'nonce');
wp_send_json_success(array('status' => 'online', 'timestamp' => time()));
}
// ============================================================================
// FONCTIONNALITÉS SUPPLÉMENTAIRES
// ============================================================================
/**
* Widget tableau de bord WordPress
*/
function bankily_add_dashboard_widget() {
wp_add_dashboard_widget(
'bankily_bpay_dashboard_widget',
__('B-PAY Bankily - Résumé', 'bankily-bpay'),
'bankily_dashboard_widget_content'
);
}
function bankily_dashboard_widget_content() {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$stats_24h = $wpdb->get_row("
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'TS' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN status = 'TA' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'TS' THEN amount ELSE 0 END) as total_amount
FROM $table_name
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
");
?>
<div style="text-align: center;">
<p><strong><?php echo number_format($stats_24h->success); ?></strong> <?php _e('succès', 'bankily-bpay'); ?> |
<strong><?php echo number_format($stats_24h->pending); ?></strong> <?php _e('en attente', 'bankily-bpay'); ?></p>
<p><strong><?php echo number_format($stats_24h->total_amount, 0, ',', ' '); ?> MRU</strong><br>
<small><?php _e('Chiffre d\'affaires (24h)', 'bankily-bpay'); ?></small></p>
<p><a href="<?php echo admin_url('admin.php?page=bankily-transactions'); ?>" class="button button-primary">
<?php _e('Voir le tableau de bord', 'bankily-bpay'); ?></a></p>
</div>
<?php
}
/**
* Notifications dans l'admin bar
*/
function bankily_admin_bar_notification($wp_admin_bar) {
if (!current_user_can('manage_woocommerce')) {
return;
}
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$urgent_count = $wpdb->get_var("
SELECT COUNT(*)
FROM $table_name
WHERE status = 'TA'
AND created_at < DATE_SUB(NOW(), INTERVAL 2 HOUR)
");
if ($urgent_count > 0) {
$wp_admin_bar->add_node(array(
'id' => 'bankily-urgent-notifications',
'title' => '<span class="ab-icon dashicons dashicons-warning" style="color: #ffc107;"></span>' .
'<span class="ab-label">' . sprintf(__('B-PAY: %d urgent', 'bankily-bpay'), $urgent_count) . '</span>',
'href' => admin_url('admin.php?page=bankily-pending'),
));
}
}
/**
* Colonnes personnalisées dans la liste des commandes
*/
function bankily_add_order_column($columns) {
$columns['bankily_status'] = __('B-PAY', 'bankily-bpay');
return $columns;
}
function bankily_show_order_column_content($column, $order_id) {
if ($column !== 'bankily_status') {
return;
}
$order = wc_get_order($order_id);
if (!$order || $order->get_payment_method() !== 'bankily_bpay') {
echo '-';
return;
}
bankily_display_order_status($order_id);
}
/**
* Afficher le statut pour HPOS
*/
function bankily_show_order_column_content_hpos($column, $order) {
if ($column !== 'bankily_status') {
return;
}
if (!$order || $order->get_payment_method() !== 'bankily_bpay') {
echo '-';
return;
}
bankily_display_order_status($order->get_id());
}
/**
* Fonction commune pour afficher le statut B-PAY
*/
function bankily_display_order_status($order_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$transaction = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE order_id = %d ORDER BY created_at DESC LIMIT 1",
$order_id
));
if ($transaction) {
$status_class = 'bankily-status-' . strtolower($transaction->status);
echo '<span class="bankily-status ' . $status_class . '">' . bankily_get_status_label($transaction->status) . '</span>';
if ($transaction->status == 'TA') {
$age_hours = (time() - strtotime($transaction->created_at)) / 3600;
if ($age_hours > 2) {
echo '<br><small style="color: #dc3545;">⚠ ' . sprintf(__('%.1fh', 'bankily-bpay'), $age_hours) . '</small>';
}
}
} else {
echo '<span style="color: #666;">-</span>';
}
}
/**
* CSS pour les colonnes des commandes
*/
function bankily_admin_order_list_css() {
global $pagenow, $post_type;
if ($pagenow === 'edit.php' && $post_type === 'shop_order') {
?>
<style>
.bankily-status {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
}
.bankily-status-ts { background: #d4edda; color: #155724; }
.bankily-status-tf { background: #f8d7da; color: #721c24; }
.bankily-status-ta { background: #fff3cd; color: #856404; }
.column-bankily_status { width: 80px; }
</style>
<?php
}
}
// ============================================================================
// FONCTIONS DE VÉRIFICATION AUTOMATIQUE
// ============================================================================
/**
* Vérification automatique des transactions en attente
*/
function bankily_auto_check_pending_transactions() {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$max_attempts = get_option('bankily_max_check_attempts', 5);
$auto_mark_failed = get_option('bankily_auto_mark_failed', false);
// Récupérer les transactions en attente
$pending_transactions = $wpdb->get_results($wpdb->prepare("
SELECT * FROM $table_name
WHERE status = 'TA'
AND check_count < %d
AND last_checked < DATE_SUB(NOW(), INTERVAL 5 MINUTE)
ORDER BY created_at ASC
LIMIT 20
", $max_attempts));
if (empty($pending_transactions)) {
return;
}
$gateway = new WC_Bankily_BPay_Gateway();
foreach ($pending_transactions as $transaction) {
$result = $gateway->check_transaction($transaction->operation_id);
if ($result && $result['errorCode'] == '0') {
$wpdb->update(
$table_name,
array(
'status' => $result['status'],
'transaction_id' => $result['transactionId'],
'last_checked' => current_time('mysql'),
'check_count' => $transaction->check_count + 1,
'error_code' => $result['errorCode'],
'error_message' => isset($result['errorMessage']) ? $result['errorMessage'] : ''
),
array('id' => $transaction->id)
);
if ($result['status'] == 'TS') {
$order = wc_get_order($transaction->order_id);
if ($order) {
$order->payment_complete($result['transactionId']);
$order->add_order_note(__('Paiement B-PAY confirmé automatiquement.', 'bankily-bpay'));
}
}
} else {
$new_check_count = $transaction->check_count + 1;
$new_status = $transaction->status;
// Marquer comme échoué si on a atteint le maximum et que l'option est activée
if ($new_check_count >= $max_attempts && $auto_mark_failed) {
$new_status = 'TF';
}
$wpdb->update(
$table_name,
array(
'status' => $new_status,
'last_checked' => current_time('mysql'),
'check_count' => $new_check_count,
'error_code' => isset($result['errorCode']) ? $result['errorCode'] : '1',
'error_message' => isset($result['errorMessage']) ? $result['errorMessage'] : __('Erreur de vérification automatique', 'bankily-bpay')
),
array('id' => $transaction->id)
);
}
// Délai pour éviter de surcharger l'API
sleep(1);
}
}
/**
* Générer un rapport quotidien
*/
function bankily_generate_daily_report() {
global $wpdb;
$table_name = $wpdb->prefix . 'bankily_transactions';
$yesterday_start = date('Y-m-d 00:00:00', strtotime('-1 day'));
$yesterday_end = date('Y-m-d 23:59:59', strtotime('-1 day'));
$report = $wpdb->get_row($wpdb->prepare("
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'TS' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN status = 'TF' THEN 1 ELSE 0 END) as failed,
SUM(CASE WHEN status = 'TA' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'TS' THEN amount ELSE 0 END) as revenue
FROM $table_name
WHERE created_at BETWEEN %s AND %s
", $yesterday_start, $yesterday_end));
if ($report && $report->total > 0) {
$success_rate = round(($report->success / $report->total) * 100, 1);
$message = sprintf(
"Rapport quotidien B-PAY (%s)\n\n" .
"📊 Transactions: %d\n" .
"✅ Réussies: %d (%s%%)\n" .
"❌ Échouées: %d\n" .
"⏳ En attente: %d\n" .
"💰 Chiffre d'affaires: %s MRU",
date('d/m/Y', strtotime('-1 day')),
$report->total,
$report->success,
$success_rate,
$report->failed,
$report->pending,
number_format($report->revenue, 0, ',', ' ')
);
wp_mail(get_option('admin_email'), 'Rapport quotidien B-PAY', $message);
}
}
?>