*/ private function order_status_result_options() { return [ 'AV_FAILED' => __( 'AV_FAILED', '1account-age-verification' ), 'NON_UK_USER' => __( 'NON_UK_USER', '1account-age-verification' ), 'TOBACCO_REFUSED' => __( 'TOBACCO_REFUSED', '1account-age-verification' ), ]; } /** * One order-status mapping row. * * @param string $status WooCommerce status name. * @param string $result Age-check result key. */ private function render_order_status_row( $status, $result ) { $options = $this->order_status_result_options(); ?>

render_order_status_row( isset( $statuses[ $i ] ) ? $statuses[ $i ] : '', isset( $results[ $i ] ) ? $results[ $i ] : '' ); } ?>
array_values( $statuses ), 'one_acc_user_status' => array_values( $results ), ] ); } // Init the form for the plugin settings. public function init_settings_form() { $this->render_connect_store(); woocommerce_admin_fields( $this->get_plugin_settings() ); $this->render_order_status_settings(); } /** * Link that asks WooCommerce to create a REST key and post it to platforms. */ public function render_connect_store() { $this->show_project_link_error(); $token = get_option( 'wc_oneacc_install_token' ); if ( ! is_string( $token ) || '' === $token ) { $token = wp_generate_password( 32, false ); update_option( 'wc_oneacc_install_token', $token, false ); } $connected_nonce = isset( $_GET['oneacc_nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['oneacc_nonce'] ) ) : ''; $connected = isset( $_GET['success'] ) ? sanitize_text_field( wp_unslash( $_GET['success'] ) ) : ''; if ( '1' === $connected && wp_verify_nonce( $connected_nonce, self::STORE_CONNECTED_NONCE ) ) { echo '

' . esc_html__( 'Store connected. WooCommerce sent the REST API key to 1Account.', '1account-age-verification' ) . '

'; } if ( self::store_is_connected() ) { echo '

' . esc_html__( 'This store is connected to 1Account.', '1account-age-verification' ) . '

'; return; } $callback = AgeCheck::install_callback_url( oneacc_api_url( 'platforms/woo/install' ), home_url(), $token ); $return_url = add_query_arg( 'oneacc_nonce', wp_create_nonce( self::STORE_CONNECTED_NONCE ), admin_url( 'admin.php?page=wc-settings&tab=oneacc_settings' ) ); $authorize = AgeCheck::install_authorize_url( home_url(), (string) get_current_user_id(), $return_url, $callback ); echo '

' . esc_html__( 'Connect store', '1account-age-verification' ) . '

'; echo '

' . esc_html__( 'Approving access creates a read/write REST API key and sends it to 1Account.', '1account-age-verification' ) . '

'; } /** * Project ID and secret inputs stay locked until the store has a REST key. * Platforms rejects a project link when no installation exists for the shop. * * @param bool $store_connected Result of store_is_connected(). * @return array */ public static function project_field_attributes( $store_connected ) { if ( $store_connected ) { return array(); } return array( 'disabled' => 'disabled', ); } /** * True when WooCommerce still has the REST key created for this plugin. * A failed approval callback deletes that key, so a remaining row means 1Account received it. */ public static function store_is_connected() { global $wpdb; if ( ! is_object( $wpdb ) ) { return false; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- WooCommerce stores these REST keys only in this table. The settings screen needs the current row, so a cached result would be wrong after the key is created or removed. $found = $wpdb->get_var( $wpdb->prepare( "SELECT key_id FROM {$wpdb->prefix}woocommerce_api_keys WHERE description LIKE %s LIMIT 1", $wpdb->esc_like( AgeCheck::install_api_key_description_prefix() ) . '%' ) ); return null !== $found && false !== $found; } // Save the form values for the plugin settings. public function update_settings_form() { woocommerce_update_options( $this->get_plugin_settings() ); $tobacco_id = trim( (string) get_option( 'wc_oneacc_settings_tobacco_client_id' ) ); $tobacco_secret = trim( (string) get_option( 'wc_oneacc_settings_tobacco_client_secret' ) ); $tobacco_ready = '' !== $tobacco_id && '' !== $tobacco_secret; if ( ! $tobacco_ready ) { update_option( 'wc_oneacc_settings_request_tobacco_dob', 'no' ); } elseif ( 'yes' === get_option( 'wc_oneacc_settings_request_checkout_dob' ) ) { update_option( 'wc_oneacc_settings_request_tobacco_dob', 'yes' ); } $this->save_order_status_settings(); $this->sync_project_ids(); } /** * Sends the saved project ids to platforms. Local options stay saved when the request fails. * A disconnected store stays silent. A connected store reports a missing token or a refused call. */ private function sync_project_ids() { $store_connected = self::store_is_connected(); $token = get_option( 'wc_oneacc_install_token' ); $has_token = is_string( $token ) && '' !== $token; $host = function_exists( 'oneacc_api_host' ) ? oneacc_api_host() : '1account.net'; $code = 0; $sent = false; if ( $store_connected && $has_token ) { $project_id = get_option( 'wc_oneacc_settings_client_id' ); $project_secret = get_option( 'wc_oneacc_settings_client_secret' ); $tobacco_id = get_option( 'wc_oneacc_settings_tobacco_client_id' ); $tobacco_secret = get_option( 'wc_oneacc_settings_tobacco_client_secret' ); $payload = AgeCheck::projects_payload( home_url(), $token, is_string( $project_id ) ? $project_id : '', is_string( $project_secret ) ? $project_secret : '', is_string( $tobacco_id ) ? $tobacco_id : '', is_string( $tobacco_secret ) ? $tobacco_secret : '', rest_url( 'wc/v3/orders/' ) ); $response = wp_remote_post( oneacc_api_url( 'platforms/woo/projects' ), array( 'timeout' => 10, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( $payload ), ) ); $code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response ); $sent = true; } $message = AgeCheck::project_link_failure_message( $store_connected, $has_token, $code ); if ( '' === $message ) { return; } \WC_Admin_Settings::add_error( $message ); set_transient( self::PROJECT_LINK_ERROR_TRANSIENT, $message, 120 ); if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->warning( 'Project link failed. host=' . str_replace( array( "\r", "\n" ), '', (string) $host ) . ' status=' . ( $sent ? (string) $code : 'skipped' ), array( 'source' => 'oneacc-project-link' ) ); } } /** * Shows a project-link error that survived a redirect. The save request already * passed the same text to WooCommerce, so this stays quiet on that POST. */ private function show_project_link_error() { $message = get_transient( self::PROJECT_LINK_ERROR_TRANSIENT ); if ( ! is_string( $message ) || '' === $message ) { return; } delete_transient( self::PROJECT_LINK_ERROR_TRANSIENT ); $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : ''; if ( isset( $_POST['save'] ) && wp_verify_nonce( $nonce, 'woocommerce-settings' ) ) { return; } echo '

