=== Bouzin Pay ===
Contributors: bouzin
Tags: stripe, payment, subscription, invoice, checkout
Requires at least: 6.6
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.1.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Manage Stripe products, payments, customers, invoices, subscriptions, disputes, payouts, and checkout tools from WordPress.

== Description ==

Bouzin Pay provides a WordPress administration interface for Stripe. It includes a sales dashboard, product and price management, payment links, customers, invoices, subscriptions, refunds, promotion codes, disputes, payouts, signed webhooks, and customer access tools.

The Bouzin Pay Customer Account block creates a customer area on the page selected in Bouzin Pay settings. A single block provides a personalized welcome screen, accessible tabs, an account summary, confirmed purchases, protected digital downloads, subscription status and renewal dates, targeted Stripe cancellation flows with confirmation, Stripe-hosted invoices or invoice PDFs, profile information, confirmed WordPress account deletion, optional merchant support contacts, secure billing management, and logout.

The **Digital Files** submenu attaches one or more downloadable files to a Stripe product. Originals never enter the public Media Library: Bouzin Pay encrypts them in bounded chunks before writing opaque containers to a guarded private vault. Every download requires a signed-in WordPress account, a user-bound nonce, the correct TEST/LIVE mode, and either a confirmed non-refunded purchase or an active subscription. A full refund revokes only the affected purchase; another valid purchase remains usable.

The interface is available in English and French. English is the source language, and a complete French translation is bundled with the plugin.

Add the **Bouzin Pay – Shop** block to display active, locally synchronized products automatically, with images, descriptions, prices and checkout buttons. The new **Products CSV** submenu provides a UTF-8 CSV export, validated import preview and explicitly confirmed, resumable import. CSV prices use integer minor currency units (e.g. 2990 for CHF 29.90). Inventory quantities are managed separately, not imported from CSV.

The **Stock** submenu offers an optional **Manage stock** checkbox per product, off by default. One-time, fixed-price products can reserve one unit during Checkout and consume it once payment is confirmed. A sold-out product cannot start another Checkout. Pending asynchronous payments keep their reservation. Confirmed expiration or failure releases it; refunds do not automatically replenish stock.

Stock applies exclusively to this site's Bouzin Pay checkout. Direct Stripe Payment Links, other websites, manual invoices and recurring prices must not sell the same tracked products. Existing external links, recurring prices, unpaid invoices and pending sessions are checked before activation. Large histories that cannot be completely checked within the safety limit require manual review (500 active links, 100 prices per product, 1000 sessions/invoices). Stock requires MySQL/MariaDB InnoDB and connection-owned GET_LOCK support. Repair the Stripe webhook connection after upgrading, and ensure WP-Cron runs regularly. This also verifies the Stripe account; restricted keys need account-read access and read access to Checkout Sessions, line items, Payment Intents, prices, links and invoices, plus the existing checkout/webhook permissions.

Exclude shop, purchase, protected-content and customer-account pages from full-page/CDN caching and clear their old cache after upgrading. The plugin marks these recognized pages and personalized REST responses as non-cacheable; every purchase still checks availability on the server. A cache or CDN that serves a response before WordPress runs must be configured separately. Also exclude these pages from third-party search indexes, static exports and page-builder caches.

The plugin keeps test and live credentials separate. API keys can be stored encrypted at rest or supplied through constants in `wp-config.php`. A restricted Stripe key with only the permissions required by the enabled features is recommended.

The following constants are supported and take priority over database settings:

* `BOUZIN_PAY_STRIPE_TEST_KEY`
* `BOUZIN_PAY_STRIPE_LIVE_KEY`
* `BOUZIN_PAY_STRIPE_TEST_WEBHOOK_SECRET`
* `BOUZIN_PAY_STRIPE_LIVE_WEBHOOK_SECRET`

= Security =

* Separate test and live keys.
* Server-side validation of products, prices, status, associations, and mode.
* WordPress access is granted only after confirmation of a paid and trusted Checkout Session.
* Signed webhooks with atomic deduplication and controlled retries.
* Idempotency keys for Stripe creation and action requests.
* Separate WordPress capabilities for sensitive operations.
* Redacted technical logs that exclude API keys, webhook secrets, signatures, client secrets, and payment methods.
* Signed checkout configuration and ownership checks before protected digital content is displayed.
* Authenticated, rate-limited digital delivery from encrypted containers whose storage path is never exposed to customers.

