=== TextTango ===
Contributors: texttango, sefakor20
Tags: sms, otp, woocommerce, two-factor, notifications
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 8.1
Stable tag: 0.20.3
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Send SMS from WordPress and WooCommerce with TextTango — passwordless login, order notifications, delivery receipts and consent handling.

== Description ==

TextTango connects your WordPress or WooCommerce site to your TextTango account and sends SMS for the moments
that matter.

**Sign in by phone**

* Passwordless login with a one-time code, as a shortcode or on the WordPress login screen.
* Optional two-factor authentication, per role.
* Optionally require a customer to verify the phone number they check out with, on both classic and block
  checkout, before an order can be placed. Off by default.
* Codes are hashed, single-use, rate limited per number and per address, and never written to the log in full.

**WooCommerce order notifications**

* Seven events — payment received, completed, on hold, cancelled, refunded, payment failed, and a staff alert
  on a new order — each switched on separately with its own wording.
* A live character and segment count while you write, so you can see before sending whether a message costs one
  SMS or two.
* Works with High-Performance Order Storage and block checkout.

**Consent, kept properly**

* An unticked consent box at checkout and in the customer's account, recorded with a timestamp and a source.
* Optional double opt-in: agreeing sends one message to the number, and only an answer from that handset turns
  it into permission.
* Order updates are treated as service messages and keep sending; marketing needs consent.
* A number the platform refuses is added to an opt-out list, and nothing is sent to it again.

**Marketing that respects the opt-out list**

* Send one message to everyone who agreed, with a review step showing recipients, cost and who was excluded —
  and why — before anything is spent.
* The audience is built from your own consent records, so somebody who opted out an hour ago is left out.
* Basket reminders for signed-in customers who left without buying, cancelled the moment they order.
* Coupon campaigns that check the code is live and warn you if there is not enough of it to go round.
* Choose a campaign's audience: your consenting customers, numbers you supply, or both. Supplied numbers are
  still filtered through your opt-out list, and using them is recorded against your name.

**Delivery receipts you can prove**

* A signed webhook endpoint, registered with TextTango from the settings screen.
* A test event you can fire and watch arrive, so the loop is proven rather than assumed.
* A self-healing poller for receipts that never turn up.

**Spending you can bound**

* Daily, monthly and per-number caps, with a per-number cap that catches a loop aimed at one customer.
* Marketing pauses first when the balance runs low. Sign-in codes are never held back by any limit.
* A circuit breaker that stops waiting on an API that is not answering.

**Built to be built on**

* A public PHP API: `texttango_send_sms()`, `texttango_is_connected()`, `texttango_get_balance()`,
  `texttango_normalize_phone()`.
* Documented actions and filters, and six WP-CLI commands.
* An audit trail of who changed what — never of what a credential was.

**Safe by default**

* Sandbox mode turns itself on outside production, so a staging clone never texts a real customer.
* Every notification is off until you switch it on, so connecting the plugin texts nobody by itself.
* Credentials are encrypted at rest and never reach JavaScript or any REST route.

The plugin is a thin client of the TextTango v2 API. You need a TextTango account and an API token, created in
the TextTango dashboard under Profile → API Tokens.

== External services ==

