table_name = $wpdb->prefix . 'formipay_tokens'; add_action( 'init', [$this, 'create_db'] ); // Setup cleanup hook add_action( 'formipay_daily_cleanup', [$this, 'cleanup_expired_tokens']); if (!wp_next_scheduled('formipay_daily_cleanup')) { wp_schedule_event(time(), 'daily', 'formipay_daily_cleanup'); } } public function create_db() { global $wpdb; $table_name = $wpdb->prefix . 'formipay_tokens'; $sql = "CREATE TABLE $table_name ( token CHAR(64) NOT NULL, form_id BIGINT UNSIGNED NOT NULL, order_id BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, used TINYINT(1) NOT NULL DEFAULT 0, use_count INT NOT NULL DEFAULT 0, PRIMARY KEY (token), KEY expires_at_idx (expires_at), KEY order_id_idx (order_id) ) {$wpdb->get_charset_collate()};"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } /** * Generate cryptographically secure token * * @param int $order_id * @param int $form_id * @param int $expires_in Seconds until expiration (default: 900 = 15 mins) * @return string */ public function generate(int $order_id, int $form_id, int $expires_in = 900): string { global $wpdb; $token = bin2hex(random_bytes(32)); // 64-character token $expires_at = formipay_date('Y-m-d H:i:s', time() + $expires_in); $wpdb->insert($this->table_name, [ 'token' => $token, 'form_id' => $form_id, 'order_id' => $order_id, 'expires_at' => $expires_at, 'created_at' => formipay_date('Y-m-d H:i:s') ]); return $token; } /** * Validate token and retrieve order data * * @param string $token * @return array|false */ public function validate(string $token) { global $wpdb; // Validate token format if (!preg_match('/^[a-f0-9]{64}$/', $token)) return false; $query = $wpdb->prepare( "SELECT form_id, order_id FROM {$this->table_name} WHERE token = %s AND expires_at > NOW() AND used = 0", $token ); return $wpdb->get_row($query, ARRAY_A); } /** * Mark token as used * * @param string $token * @return bool */ public function mark_used(string $token): bool { global $wpdb; return (bool) $wpdb->update( $this->table_name, ['used' => 1], ['token' => $token] ); } /** * Cleanup expired tokens */ public function cleanup_expired_tokens() { global $wpdb; $wpdb->query("DELETE FROM {$this->table_name} WHERE expires_at < NOW()"); } /** * Get token usage count * * @param string $token * @return int */ public function get_usage_count(string $token): int { global $wpdb; return (int) $wpdb->get_var($wpdb->prepare( "SELECT use_count FROM {$this->table_name} WHERE token = %s", $token )); } /** * Increment token usage * * @param string $token * @param int $max_uses * @return bool * @throws Exception */ public function increment_usage(string $token, int $max_uses = 5): bool { global $wpdb; $current_count = $this->get_usage_count($token); if ($current_count >= $max_uses) { throw new Exception('Token usage limit reached'); } return (bool) $wpdb->update( $this->table_name, ['use_count' => $current_count + 1], ['token' => $token] ); } }