get_meta(self::PREFIX.'click_id', true) !== '') { return; } $touch = Affclix_Attribution::current_touch(); if ($touch === null) { return; } foreach ($touch as $field => $value) { $order->update_meta_data(self::PREFIX.$field, $value); } $order->update_meta_data(self::PREFIX.'version', AFFCLIX_WC_VERSION); } /** * Read an order's stored attribution back out. * * @param WC_Order $order Order to read. * @return array Flat keys, ready to publish. */ public static function read($order) { $out = []; foreach (Affclix_Settings::meta_keys() as $field => $public_key) { $value = $order->get_meta(self::PREFIX.$field, true); if ((string) $value !== '') { $out[$public_key] = (string) $value; } } return $out; } /** * Append the flat keys to an outgoing webhook payload. * * @param array $payload Payload WooCommerce is about to send. * @param string $resource Resource name, e.g. 'order'. * @param int $resource_id Resource ID. * @param int $webhook_id Webhook ID. * @return array */ public static function expose_in_payload($payload, $resource, $resource_id, $webhook_id) { if ($resource !== 'order' || empty($payload['id'])) { return $payload; } // Only our own webhook gets the extra keys. Another integration's // webhook has no business receiving this store's affiliate data. if (! Affclix_Webhook::is_ours((int) $webhook_id)) { return $payload; } $order = wc_get_order($resource_id); if (! $order) { return $payload; } $payload['meta_data'] = self::merge_meta( isset($payload['meta_data']) ? $payload['meta_data'] : [], self::read($order) ); return $payload; } /** * Same republishing for authenticated REST reads. * * AffClix backfills orders over the REST API when a webhook was missed, and * that path must see the same fields the webhook does or a reconciled order * would come back unattributed. * * @param WP_REST_Response $response Response object. * @param WC_Order $order Order being prepared. * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public static function expose_in_rest($response, $order, $request) { if (! $order instanceof WC_Order) { return $response; } $data = $response->get_data(); $data['meta_data'] = self::merge_meta( isset($data['meta_data']) ? $data['meta_data'] : [], self::read($order) ); $response->set_data($data); return $response; } /** * Merge flat key/value pairs into a WooCommerce meta_data list. * * Existing entries win: if the store genuinely has its own `click_id` meta, * we must not overwrite what the merchant sees as their own data. * * @param array $meta_data Existing meta_data entries. * @param array $extra Flat key => value pairs to add. * @return array */ private static function merge_meta($meta_data, array $extra) { $meta_data = is_array($meta_data) ? $meta_data : []; $present = []; foreach ($meta_data as $entry) { $key = is_array($entry) ? (isset($entry['key']) ? $entry['key'] : null) : (isset($entry->key) ? $entry->key : null); if ($key !== null) { $present[$key] = true; } } foreach ($extra as $key => $value) { if (isset($present[$key])) { continue; } // id 0 marks this as synthetic — it is not a stored meta row, and // anything trying to update it by id would find nothing. $meta_data[] = [ 'id' => 0, 'key' => $key, 'value' => $value, ]; } return $meta_data; } }