This plugin is a thin client of the TextTango SMS API (https://app.texttango.com/docs/api/v2), operated by
TextTango (rCodez). Every SMS the plugin sends — a sign-in code, a checkout verification code, a WooCommerce
order notification, a form-submission alert, or a marketing campaign message — is delivered through this API, so
it is required for the plugin's core functionality.

What is sent, and when: the recipient's phone number and the text of the message, sent at the moment each of
those events fires (for example, the instant a customer requests a sign-in code, or the instant an order changes
status and a notification is due). No other personal data is sent. TextTango sends delivery status back to the
site over a webhook the site owner registers from the Setup screen, so the plugin can show whether a message
was delivered.

This service is provided by TextTango: Terms of Service (https://texttango.com/terms), Privacy Policy
(https://texttango.com/privacy).

== Installation ==

1. Upload the plugin to `/wp-content/plugins/texttango` and activate it.
2. Go to **TextTango → Setup** and work down the stages: paste your API token, choose a sender ID, register the
   site for delivery receipts, send yourself a test message, and switch on the notifications you want.
3. Each stage tells you what it needs and shows its own state, so you can stop and come back.

== Frequently Asked Questions ==

= Do I need WooCommerce? =

No. Sign-in by phone, the public API and WP-CLI all work on any WordPress site. WooCommerce is needed only for
order notifications.

= Can I create an API token from WordPress? =

No. Tokens are created in the TextTango dashboard and shown once. The Setup screen links you to the right page
and lists the abilities the token needs.

= Will a staging site text my customers? =

No. Sandbox mode turns on automatically when the site environment is not `production`, and logs messages
instead of sending them. It is also the one setting that never travels in a settings export, so importing a
production configuration into a staging clone cannot switch it off.

= Why can I not register the delivery-receipt URL on my local site? =

TextTango only accepts a callback on a public HTTPS address, because it has to be able to reach it. Everything
else works locally; the Setup screen says so rather than letting the attempt fail.

= What happens if a message cannot be delivered? =

It is recorded in the log with the reason. You can optionally have the plugin email the customer instead — but
never for a message somebody opted out of, never for a sign-in code, and never after a timeout, since the text
may have arrived after all.

= Does a spend cap stop sign-in codes? =

No. Limits shed from the bottom up: marketing first, then order updates. A code that does not arrive locks
somebody out of their own account, so codes pass through every ceiling.

= Can I move a configuration between sites? =

Yes — **TextTango → Advanced** exports the settings as JSON and imports them. Credentials are not included, and
neither is sandbox mode.

== Screenshots ==

1. The Setup screen: every stage with its state and its controls in place.
2. Notifications, with a live segment count as you write.
3. The message log, with delivery status and the reason anything was skipped.

== Changelog ==

= 0.20.3 =
* Updated the bundled Action Scheduler library from 3.9.3 to 4.1.0.
* The "Role for new accounts" setting for phone-based self-registration now only accepts roles with no
  capabilities beyond WordPress's own `read` — the same ceiling a subscriber already has. Roles with real
  capabilities (Editor, Shop Manager, or any custom role like them) are no longer selectable there, and any
  such value already saved falls back to a safe role automatically.

= 0.20.2 =
* Removed the Plugin URI header — it had come to match the Author URI exactly, which wordpress.org's own
  submission form rejects as ambiguous. Author URI already says everything it did.

= 0.20.1 =
* Documented the plugin's use of the TextTango API as an external service, with links to its Terms of Service
  and Privacy Policy.
* Fixed a broken Plugin URI.
* `composer.json` now ships with the plugin, so its dependencies are inspectable.

= 0.20.0 =
* An OTP step can now gate order placement: a customer verifies the phone number they checked out with before
  the order goes through, on classic and block checkout alike. Off by default — a merchant switches it on from
  Sign-in settings. Enforced server-side on both checkout paths, so it holds even with JavaScript disabled.

= 0.19.1 =
* The customer-facing stylesheet (the sign-in form) no longer ships every admin-screen style alongside it —
  tables, charts, badges, the setup checklist and the rest now build into a separate admin-only bundle. The
  stylesheet a storefront visitor downloads is about a fifth of the size it was.
* Gravity Forms' field-mapping logic — reading a composite field such as Name or Address, which Gravity Forms
  splits into several numbered inputs rather than one — now has direct test coverage.

= 0.19.0 =
* Campaigns can now be narrowed further: how many times somebody has ordered, which product category they bought
  from, and which country their orders were billed to — on top of the existing recency, spend and single-product
  filters, and combinable with all of them the same way recency and spend already combine.
* The review screen's summary of who a campaign is going to reads as one sentence whatever combination of these
  you choose.

= 0.18.0 =
* **Fixed:** personal discount codes were missing from WordPress's privacy tools. A data-subject export left
  out the codes issued to that person, an erasure left their number behind, and no retention setting ever
  removed those rows. If you have answered a request since 0.17.0, the codes were not included.
* **Fixed:** an erasure could also miss message-log rows matched only by phone number rather than by account,
  because it cleared the number from the profile before looking those rows up.
* Erasing somebody now unlinks the coupons minted for them and keeps the coupons themselves — a code already
  used is attached to a real order, and removing it would change that order's record.
* Your retention setting now covers campaign recipients as well as the message log.
* **New:** open a personal campaign from its history row to see who it reached, the code each person was given,
  and whether it has been used.

= 0.17.2 =
* **Fixed:** a campaign you had just sent was missing from Analytics and the dashboard widget for several
  minutes. The figures were cached and nothing cleared the cache when you sent something, so both screens
  showed the state of a few minutes ago without saying so.
* The Analytics screen now reads live every time you open it, and the dashboard widget is refreshed the moment
  anything is sent.
* A campaign is asked how it went about half a minute after it goes out, rather than on the next five-minute
  check, so its cost and delivered count appear while you are still looking.

= 0.17.1 =
* **Fixed:** the Spent figure on Analytics and the dashboard widget left campaigns out. It showed what your
  per-message sending had cost while counting campaign recipients in the Sent total beside it, so the two did
  not describe the same thing. TextTango reports what a campaign cost and the plugin was not reading it.
* Campaigns sent before this update are priced up in the background over the next few minutes, so your older
  history fills in on its own.
* Where a campaign has not been priced yet, the screens say the total is still partial rather than showing a
  short figure as if it were complete.

= 0.17.0 =
* Coupon campaigns can now give every customer their own code instead of one shared one. Each code works once,
  for one person, and expires with the coupon you built it from — so a code posted in a group chat is no use
  to anybody but its owner.
* The review says what that changes before you send: how many separate messages it is, how many batches, and
  that a code is created only as its message goes out.
* **Fixed:** campaigns sent to your whole consenting audience were not being recorded at all. The campaign went
  out, but it never appeared in your history — and your spend caps could not see it either, so a large send was
  invisible to the ceiling meant to catch it.
* **Fixed:** a fatal error on the Consent screen once anything appeared in the "Agreed, with no account" list.
  Its pager and the opt-out list's had been wired to each other's tables.

= 0.16.0 =
* A new Audience screen under TextTango: everyone who has agreed to marketing, in one list — customers with an
  account, guests who agreed at checkout, and numbers imported from a list.
* See why somebody is not getting your campaigns. Somebody who agreed but left no usable number now shows up
  with a reason, instead of being silently absent.
* Search by number or by name. Type a number the way you read it off an order — `024 123 4567` finds it.
* Stop messaging one person, or allow them again, without waiting for them to opt out. Stopping a number stops
  order updates as well as marketing.
* Preparing a campaign is much faster on a large store. It used to ask the opt-out list about each recipient
  one at a time — thousands of separate queries on a store with thousands of customers.

= 0.15.0 =
* A new Analytics screen under TextTango: sends over time on a proper chart, a delivery-status ring, what it
  cost, which events are busiest, and a table of every event with how many were delivered, how many failed and
  what each one spent.
* Campaigns are counted too. They are sent in bulk and have no individual rows in the message log, so they
  used to be missing from every figure — the totals now cover them, with their credits shown beside what
  per-message sending cost.
* A TextTango widget on your WordPress dashboard with the same figures at a glance.
* Switch either between the last 7, 30 or 90 days.
* Charts are drawn on the server, so the plugin loads no charting library and they work with JavaScript off.

= 0.14.0 =
* Send a text when somebody fills in a form — Contact Form 7, WPForms, Gravity Forms and Fluent Forms. Tell
  yourself an enquiry arrived, text the person back, or capture marketing consent from a checkbox on the form.
* Each form is set up separately, because only you know which of its fields holds a phone number.

= 0.13.0 =
* Campaign history is no longer thrown away. It used to keep only the last fifty and delete the rest — what you
  sent, to how many people and what it cost is now kept and paged through.
* The opt-out list, consent list and audit trail can be paged through too. They showed the newest few with no
  way to reach the rest.
* Pagination now offers page numbers rather than only Newer and Older.
* The log's filter boxes are wide enough to read.

= 0.12.1 =
* The new-campaign form is reorganised into Campaign, Audience, Message and Schedule — name it, choose who it
  goes to, write it, then say when — and it is no longer as wide as the screen.

= 0.12.0 =
* Campaigns is now two tabs — writing a new one, and the history of what has already gone. Sending takes you
  straight to the history so you can watch it go.
* Every TextTango screen now opens with the logo, so Settings, Campaigns and Setup look like one product.

= 0.11.1 =
* Each settings tab now carries an icon.

= 0.11.0 =
* The message log can be searched. Filter by status or event, or type a customer's number — the whole number or
  just the last few digits — and page through the results. It used to show the fifty newest messages with no way
  to reach the fifty-first.
* Tables, buttons and focus outlines are consistent across every screen, and keyboard focus is now visible on
  buttons.

= 0.10.0 =
* Activating the plugin now tells you what it still needs, and the menu opens Setup until setup is done.
* Screens with nothing on them yet explain what would fill them, instead of only saying they are empty.

= 0.9.0 =
* Narrow a campaign to people who bought a particular product.
* Text the password reset link as well as emailing it, to numbers customers have verified.

= 0.8.0 =
* Narrow a campaign to customers who ordered recently, spent enough, or have never ordered at all.
* Uninstalling now removes everything the plugin wrote, including consent recorded against orders. Earlier
  versions left some of it behind.

= 0.7.0 =
* Consent can now be recorded against a phone number with no account behind it, so an imported subscriber list
  no longer loses most of its people. Your opt-out list and any refusal on an account both still outrank it.
* Schedule a campaign for later, and stop it any time before it goes.

= 0.6.0 =
* Three more events: order shipped when a tracking number appears, a welcome for new accounts, and a low-stock
  staff alert. Each offers only the merge tags it can actually resolve.
* Choose who a campaign goes to: your consenting customers, numbers you supply, or both. Supplied numbers are
  for consent you hold somewhere this plugin cannot see — they are counted separately, your opt-out list still
  applies to them, and the audit trail records who vouched for them.
* Send a failed message again from the log.
* An optional email when sending stops working — a refused token, no credit, or TextTango unreachable. Never
  for a single failed message.
* Import your existing opt-in list from WP SMS.
* Fixed a database error logged on every upgrade check.
* Fixed the account balance showing two different figures: the settings screen read a value written only when
  you pressed Test connection, while everywhere else showed the hourly reading.
* The TextTango logo now appears on the settings screen and in the admin menu.

= 0.5.0 =
* Campaigns: one message to everyone who agreed, with a review step before anything is spent.
* The audience is gathered from your own consent records, not from a synced contact group.
* Campaigns count towards your spend caps, and one that will not fit under a cap is refused rather than trimmed.
* Quiet hours schedule a campaign with TextTango instead of holding it on your site.
* Abandoned basket reminders for signed-in customers, with an optional follow-up.
* Coupon campaigns with `{coupon_code}` and friends, refusing a code that cannot be redeemed and warning when
  there is not enough of it for everyone it is going to.
* Fixed a database error logged on every upgrade check.

= 0.4.0 =
* Public PHP API, documented action and filter surface, and WP-CLI commands.
* Spend caps with tiered pausing, hourly balance watching, and a circuit breaker.
* An audit trail of settings, credential, consent and opt-out changes.
* Optional email fallback for a message the phone could not receive.
* Delivery receipts: self-registration, a provable test event, and in-app secret rotation.
* Native Site Health checks and a support-safe debug panel.
* Settings import and export.
* A Setup screen that does each stage's work in place.

= 0.3.0 =
* Marketing consent at checkout and in the account, with a recorded trail.
* Optional double opt-in, confirmed from the handset.
* Opt-out list, honoured for every message including order updates.
* Consent-gated contact sync with a nightly reconciliation sweep.
* WordPress personal-data export and erasure.
* Quiet hours that hold messages rather than dropping them, and log retention.

= 0.2.0 =
* Delivery-receipt webhooks with signature verification and a self-healing poller.
* Message log with per-event delivery status.

= 0.1.0 =
* Connection settings with an encrypted token, sandbox mode and a test send.
* Passwordless sign-in by phone, with optional two-factor.
* WooCommerce order notifications with editable templates.

== Upgrade Notice ==

= 0.20.3 =
If "Role for new accounts" (Settings → Sign in) was set to anything other than Subscriber or Customer, it now
falls back to Subscriber — pick a low-privilege role again from the dropdown if you want something specific.

= 0.20.2 =
Nothing to do. Header metadata only — no behavior changes.

= 0.20.1 =
Nothing to do. Documentation and packaging only — no behavior changes.

= 0.15.0 =
Adds one index to the message log on first load after upgrading, which may take a moment on a large log. It also
makes the existing spend-cap check cheaper on every send.

= 0.14.0 =
Nothing to do. Form triggers are off until you set one up under TextTango → Forms.

= 0.13.0 =
Moves campaign history into its own table on first load after upgrading; existing history is carried over. Your
spend caps count exactly as before.

= 0.12.0 =
Nothing to do. Campaigns splits into two tabs; any campaign already scheduled or in flight is unaffected.

= 0.11.0 =
Adds an index to the message log on first load after upgrading, which may take a moment on a large log. The
log can then be filtered and searched by number.

= 0.10.0 =
Nothing to do. This one is about the first few minutes after installing: the plugin now says what it needs when
you activate it, and points its menu at the screen that helps.

= 0.9.0 =
Campaigns can be narrowed to buyers of a particular product, and the password reset link can be texted as well
as emailed. Both are off until you switch them on.

= 0.8.0 =
Campaigns can be narrowed to part of your consenting audience. Fixes an uninstall that left data behind,
including marketing consent recorded against orders — if you have uninstalled a previous version, some of that
may still be in your database.

= 0.7.0 =
Adds a database table for consent recorded against a number rather than an account, created automatically on
upgrade. Campaigns can now be scheduled for later and cancelled before they send.

= 0.6.0 =
A campaign's audience is now an explicit choice — your consenting customers, numbers you supply, or both. A
campaign meant for a list you hold elsewhere no longer also goes to your customers. Three new notification
events, all off until you switch them on.

= 0.5.0 =
Adds marketing campaigns, basket reminders and coupon blasts. All are off until you use them, and every one of
them requires marketing consent. Campaigns now count towards your spend caps, so a cap you set for order
updates also bounds a campaign.

= 0.4.0 =
`texttango_should_send` now runs for every message rather than WooCommerce notifications alone, and its third
argument is the send arguments rather than the order. If you filter it, branch on the event — it now also gates
sign-in codes.
