Category: Setup Wizard / Custom Fields
Description:
Currently, custom fields can only be attached to the Setup Wizard via a Registration form — but that form (and any fields on it) is automatically skipped when the user is already logged in. This means there is no way to require a field at the point of payment/plan-selection for existing members who are renewing, upgrading, or purchasing an additional plan.
This is a problem for any site that needs to capture a legally required affirmative action at checkout rather than at signup. In our case, EU Directive 2011/83/EU Art. 16(m) requires an explicit consent action from the customer to waive their 14-day withdrawal right on digital content/services — and that consent has to be captured at the moment of purchase, not buried in a policy page or only shown to new registrants.
Requested feature:
Add the ability to attach custom fields (or at minimum a required checkbox/agreement field type) directly to the plan/payment step of the Setup Wizard, independent of the Registration form — so it displays and is enforced for logged-in members purchasing/renewing/changing a plan, not just at initial signup.
Why it matters:
Any ARMember site with legal, contractual, or compliance requirements tied to the purchase moment (consent, waivers, terms acceptance, age confirmation, etc.) needs this, and right now the only path is building custom validation against internal hooks that aren't documented extension points — which is fragile across plugin updates.
We currently have a working mu-plugin implementation using arm_after_setup_gateway_section, arm_validate_field_value_before_form_submission, and arm_after_setup_form_validate_action, and are happy to share the exact code with your dev team if it helps evaluate this.
Current implementation workaround via mu-plugins (fix-arm-refund-waiver-consent.php):
<?php
/* Adds a required "immediate access / refund waiver" checkbox to ARMember's
plan checkout (the [arm_setup] form) and blocks checkout server-side
unless it's checked — see refunds.html Section 3, which states the
14-day EU/UK withdrawal right is waived once a subscribed module is
accessed. ARMember's own custom-field system can't cover this: its
registration/custom fields are skipped entirely for logged-in users
(confirmed in class.arm_membership_setup.php, arm_member_validate_meta_details()
call with $is_validate_form_field = 0), so a native field would only ever
apply to brand-new signups, not existing members buying/renewing a plan.
Hooking ARMember's own render + validation pipeline instead, without
touching its core files (would be wiped on plugin update):
- arm_after_setup_gateway_section: filters the checkout form's HTML while
it's still being assembled, before </form> is written, so the injected
checkbox is a real field that gets POSTed. Fires for both new-signup and
logged-in-member checkout, since only the *registration* module (not the
payment/gateway module) is skipped when logged in.
- arm_validate_field_value_before_form_submission: fires unconditionally
at the end of ARMember's arm_member_validate_meta_details(), regardless
of $is_validate_form_field — the one validation point common to both
paths. Returning an errors array here blocks checkout the same way
ARMember's own required-field errors do.
- arm_after_setup_form_validate_action: fires only once validation has
already passed, so by the time this runs we know the box was checked —
used here purely to write an audit-trail record (timestamp, IP, user
agent) against the user, as evidence if a chargeback or withdrawal
claim is ever disputed. */
define('QBW_REFUND_WAIVER_FIELD_SLUG', 'qbw_refund_waiver_consent');
function qbw_refund_waiver_is_paid_checkout($post_data) {
return !empty($post_data['setup_action']) && $post_data['setup_action'] === 'membership_setup'
&& (!empty($post_data['payment_gateway']) || !empty($post_data['_payment_gateway']));
}
function qbw_refund_waiver_label_text() {
$lang = function_exists('pll_current_language') ? pll_current_language() : substr(get_locale(), 0, 2);
$labels = [
'it' => 'Richiedo l’accesso immediato al Servizio e riconosco di perdere il mio diritto di recesso di 14 giorni non appena accedo a un modulo sottoscritto. Consulta la nostra Politica di Rimborso e Cancellazione.',
'en' => 'I request immediate access to the Service and acknowledge that I lose my 14-day right of withdrawal once I access any subscribed module. See our Refund & Cancellation Policy.',
];
return $labels[$lang] ?? $labels['en'];
}
/* Render: append the checkbox to the payment/gateway section of the checkout form. */
add_filter('arm_after_setup_gateway_section', function ($module_content) {
$text = esc_html(qbw_refund_waiver_label_text());
$module_content .= '<div class="arm-df__form-field arm_module_box" style="margin:16px 0;padding:14px 16px;border:1px solid var(--color-divider, #ccc);">'
. '<label style="display:flex;align-items:flex-start;gap:10px;font-size:.92rem;line-height:1.5;cursor:pointer;">'
. '<input type="checkbox" name="' . esc_attr(QBW_REFUND_WAIVER_FIELD_SLUG) . '" value="1" required style="margin-top:3px;flex-shrink:0;">'
. '<span>' . $text . '</span>'
. '</label></div>';
return $module_content;
}, 10, 1);
/* Validate: block checkout server-side if the box wasn't checked. */
add_filter('arm_validate_field_value_before_form_submission', function ($return, $armform, $posted_data) {
if (!qbw_refund_waiver_is_paid_checkout($posted_data) || !empty($posted_data[QBW_REFUND_WAIVER_FIELD_SLUG])) {
return $return;
}
$errors = is_array($return) ? $return : [];
$errors[QBW_REFUND_WAIVER_FIELD_SLUG] = qbw_refund_waiver_label_text();
return $errors;
}, 10, 3);
/* Audit trail: log consent once checkout has actually passed validation. */
function qbw_record_refund_waiver_consent($user_id, $context) {
if (empty($user_id)) {
return;
}
$log = get_user_meta($user_id, 'qbw_refund_waiver_consent_log', true);
$log = is_array($log) ? $log : [];
$log[] = [
'time' => current_time('mysql'),
'ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
'ua' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
'context' => $context,
];
update_user_meta($user_id, 'qbw_refund_waiver_consent_log', $log);
}
/* Existing member buying, changing, or renewing a plan through the setup form. */
add_action('arm_after_setup_form_validate_action', function ($setup_id, $post_data) {
if (empty($post_data[QBW_REFUND_WAIVER_FIELD_SLUG]) || !is_user_logged_in()) {
return;
}
qbw_record_refund_waiver_consent(get_current_user_id(), 'setup:' . $setup_id);
}, 10, 2);
/* Brand-new signup (including signup-with-plan-purchase through the setup wizard,
which creates the account via the same arm_register_new_member() call). */
add_action('arm_after_add_new_user', function ($user_id, $posted_data) {
if (empty($posted_data[QBW_REFUND_WAIVER_FIELD_SLUG])) {
return;
}
qbw_record_refund_waiver_consent($user_id, 'registration');
}, 10, 2);