=== Valyda Age Verification ===
Contributors: valyda
Tags: age verification, woocommerce, age restriction, e-id, switzerland
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 1.13.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Age verification in the WooCommerce checkout. The ID never reaches your shop - only the outcome is stored. e-ID ready.

== Description ==

Shops selling alcohol, tobacco, vapes or other age restricted goods in
Switzerland have to check their customers' age. Today that usually means a
self-declared checkbox, which is not evidence of anything, or an uploaded ID
document, which means handling exactly the sensitive data you would rather not
hold.

The Swiss state e-ID (swiyu) offers a third option: the customer's wallet proves
that they are old enough without revealing who they are. This plugin brings that
to WooCommerce.

= What the customer sees =

A row in the checkout and a dialog that opens from it. Which channel the dialog
offers is your choice under Verification methods.

Today that is the ID picture: drag it in or pick it, see a preview, send. The
order button unlocks and the order waits for you.

The e-ID channel is built and listed there too, but not selectable yet - see
below.

= Making it look like your shop =

The check sits in the middle of the checkout, where a customer is already
paying attention. Under Appearance you set the accent and button colours, the
text and border colours, the font (your theme's or the shipped one), the size,
the corner radii and whether the check draws a box at all, plus a field for
your own CSS. Under Texts you can reword every line the customer reads.

The preview beside the settings shows the real widget and follows the fields
as you type, so you never have to save and walk to a checkout to see a colour.

Every field left empty means "leave it alone". That matters for the texts: the
check ships German, French, Italian and English and picks by the customer's
language. Leave a field empty and it stays that way; fill one in and your
wording is used for everyone.

= An age check that works today =

The e-ID has no issuing date yet, and almost nobody has a wallet. So the plugin
ships a second way: the customer sends a picture of their ID, pays, and the
order waits on a status of its own until you have looked at it. You compare the
picture with the delivery address and accept or reject.

The picture goes to your shop and never to us. We only ever record that a check
passed, how, and when.

= What gets stored =

On the order: whether the check passed, when, which profile was used, and a
reference id. That is the complete list. No name, no date of birth, no social
security number, no ID document number, no image.

For an age check those never leave the customer's wallet in the first place. The
wallet transmits a single attribute - `age_over_18` or `age_over_16` - and its
value is a boolean. Selective disclosure is a property of the credential format,
not a promise this plugin makes.

= Both checkouts are protected =

WooCommerce ships two checkouts and they share no code. The classic shortcode
checkout runs `woocommerce_checkout_process`; the block checkout places orders
through the Store API and never calls that hook at all. A plugin that only knows
the classic hook silently lets unverified orders through the block checkout.
This plugin blocks both, server side, and each path is tested separately.

The browser is never trusted. Before an order is created, PHP asks the Valyda
API for the result itself. A manipulated front end cannot place an unverified
order - the widget only drives the display.

= Check profiles =

* 18+ age check - transmits `age_over_18`, yes/no only
* 16+ age check - transmits `age_over_16`, yes/no only
* Full identity - first name, surname, date of birth

= Scope =

By default a check runs as soon as the cart holds a product you flagged as age
restricted (Product > General > "Age verification required"). You can also
require a check on every order, or decide in code with the
`valyda_cart_requires_check` filter.

= Developer hooks =

* `valyda_cart_requires_check` - your own rules for when a check runs
* `valyda_required_profile` - pick the check profile per cart
* `valyda_api_base` - point at a different endpoint for staging

= Requirements =

A Valyda account for the API key, and WooCommerce 8.0 or newer. The plugin is
compatible with High-Performance Order Storage.

The e-ID has no issuing date. The plugin runs against the swiyu public beta,
which needs a beta identity in the swiyu wallet - so the e-ID check is
something you can try, not something your customers can use yet. The ID review
does not depend on it and works from the day you switch it on.

== External services ==

This plugin is the client for Valyda, a hosted verification service. It cannot
work without it: running the state verification infrastructure yourself means
operating a Java service, a database, DID key material and a trust registry
entry, which is the problem the service exists to remove.

**What is contacted**

`https://api.valyda.ch` - the Valyda API, operated by Valyda in Switzerland.

**When, and what is sent**

* When an administrator clicks "Connect with Valyda", the plugin sends the
  shop's HTTPS return address and a random proof challenge. After dashboard
  sign-in and explicit confirmation, a one-time code and private proof are
  exchanged server to server for a shop-specific key. No key is placed in the
  browser URL. The shop address and connection reference are stored with the
  Valyda account so the administrator can review and revoke access.

* When a customer reaches a checkout that needs a check, your server creates a
  verification session. Sent: your API key, the requested check profile, and an
  opaque cart reference. No customer data.
* While the customer completes the check, the browser polls the session status
  with a one-time token scoped to that single verification. The API key stays on
  your server and never reaches the browser.
* Before an order is placed, your server reads the result. Received: a status,
  a boolean, and a timestamp.
* On every checkout that needs a check, the browser loads the verification
  widget script from `https://api.valyda.ch/widget/v1/valyda.js`. It is served
  from the API rather than bundled so that fixes reach every shop without a
  plugin update.
* The admin screen requests `https://api.valyda.ch/v1/account` with the shop key
  to show connection and subscription status. Disconnecting an automatically
  connected shop revokes its key. The "Start test verification" button creates
  a real verification.

**What Valyda stores**

The outcome, a timestamp and a session id. Processing happens exclusively in
Switzerland.

Documentation: https://valyda.ch/docs/woocommerce
Terms: https://valyda.ch/agb
Privacy policy: https://valyda.ch/datenschutz

== Installation ==

1. Install and activate the plugin.
2. Click **Connect with Valyda** in the plugin, sign in (including with Google), and confirm your shop. You return to WordPress automatically; no API key needs to be copied. Your shop must use HTTPS.
3. Choose your subscription in Valyda and refresh its status in the plugin. Manual API-key setup remains available under Advanced.
4. Under Check profiles, choose whether every order needs a check or only carts
   holding a flagged product.
5. Flag your age restricted products under Product > General.
6. Use "Start test verification" in the preview to confirm key, mode and
   reachability in one step.

== Frequently Asked Questions ==

= Does my shop ever see the customer's date of birth? =

No. For an age check the wallet transmits a single boolean. Neither your shop
nor Valyda learns the date of birth. Only the "Full identity" profile requests
name and date of birth, and you choose whether to use it.

= What happens if a customer does not have the swiyu wallet? =

Switch on the fallback under Checkout & widget and give it a label and a URL.
The widget then offers your existing method alongside the e-ID.

= Can a customer bypass the check with browser tools? =

No. Unlocking the order button in the browser changes nothing: the server asks
the API for the result before creating the order, on both the classic and the
block checkout. An unverified order is rejected.

= The customer's wallet says no matching credential is available. =

The wallet needs to be recent enough to support DCQL, the query language the
verifier uses. Ask the customer to update the swiyu app. Server side that case
looks like a verification that stays pending.

= Is this usable today? =

Yes, through the ID review: the customer sends a picture of their ID, you look
at it and decide. That needs no e-ID and no other provider.

The e-ID check itself runs against the swiyu public beta, which needs a beta
identity in the wallet. The federal trust infrastructure is not in production
yet and there is no date for it, so treat that path as a preparation, not as a
feature your customers can use.

= Where is data processed? =

Switzerland, on infrastructure operated by Valyda.

== Screenshots ==

1. What the customer gets: drag a picture of their ID in, or pick one. The preview is there so they can see they photographed the right side.
2. The checkout row. The order button stays locked until a method has answered.
3. The shop's side: the order waits on "Age check pending", the picture sits next to the delivery address, and one click accepts or rejects. The picture is deleted either way.
4. The verification result on the order screen, including which method answered.
5. Verification methods. The ID picture works today; the e-ID is listed with the reason it is not selectable yet.
6. Check profiles, and which attribute each one transmits - an age limit is a yes/no, nothing else.
7. Appearance: the preview follows the fields as you type, so you never have to save and walk to a checkout.

== Changelog ==

= 1.13.1 =
* Connecting opens a separate login tab. After completion it closes and the original plugin page updates automatically.
* Same-origin status messages work even when the login provider separates browser tabs. A return link is available if the browser prevents automatic closing.

= 1.13.0 =
* Connect WooCommerce directly to your Valyda account: sign in, confirm the shop and return automatically.
* One-time connection codes with private server-side proof and WordPress session binding.
* View and revoke connected shops in the dashboard. Manual keys remain under Advanced.

= 1.12.2 =
* Disconnect the shop from a Valyda account during setup or from the dashboard. Settings are kept; checkout verification is disabled and subscriptions remain unchanged.

= 1.12.1 =
* Clear numbered setup steps with muted completed steps and a highlighted next action.

= 1.12.0 =
* Guided first setup for account, subscription and API key. Settings and widget preview stay hidden until connected and entitled.
* Connection status now validates the API key and subscription without starting a verification or using quota.
* First-time connections leave checkout activation to the merchant; existing settings remain intact.

= 1.11.1 =
* Wider widget preview column in the WordPress dashboard, with earlier stacking on smaller screens.

= 1.11.0 =
* Updated valyda. wordmark and restrained blue-grey admin styling to match the new website.
* Refreshed verification dialog with a blurred backdrop, clearer typography and bolder buttons.
* Merchant appearance settings remain supported; updated default colour previews and translations.

= 1.10.4 =
* Pakete Standard, Plus und Pro. Die einmalige Testphase startet beim ersten Abo-Abschluss.

= 1.10.3 =
* Starter, Shop and Growth subscriptions with included monthly document checks.
* Clear trial/quota limit messages and account links for usage and spending limits.
* Reading an existing verification no longer clears a quota warning.

= 1.10.2 =
* Opening the admin widget preview now starts the test verification instead of
  staying in the loading state. Both test buttons use the same flow, with a
  request timeout and a retry action if the connection fails.

= 1.10.1 =
* The admin test verification now uses the same verification channel as checkout
  and opens the current ID verification widget instead of the legacy e-ID view.

= 1.10.0 =
* You can now get an account yourself. The settings screen sends you to a
  sign-up that hands you a key in a minute, with no payment details and no
  appointment - until now the only way in was an e-mail to us.
* The plugin says something useful when the subscription needs attention. A
  trial about to end or a failed payment shows up as a notice on every admin
  screen, because a shop owner about to lose the age check is looking at
  orders, not at the age check settings.
* An account that has lapsed no longer looks like a broken API. "Status 402"
  is now a sentence naming what happened and where to fix it.

= 1.9.1 =
* The German, French and Italian catalogs are complete again. 29 strings per
  language had been added with the verification methods and the list columns
  and never translated - among them the ones every customer reads at the
  checkout, and every column value a shop owner sees.

= 1.9.0 =
* Renamed. The plugin is now "Valyda Age Verification" and its text domain is
  valyda-age-verification. The old name led with the e-ID, which has no issue
  date and is one channel among several - and the directory slug it would have
  produced can never be changed again. The name says what the plugin does; the
  e-ID is what it is ready for.
* The tag "swiyu" is gone. Tags that only one plugin uses are not shown, so it
  was one of five slots spent on nothing. "age restriction" took its place.
* "WC tested up to" was three majors behind and now names the version the demo
  shop actually runs.
* The translation catalogs were rebuilt. They still held 18 translations for
  strings removed in 1.8.0.

= 1.8.1 =
* Tested against WordPress 7.1, and the demo shop now runs that version. The
  readme claimed 6.9, which keeps a plugin out of the directory search.
* The two queries against the review table use the %i placeholder for the table
  name instead of building the statement by hand. Same SQL, minus the reason to
  silence a sniff.
* The block checkout escapes its error message on the throw, like the fallback
  path always did.

= 1.8.0 =
* New: an "Age check" column in the product list, plus quick edit and bulk
  edit. Flagging products was a per-product job before, which is workable for
  nine products and hopeless for two hundred - the honest outcome was a shop
  that believes it gates its whole range and gates two thirds of it.
* New: an "Age check" column in the order list, showing verified, awaiting a
  decision, or nothing asked for. Until now the answer existed only inside
  each order.
* Removed three settings that never did anything: "Redirect on success",
  "Redirect on failure" and "Verification lifetime". They were saved and
  validated and read by no code at all - the widget stays in the checkout and
  the lifetime is the API's. A setting that promises something it does not do
  is worse than a missing one.
* The wording of the ID check is now editable. The text screen had a full
  group for the e-ID dialog nobody can reach and nothing for the channel every
  customer actually sees.
* An order whose channel could not be determined is recorded as unknown rather
  than as an e-ID check. It was a fair guess while the e-ID was the only
  machine channel, and a false entry in your records now that it is not.

= 1.7.0 =
* New: the ID check runs at a verification service. The customer photographs
  their ID on a page hosted by that service; the picture reaches neither your
  shop nor Valyda. Back comes "old enough" or "not", and that is all that is
  stored. This is the default for new installs and is switched on for existing
  ones on update.
* The ID picture channel is no longer selectable. It kept the document away
  from Valyda but not from you, and not handling other people's ID documents
  is the point of this plugin. Pictures already waiting for a decision can
  still be reviewed as before, and nothing about existing orders changes.
* The order screen now names the verification service as its own method.
  Before, anything that was not the shop's own process was labelled "Checked
  with the e-ID" - which was true while the e-ID was the only machine channel
  and would have been a false entry in your records the moment it was not.

= 1.6.0 =
* New: Verification methods. One list, one place - which channels the checkout
  offers. The ID picture is on by default.
* The e-ID is listed but cannot be selected. The code is there and works
  against the swiyu public beta, but a public beta identity proves nothing
  about anyone's age: anyone can issue themselves one. Offering it in a real
  checkout would look like an age check and be none. It becomes selectable
  when the Confederation issues real credentials - a default, not a rewrite.
* The upload now lives inside the dialog, with drag and drop, a preview of the
  picture and its own states. It used to open a panel further down the
  checkout page, which meant the thing you clicked vanished and the field
  appeared somewhere you had to go looking for.
* A checkout with no e-ID no longer creates a verification session or shows a
  QR code at all.


= 1.5.0 =
* New: ID review - an age check that works today, with no e-ID and no provider.
  A customer without a wallet sends a picture of their ID, pays, and the order
  waits on its own status until you have looked at it. Accepting releases the
  order; rejecting cancels it.
* The picture goes to your shop, never to Valyda. It is held in your database
  rather than in wp-content/uploads, so no URL can reach it, and it is deleted
  the moment you decide. Undecided pictures are removed after a few days.
* Orders that are waiting show as "Age check pending" and can be filtered.
* Off by default. Switching it on means holding identity documents in your own
  shop, and that should be a decision, not a surprise.

= 1.4.0 =
* An alternative check can now release an order. If your own process decides a
  customer is old enough, your backend calls Valyda_Checkout::confirm_fallback()
  and the checkout is released - the e-ID is one way to answer the check, not
  the only one. Nothing about the check itself is sent: no document number, no
  date of birth, not even a note.
* Orders record which method answered, as _valyda_channel, and the order screen
  says so. "Verified" stopped being the whole answer once there were two ways.
* A customer coming back from your own process is no longer asked again. The
  order button used to stay disabled while the server would have let the order
  through.

= 1.3.0 =
* New Appearance settings: accent, button, text, border and surface colours,
  the theme's font or the shipped one, font size, corner and button radii,
  button weight and capitals, and whether the check draws a box. Plus a field
  for your own CSS, loaded on the checkout only.
* New Texts settings: every wording the customer can read, including the
  messages WooCommerce prints when it holds an order back.
* The preview beside the settings now shows the real widget with your own
  colours and wording, and follows the fields as you type. It used to show
  nothing until you started a test verification.
* An empty field means "keep the default", and the default is per language.
  Only what you fill in is sent, so the check keeps its four translations for
  everything you leave alone.
* The title and introduction that used to live under "Checkout & widget" moved
  to Texts. Existing wording is carried over on upgrade.

= 1.2.0 =
* The age check now sits directly above the order button in the block checkout
  instead of beside the order total, where it read as a remark rather than as a
  step of the purchase.
* The order button is disabled until the check has passed. It was clickable
  before; the order was still refused - on the server, as always - but a button
  that cannot work should not invite the click.

= 1.1.1 =
* The dialog survives a re-render of the checkout. Both checkouts rebuild their
  markup on cart and address changes, which used to close the dialog on a
  customer mid-scan and abandon the session behind it.

= 1.1.0 =
* The check now opens in a modal from a button in the checkout, instead of
  sitting expanded in the order summary.
* The verification session is created when the customer opens the modal, not
  when the checkout loads. Sessions have a fixed lifetime, and a slowly filled
  address form used to outlast them.
* The QR code carries the Valyda mark in its centre.
* Translations for German (Switzerland and Germany), French and Italian.

= 1.0.0 =
* First release.
* Age verification at the classic and the block checkout, both blocked server
  side.
* Check profiles for 18+, 16+ and full identity.
* Product level flag for age restricted items, or a check on every order.
* Fallback slot for an existing verification method.
* Admin screens for status, settings, verifications and support, with a preview
  that runs a real test verification.
* Translations for German (Switzerland and Germany), French and Italian.
* Compatible with High-Performance Order Storage.

== Upgrade Notice ==

= 1.1.0 =
The check moves into a modal and the QR code gets the brand mark. No settings
change; shop-specific wording for the checkout row is preserved.

= 1.0.0 =
First release.