= External service =

This plugin connects to Stripe to create and manage payment-related objects and to process checkout and customer portal requests. Data sent to Stripe can include customer contact information, product and price identifiers, transaction details, subscription details, and request metadata required to perform the selected operation.

Stripe is an external service. Its use is subject to the [Stripe Services Agreement](https://stripe.com/legal/ssa) and [Stripe Privacy Policy](https://stripe.com/privacy).

If an administrator adds a product image hosted by another provider, visitors' browsers contact that image host when the shop is displayed. The administrator is responsible for disclosing that provider where required.

= Development =

Readable, uncompressed JavaScript and CSS source for every generated frontend asset is included in the `source` directory. The source-to-bundle mapping and build instructions are documented in `source/README.md`; the exact npm dependency and build commands are declared in `package.json` and locked in `package-lock.json`.

The catalogue assets in `catalogue/assets` are readable runtime source and require no bundling. Its PHP source, CSV examples and gettext generation script are included.

= Credits and licenses =

Bouzin Pay is derived from WPMarmite Pay 1.5.5. The original work is Copyright (C) 2024 WPMarmite, and the Bouzin Pay modifications are Copyright (C) 2026 Bouzin. WPMarmite is not affiliated with and does not provide support for Bouzin Pay.

Third-party copyright and license notices for Stripe PHP, Composer, Terser, and the included CA certificate bundle are provided in `THIRD-PARTY-NOTICES.txt`.

== Installation ==

The dashboard offers 30 days, 3, 6 or 12 rolling calendar months, and all history. Exact dates use the WordPress timezone. It counts succeeded PaymentIntents by their creation date and keeps currencies separate; canceled or incomplete payments do not count as revenue. Gross amounts are before fees and refunds. The refund card counts succeeded refunds created within the selected period. Active subscriptions and available balance show the current position, independently of the selected period. History loads in bounded batches, with provisional totals marked until completion. Completed totals are cached for two minutes; Synchronize clears the dashboard cache. The selected period is remembered in this browser for each mode.

1. Upload and activate the plugin.
2. Open Bouzin Pay > Settings.
3. Add separate Stripe keys for test and live mode.
4. Start in test mode. The plugin creates its signed webhook automatically.
5. Run a synchronization from the dashboard.
6. Set WordPress account creation to "After Sale" so a customer account is created only after Stripe confirms payment.
7. Select the customer account page in Bouzin Pay settings and add the Bouzin Pay Customer Account block if the page does not already contain it.
8. For downloadable products, open Bouzin Pay > Digital Files, select a synchronized Stripe product, and upload one or more files to its encrypted vault.

The Gutenberg buy button and each shareable Purchase Link ask for the customer's email address before opening Stripe Checkout. They use the same protected checkout flow, and confirmed purchases, invoices, and subscriptions are associated with the resulting WordPress customer account.

For a digital product, edit its Purchase Link and select a protected WordPress page. That page is available only to administrators and authenticated customers whose confirmed Stripe purchase or active subscription includes the linked product. Shortcodes remain supported for page builders and classic content, but the visual generator is intentionally omitted from the primary administration workflow.

Page-content protection covers WordPress page rendering, core REST page responses, excerpts, feeds, XML-RPC post/page representations and public embeds. Protected pages are excluded from core sitemaps. Editors with permission to edit a page can still manage its content. This does not encrypt the page or protect files linked from the public Media Library: use Digital Files for paid downloads. Third-party APIs, search engines and static exports require a separate access-aware integration.

Unpublish protected pages before deactivating or uninstalling Bouzin Pay: page authorization cannot run while the plugin is inactive. Associate each page that needs protection explicitly; protecting a parent does not automatically protect its children. Encrypted vault files remain encrypted without the plugin.

Set your own support email and/or contact page in Bouzin Pay > Settings if customers should see contact links. No developer support link is added to the public account by default. PHP cURL, JSON and mbstring are required for Stripe; the digital vault also requires Sodium or OpenSSL.

Test webhooks, taxes, refunds, subscriptions, and emails on a staging site before enabling live mode.

== Frequently Asked Questions ==

= How do I sell protected PDF access as a subscription? =

Open Bouzin Pay > Products and create or select a product. Use **Add a price**, select a **Subscription** sale type, then choose the period and enter 1, 2, or 3. Attach the PDF from **Protected Files** to that same product, then create a Purchase Link or copy the price's buy button. A value of 3 with Month bills every three months until cancellation; it is not a one-time three-month pass. The PDF is displayed and served only while the synchronized subscription is active or trialing and its known paid period has not ended. Select **One-time purchase — lifetime access** instead when access must not expire.

= Can customers cancel a subscription from their WordPress account? =

Yes. The Subscriptions tab displays each synchronized subscription and its next renewal or access-end date. **Manage or cancel my subscription** first asks the customer to continue, then opens Stripe directly on the cancellation flow for that exact subscription. Stripe displays the final consequences and collects the final confirmation. Subscription cancellation must be enabled in the matching TEST or LIVE Stripe customer-portal configuration.

= Can customers delete their WordPress account? =

Yes. Eligible customer accounts have a protected deletion form in the Profile tab. The customer must enter the current password, type DELETE, and confirm the access warning. Staff accounts and multisite accounts cannot use this action. A renewing LIVE subscription must be canceled first. WordPress profile and protected-content access are removed immediately, while Stripe billing records, invoices, transactions, and legally required records remain in Stripe.

= Does Bouzin Pay store card or bank details? =

No. Full card numbers, full bank details, and legal identity data remain in Stripe's secured interfaces.

= What data is stored locally? =

The plugin stores a limited administrative cache of selected Stripe objects, redacted technical logs, and event history needed to prevent duplicate webhook processing. Webhook history is retained for 180 days and technical logs for 90 days by default; both durations are configurable in Bouzin Pay settings. Failed webhook records are retained for up to twice the configured webhook duration to support diagnosis.

= Can all plugin data be removed? =

Enable the complete deletion option before uninstalling the plugin. Deactivation alone never deletes data. Resolve all pending payments first: unresolved stock attempts and their inventory ledger/account binding are deliberately preserved even during complete deletion. Reconcile them before a later full cleanup. No operation deletes products, sales or invoices from Stripe.

= Where are paid digital files stored? =

They are stored as encrypted `.bpv` containers in Bouzin Pay's guarded vault under the WordPress uploads directory, never as public Media Library attachments. Back up both the WordPress database and the complete uploads directory together. The per-file keys are encrypted from the site's WordPress authentication salts; changing those salts or restoring only one half of the backup makes existing protected files unreadable until they are uploaded again.

== Privacy ==

Bouzin Pay can store Stripe identifiers, customer contact details, payment states, invoices, subscriptions, related object summaries, redacted logs, and webhook processing history in the WordPress database. It does not store full card numbers or full bank details.

Protected digital content is linked to a Stripe product and remains subject to a server-side ownership check on each request. The plugin stores encrypted file metadata, purchase entitlements, refund state and an aggregate download count, but no IP address in digital download records. Invoice files and hosted invoice pages are delivered by Stripe and are not copied into the WordPress media library.

Optional inventory stores product/session identifiers, quantities, revisions and durable deduplication records. A pending checkout creation request, which can include an email/customer identifier and return URLs, is encrypted locally until its session is recovered. Once the session is known, this payload is cleared. Unknown payments retain their reservation and recovery data for administrator review; no browser cancellation or local time limit silently releases them. Terminal stock attempts and paid/released reservation records are removed after 180 days in bounded daily batches; pending and uncertain payments are never removed automatically.

The plugin adds suggested privacy-policy text in the WordPress privacy guide. Site administrators remain responsible for adapting that text to their actual configuration and legal obligations.

The WordPress personal-data tools export local customer snapshots and purchase access, including customer records without a remaining WordPress account. Erasure removes contact details, free text, metadata and invoice download URLs from the local cache and removes the WordPress purchase/subscription associations. The same cleanup runs on account deletion. Unresolved encrypted checkout recovery must be resolved first; the eraser reports retained data instead of deleting payment recovery state.

Once a successful refund has revoked purchase access, a later refund delivery or bank refund failure does not automatically restore that access; the merchant must resolve the failed refund with the buyer.

Minimal financial identifiers and amounts, revoked purchase tombstones and keyed erasure fingerprints remain locally until explicit full removal of plugin data. These pseudonymous records prevent duplicate settlement, delayed webhook access restoration and reimport of erased contact details. The privacy eraser explicitly reports this retention. The suppression key is stored separately from WordPress authentication salts. Regular synchronization keeps other customer summaries until erasure or complete plugin-data removal; the merchant must define any further retention required for their business. No local privacy operation cancels billing or deletes Stripe's records. A fresh authorized Stripe lookup in the administration can still retrieve records retained by Stripe.

== Changelog ==

= 0.1.1 =

* Use an independent Checkout signing secret, preserving verification of existing paid sessions and vault encryption.
* Enqueue inline styles through WordPress and prefix localized JavaScript globals.
* Generate a non-executable vault index document.
* Preserve the customer subscription display fix and separate client/webhook API versions.


= 0.1.0 =

* Load the TEST/LIVE toolbar script and styles only when the administrator toolbar is visible, and defer script execution.
* Fixed authenticated checkout from purchase links and Gutenberg buttons by sending the WordPress REST nonce; preserved guest checkout and protected signed-in checkout pages from caching.

* Prevented stale and concurrent Stripe events from restoring canceled subscription or fully refunded purchase access.
* Added atomic subscription revisions, fail-closed refund checks and retryable webhook database/API failures.
* Aligned automatically created webhook payloads with the client API version.

* Optimized protected-page sitemap filtering without an unbounded exclusion-ID list; preserved sitemap pagination, cache invalidation and other plugins' filters.

* Initial public release.
* Hardened protected page access across WordPress representations and removed the global 200-link limit.
* Added explicit no-cache handling for account pages and protected content, plus setup guidance for cache/CDN exclusions.
* Added local customer privacy export, erasure, reimport suppression and revoked-purchase tombstones.
* Added optional merchant support settings without unsolicited public developer links.
* Replaced the global recent-invoice scan with customer-specific pagination.
* Included the complete GPLv2 license, retained required attribution, and integrated subscription help into this readme.
* Added visible vault backup and salt-rotation guidance.
* Added a required active-product selector to price creation, with automatic preselection from the Products screen and a resilient manual fallback.
* Made new price creation explicitly choose between a lifetime one-time purchase and a recurring subscription.
* Added one-click recurring-price creation and billing intervals such as every 2 or 3 months.
* Added a dedicated customer-account Subscriptions tab with status and renewal or access-end dates.
* Added a confirmed button that opens Stripe's cancellation flow for the exact selected subscription.
* Added password-protected WordPress customer-account deletion while preventing deletion during a renewing LIVE subscription.
* Preserved Stripe invoices, transactions, and legally required billing records when a WordPress account is deleted.
* Completed the customer-account interface and subscription workflow in English and French.
* Added Stripe dashboard and management tools.
* Added selectable dashboard periods: 30 days, 3, 6 or 12 months, and all time, with aligned revenue/payment/refund totals.
* Added adaptive daily, weekly, monthly or yearly charts, currency switching, exact dates and an accessible data table.
* Explained empty periods and excluded canceled/pending payments; remembered the selected period locally.
* Added resumable history pagination beyond 1,000 objects, with clearly marked provisional totals during loading.
* Added products, sales, customers, invoices, promotions, subscriptions, disputes, and payouts.
* Added separated test/live modes, signed webhooks, encrypted key storage, and redacted logs.
* Added blocking validation before enabling live mode.
* Added suggested privacy-policy content.
* Added a Gutenberg customer account block with purchases, subscriptions, Stripe invoices, billing portal access, and protected digital-content links.
* Unified Gutenberg buttons, purchase links, and shortcodes behind the same email-verified checkout and post-payment WordPress account flow.
* Simplified the interface by keeping shortcodes as an advanced compatibility feature without a separate generator screen.
* Added configurable retention and scheduled purging for webhook history and redacted technical logs.
* Added readable JavaScript and CSS sources with a reproducible asset build.
* Bundled Stripe PHP 21.3.1 with upstream API request URL hardening and PHP 8.1 through 8.4 compatibility.
* Added validated CSV product import/export and a dynamic shop block.
* Added optional per-product stock with atomic reservations and idempotent Stripe settlement.
* Integrated stock into the common checkout, signed webhooks and five-minute reconciliation.
* Guarded incompatible payment links, recurring prices and manual invoice paths.
* Added bounded 180-day cleanup for terminal stock technical records while preserving unresolved payments.
* Added encrypted digital-file storage, authenticated customer downloads, subscription access and full-refund revocation.
* Restored the capability-protected page selector used by settings and purchase links without restoring the removed shortcode-generator screen.
