{"id":341237,"date":"2026-07-16T13:45:18","date_gmt":"2026-07-16T13:45:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/pay4all-age-verification\/"},"modified":"2026-07-16T13:44:57","modified_gmt":"2026-07-16T13:44:57","slug":"pay4all-age-verification","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/pay4all-age-verification\/","author":23516902,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"7.0.1","requires":"5.9","requires_php":"7.4","requires_plugins":null,"header_name":"Pay4All Age Verification","header_author":"Pay4All","header_description":"Age verification and legal-age gate for Pay4All Shop via ID + selfie capture, cross-device QR flow, AES-256-GCM encryption at rest. Ideal for alcohol, tobacco, or any adult-only product.","assets_banners_color":"","last_updated":"2026-07-16 13:44:57","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/pay4all.ch\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":33,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"pay4all","date":"2026-07-16 13:44:57"}},"upgrade_notice":{"1.0.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3610333,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3610333,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"Product edit screen : the <em>Requires age verification<\/em> checkbox, added right next to the price row on Pay4All Shop products.","2":"Customer checkout with an age-restricted product in the cart : the KYC panel appears in the \u00ab V\u00e9rification d'\u00e2ge \u00bb step with the QR code + status of each photo slot.","3":"Mobile capture page (loaded from the QR) : native camera capture for ID recto \/ verso and selfies, live upload progress, encrypted at rest before storage.","4":"Admin lightbox : merchant clicks the shield icon on the order edit screen \u2014 the encrypted photos are streamed, decrypted on-the-fly, and displayed side-by-side without being written back to disk.","5":"Settings page : merchant chooses required documents (ID \/ selfies \/ both), OR \/ AND mode, purge delay, and can override the customer-facing texts in each activated language."}},"plugin_section":[],"plugin_tags":[19958,70877,5616,5240,30944],"plugin_category":[],"plugin_contributors":[268195],"plugin_business_model":[],"class_list":["post-341237","plugin","type-plugin","status-publish","hentry","plugin_tags-adult","plugin_tags-age-gate","plugin_tags-age-verification","plugin_tags-id-verification","plugin_tags-selfie","plugin_contributors-pay4all","plugin_committers-pay4all"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/pay4all-age-verification\/assets\/icon-128x128.png?rev=3610333","icon_2x":"https:\/\/ps.w.org\/pay4all-age-verification\/assets\/icon-256x256.png?rev=3610333","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p><strong>Pay4All Age Verification<\/strong> blocks any order containing age-restricted products until the customer has uploaded a valid ID (recto + verso) and\/or two selfies. Photos are captured either directly on the customer's desktop or, more commonly, through a QR code that hands off to their smartphone.<\/p>\n\n<p>The plugin plugs into <strong>Pay4All Shop<\/strong> (pay4all-form). Install it alongside Pay4All Shop and it will only surface where a form contains an age-restricted product.<\/p>\n\n<h4>How it works<\/h4>\n\n<ol>\n<li>Merchant ticks <em>Requires age verification<\/em> on any product edit screen (Pay4All Shop product).<\/li>\n<li>When a customer adds such a product to their form, a panel appears in the KYC step showing a QR code.<\/li>\n<li>Customer scans the QR with their phone -&gt; the mobile capture page opens -&gt; they take the required photos with their phone camera.<\/li>\n<li>The desktop form polls the server every 4 seconds and unlocks automatically when every required slot is filled \u2014 no page refresh needed.<\/li>\n<li>On order submit, the encrypted photos are moved from the session bucket to the order-scoped bucket, and are only decrypted on demand by an admin from the order edit screen.<\/li>\n<\/ol>\n\n<h4>Security<\/h4>\n\n<ul>\n<li>Photos are encrypted with <strong>AES-256-GCM<\/strong> using a per-order (or per-user) subkey derived via HMAC-SHA256 from a master key stored in <code>wp-content\/uploads\/p4all-age-secure\/age.key<\/code> (outside the database backup).<\/li>\n<li>Ciphertexts live in a private folder guarded by <code>.htaccess<\/code>, <code>index.php<\/code> and <code>web.config<\/code> so they are never web-servable.<\/li>\n<li>Admin decryption endpoint is nonce-protected and requires the <code>edit_users<\/code> capability.<\/li>\n<li>Photos are auto-purged when the order (or user) is permanently deleted.<\/li>\n<\/ul>\n\n<h4>WooCommerce support<\/h4>\n\n<p>Pay4All Age Verification focuses on <strong>Pay4All Shop<\/strong>. The free plugin does not ship any merchant-facing WooCommerce surfaces (no checkout panel, no order metabox, no product-level \u00ab Requires age verification \u00bb checkbox, no register-form opt-in). If you run a WooCommerce shop and want the full verification flow (cross-device QR checkout panel, order edit metabox with decrypted photo lightbox, product-level checkbox with Quick Edit, My Account register-form opt-in, HPOS + Checkout Blocks compatibility), the WooCommerce integration is available in the paid add-on <strong>Pay4All Pro<\/strong>, distributed from pay4all.ch and not through WordPress.org.<\/p>\n\n<h4>WPML \/ Polylang<\/h4>\n\n<p>Multilingual-aware : when the <em>Requires age verification<\/em> flag is set on a source product, every translation is treated as age-restricted too \u2014 even if the flag hasn't been mirrored to the translation.<\/p>\n\n<h4>Available languages<\/h4>\n\n<p>Fran\u00e7ais (fr_FR), Deutsch (de_DE), Italiano (it_IT), English (en_US).<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin does not connect to any external service. Everything runs on the site: photo capture, encryption, storage and admin decryption are all local. No data is sent to any third-party server.<\/p>\n\n<h3>Source code<\/h3>\n\n<p>The complete, non-minified source of this plugin is bundled inside the ZIP itself. Every PHP, CSS and JavaScript file is human-readable and directly editable. No compiler, transpiler or bundler is required to work on this plugin.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin to <code>\/wp-content\/plugins\/pay4all-age-verification\/<\/code> or install via <em>Plugins &gt; Add New<\/em>.<\/li>\n<li>Activate it from the <em>Plugins<\/em> menu.<\/li>\n<li>Open <em>Pay4All AGE &gt; Param\u00e8tres<\/em> to tune what documents you require and to override the customer-facing texts if needed.<\/li>\n<li>On each product that requires an age check, tick the <em>Requires age verification<\/em> checkbox on the product edit screen (Pay4All Shop product).<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20i%20need%20pay4all%20shop%20or%20woocommerce%3F\"><h3>Do I need Pay4All Shop or WooCommerce?<\/h3><\/dt>\n<dd><p>Yes. Pay4All Age Verification is a companion plugin \u2014 it plugs into either <strong>Pay4All Shop<\/strong> (free, ships the form-based checkout) or <strong>WooCommerce<\/strong> (via the paid Pay4All Pro add-on). On a bare WordPress install without either, the plugin stays dormant and shows an admin notice on the Plugins screen.<\/p><\/dd>\n<dt id=\"how%20are%20photos%20encrypted%3F\"><h3>How are photos encrypted?<\/h3><\/dt>\n<dd><p><strong>AES-256-GCM<\/strong> with a per-order (or per-user) subkey derived via HMAC-SHA256 from a master key. The master key is generated once and stored in <code>wp-content\/uploads\/p4all-age-secure\/age.key<\/code> (mode 0600). Ciphertexts live in the same private folder, guarded by <code>.htaccess<\/code>, <code>index.php<\/code> and <code>web.config<\/code> so they are never web-servable \u2014 a direct URL always returns 403 \/ 404. See the <em>Security<\/em> section above for the full threat model.<\/p><\/dd>\n<dt id=\"where%20is%20the%20master%20encryption%20key%20stored%3F\"><h3>Where is the master encryption key stored?<\/h3><\/dt>\n<dd><p>Outside the database, in <code>wp-content\/uploads\/p4all-age-secure\/age.key<\/code>. This means a stolen SQL dump alone is not enough to decrypt the photos \u2014 the attacker would also need read access to that specific file. Please make sure your backup strategy either encrypts <code>wp-content\/uploads\/p4all-age-secure\/<\/code> or excludes it entirely from the backup archive.<\/p><\/dd>\n<dt id=\"what%20happens%20to%20the%20photos%20when%20an%20order%20is%20deleted%3F\"><h3>What happens to the photos when an order is deleted?<\/h3><\/dt>\n<dd><p>They are auto-purged. Both the WooCommerce order lifecycle (<code>woocommerce_delete_order<\/code> hook) and Pay4All Shop order deletion trigger <code>KycStorage::purge_order()<\/code>, which removes every encrypted blob and the order-scoped directory. A daily cron also purges session-scoped buckets older than the configured TTL (default 24 h).<\/p><\/dd>\n<dt id=\"what%20happens%20if%20a%20user%20account%20is%20deleted%3F\"><h3>What happens if a user account is deleted?<\/h3><\/dt>\n<dd><p>The <code>delete_user<\/code> hook fires <code>KycStorage::purge_user()<\/code> \u2014 every stored user photo (recto \/ verso \/ selfie_1 \/ selfie_2) is removed together with the user record. GDPR-friendly by default.<\/p><\/dd>\n<dt id=\"does%20the%20mobile%20capture%20page%20work%20on%20all%20phones%3F\"><h3>Does the mobile capture page work on all phones?<\/h3><\/dt>\n<dd><p>The page uses the native <code>&lt;input type=\"file\" accept=\"image\/*\" capture=\"\u2026\"&gt;<\/code> API \u2014 supported by Safari (iOS 6+), Chrome, Firefox, Edge on both Android and iOS. On the desktop side, the QR code is generated with a pure-JS library (no external service call) and the pairing is polled every 4 seconds via a REST endpoint scoped to the session token.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20wpml%20%2F%20polylang%3F\"><h3>Does it work with WPML \/ Polylang?<\/h3><\/dt>\n<dd><p>Yes. When a product's <em>Requires age verification<\/em> flag is ticked on a source-language product, every translation is treated as age-restricted too \u2014 even if the flag hasn't been mirrored to the translation post. Same behaviour on WooCommerce products via Pay4All Pro.<\/p><\/dd>\n<dt id=\"which%20languages%20ship%20out%20of%20the%20box%3F\"><h3>Which languages ship out of the box?<\/h3><\/dt>\n<dd><p>French (fr_FR), German (de_DE), Italian (it_IT) and English (en_US). Every customer-facing string is also overridable per-language from the settings page \u2014 merchants can rewrite the KYC panel copy without touching a <code>.po<\/code> file.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20contact%20any%20external%20service%3F\"><h3>Does the plugin contact any external service?<\/h3><\/dt>\n<dd><p>No. Photo capture, encryption, storage and admin decryption are all local \u2014 nothing leaves the site. The QR code is generated client-side ; the mobile page polls only your own WordPress REST API.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>Product-level <em>Requires age verification<\/em> checkbox (Pay4All Shop).<\/li>\n<li>Cross-device QR flow (desktop QR -&gt; mobile capture page).<\/li>\n<li>AES-256-GCM encryption at rest, per-order and per-user derived subkeys.<\/li>\n<li>Metabox on the Pay4All Shop order edit screen with nonce-protected decrypted-photo streaming and a lightbox to view every document side-by-side.<\/li>\n<li>Shield column on the WordPress users list \u2014 one click opens the customer's verified documents.<\/li>\n<li>Settings page with OR \/ AND mode, purge delay, per-locale text overrides.<\/li>\n<li>FR \/ DE \/ IT \/ EN built-in translations, with 4-language text override per string from the settings page.<\/li>\n<li>WPML \/ Polylang aware : translated products inherit the <em>Requires age verification<\/em> flag from their source.<\/li>\n<\/ul>","raw_excerpt":"Age verification and legal-age gate for Pay4All Shop. ID + selfie capture, cross-device QR, AES-256-GCM encryption. 100% local.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/341237","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=341237"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/pay4all"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=341237"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=341237"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=341237"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=341237"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=341237"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=341237"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}