' . esc_html( $message ) . '

'; } // Create the form for the plugin settings. public function get_plugin_settings() { $store_connected = self::store_is_connected(); $project_attributes = self::project_field_attributes( $store_connected ); $credentials_desc = __( 'Enter your 1account API credentials to validate customers age via 1Account. Login to your 1account business user account and navigate to My Business > Projects to access your project\'s ID and Secret.', '1account-age-verification' ); if ( ! $store_connected ) { $credentials_desc = __( 'Connect the store before entering project IDs. After the store is connected, log in to your 1account business user account and open My Business > Projects for each project ID and secret.', '1account-age-verification' ); } $settings = [ 'api_section_title' => [ 'name' => __( 'API credentials', '1account-age-verification' ), 'type' => 'title', 'desc' => $credentials_desc, 'id' => 'wc_oneacc_settings_section_title', ], 'client_id' => [ 'name' => __( 'Project ID', '1account-age-verification' ), 'type' => 'text', 'desc' => '', 'desc_tip' => __( 'Get your API credentials from 1Account.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_client_id', 'custom_attributes' => $project_attributes, ], 'client_secret' => [ 'name' => __( 'Project Secret', '1account-age-verification' ), 'type' => 'text', 'desc' => '', 'desc_tip' => __( 'Secret for the Project ID above.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_client_secret', 'custom_attributes' => $project_attributes, ], 'tobacco_client_id' => [ 'name' => __( 'Tobacco Project ID', '1account-age-verification' ), 'type' => 'text', 'desc' => __( 'Optional. Leave blank to always use the Project ID above.', '1account-age-verification' ), 'desc_tip' => __( 'Used when the order contains a product tagged TOBACCO.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_tobacco_client_id', 'custom_attributes' => $project_attributes, ], 'tobacco_client_secret' => [ 'name' => __( 'Tobacco Project Secret', '1account-age-verification' ), 'type' => 'text', 'desc' => __( 'Required when a Tobacco Project ID is set.', '1account-age-verification' ), 'desc_tip' => __( 'Secret for the Tobacco Project ID. It is not the Project Secret above.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_tobacco_client_secret', 'custom_attributes' => $project_attributes, ], 'request_checkout_dob' => [ 'name' => __( 'Always request date of birth at checkout', '1account-age-verification' ), 'type' => 'checkbox', 'desc' => __( 'Adds a required date of birth field to every checkout, including transactions with tobacco.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_request_checkout_dob', 'default' => 'no', ], 'request_tobacco_dob' => [ 'name' => __( 'Request date of birth at checkout for tobacco purchases', '1account-age-verification' ), 'type' => 'checkbox', 'desc' => __( 'Adds a required date of birth field when products tagged TOBACCO are in the cart.', '1account-age-verification' ), 'id' => 'wc_oneacc_settings_request_tobacco_dob', 'default' => 'no', ], [ 'type' => 'sectionend', 'id' => 'wc_oneacc_settings_section_title', ], ]; return apply_filters( 'oneacc_plugin_settings', $settings ); } public static function get_form_data() { return [ 'client_id' => get_option( 'wc_oneacc_settings_client_id' ), 'client_secret' => get_option( 'wc_oneacc_settings_client_secret' ), 'tobacco_client_id' => get_option( 'wc_oneacc_settings_tobacco_client_id' ), 'tobacco_client_secret' => get_option( 'wc_oneacc_settings_tobacco_client_secret' ), ]; } }