sendRegistration(); } /** * Report the store address plus the REST credentials Afileasy needs to * publish the affiliates' coupons as real WooCommerce coupons. */ public function sendRegistration(): void { $connection = Connection::get(); if ($connection === null) { return; } $credentials = ApiKey::ensure(); $payload = [ 'event' => 'connection.registered', 'event_id' => 'registered_'.wp_generate_uuid4(), 'site_url' => site_url(), 'plugin_version' => AFILEASY_WC_VERSION, ]; if ($credentials !== null) { $payload['consumer_key'] = $credentials['consumer_key']; $payload['consumer_secret'] = $credentials['consumer_secret']; } if ($this->post($connection, $payload)) { update_option(self::REGISTERED_OPTION, ['fingerprint' => self::fingerprint()], false); delete_transient(self::REGISTER_LOCK); } } /** * @param int $order_id */ public function onPaymentComplete($order_id): void { $this->queuePaid((int) $order_id); } /** * @param int $order_id * @param string $from * @param string $to */ public function onStatusChanged($order_id, $from, $to): void { if (in_array($to, self::PAID_STATUSES, true)) { $this->queuePaid((int) $order_id); } } /** * @param int $order_id * @param int $refund_id */ public function onRefunded($order_id, $refund_id): void { $this->enqueue((int) $order_id, 'order.refunded'); } /** * Queue an `order.paid` once per order. */ private function queuePaid(int $order_id): void { if (Connection::get() === null) { return; } $order = wc_get_order($order_id); if (! $order || $order->get_meta(self::PAID_FLAG)) { return; } $order->update_meta_data(self::PAID_FLAG, current_time('mysql')); $order->save_meta_data(); $this->enqueue($order_id, 'order.paid'); } private function enqueue(int $order_id, string $event): void { if (Connection::get() === null) { return; } if (function_exists('as_enqueue_async_action')) { as_enqueue_async_action(self::SEND_HOOK, [$order_id, $event], 'afileasy'); return; } // Fallback: send inline if Action Scheduler is unavailable. $this->send($order_id, $event); } /** * Build the payload and POST it to Afileasy. * * @param int $order_id * @param string $event */ public function send($order_id, $event): void { $connection = Connection::get(); if ($connection === null) { return; } $order = wc_get_order((int) $order_id); if (! $order) { return; } $datePaid = $order->get_date_paid(); $payload = [ 'event' => $event, 'event_id' => $event.'_'.$order->get_id().'_'.time(), 'site_url' => site_url(), 'order' => [ 'id' => $order->get_id(), 'status' => $order->get_status(), 'currency' => $order->get_currency(), 'total' => $order->get_total(), 'total_refunded' => $order->get_total_refunded(), 'date_paid' => $datePaid ? $datePaid->date('c') : null, 'date_created' => $order->get_date_created() ? $order->get_date_created()->date('c') : null, 'date_modified' => $order->get_date_modified() ? $order->get_date_modified()->date('c') : null, // Codes applied at checkout: an affiliate coupon credits the // sale even when the visitor never clicked the referral link. 'coupons' => array_values($order->get_coupon_codes()), 'customer' => [ 'id' => $order->get_customer_id(), 'email' => $order->get_billing_email(), 'name' => trim($order->get_billing_first_name().' '.$order->get_billing_last_name()), ], ], 'event_link_id' => $order->get_meta(Attribution::ORDER_META_KEY) ?: null, ]; $this->post($connection, $payload); } /** * POST an authenticated payload to the Afileasy order-events endpoint. * * @param array $connection * @param array $payload * @return bool Whether Afileasy accepted it. */ private function post(array $connection, array $payload): bool { $response = wp_remote_post(Connection::webhookUrl($connection), [ 'headers' => [ 'Content-Type' => 'application/json', 'X-Afileasy-Token' => $connection['s'], ], 'body' => wp_json_encode($payload), 'timeout' => 20, 'blocking' => true, ]); if (is_wp_error($response)) { return false; } $status = (int) wp_remote_retrieve_response_code($response); return $status >= 200 && $status < 300; } }