' . 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' ),
];
}
}