# Stripe payment integration — CGYellowPages v4.1

Adds Stripe as a payment gateway for paid listing periods, alongside the existing
PayPal gateway. It follows the conventions of the bundled `pjPaypal` plugin:
same plugin/folder layout, model style, migration style, admin transactions grid,
and the same listing-activation logic — so the payment flow behaves identically to
PayPal from the admin's point of view.

Because Stripe Checkout is a redirect (hosted) flow rather than an in-page button,
the customer is sent to Stripe's hosted page and returned to a dedicated confirm
handler. That is the only behavioural difference from PayPal.

## What was added

New plugin — `app/plugins/pjStripe/`
- `config/config.inc.php` — plugin descriptor (PLUGIN_ID 303)
- `config/database.sql` — creates `plugin_stripe` and registers its field labels
- `config/updates/update_2026_07_03_10_00_00.sql` — adds the Stripe options and labels
- `controllers/pjStripe.controller.php`
  - `pjActionCreateOrder` — creates a Checkout Session, returns its id/url
  - `pjActionConfirm`     — verifies a paid session (status, amount, currency), stores it
  - `pjActionForm`        — renders the redirect-to-Stripe view
  - `pjActionIndex` / `pjActionGetStripe` / `pjActionGetDetails` — admin transactions grid
- `models/pjStripe.model.php` — `plugin_stripe` table model
- `views/pjStripe/*` — redirect view, admin grid, transaction details
- `web/js/pjStripe.js` — admin transactions datagrid

Wiring into the existing app (additions only, no lines removed)
- `app/config/options.inc.php` — `pjStripe` added to `$CONFIG['plugins']`
- `app/controllers/pjListings.controller.php` — new `pjActionConfirmStripe()`
  (the Stripe return handler; reuses the exact listing-activation logic of
  `pjActionConfirmPayment`)
- `app/controllers/pjAdminListings.controller.php` — new `pjActionGetStripeForm()`
  (builds the Stripe payment params, mirror of `pjActionGetPaymentForm()`)
- `app/views/pjAdminListings/pjActionGetStripeForm.php` — renders the Stripe form
- `app/views/pjAdminListings/pjActionPayment.php` — "Buy now (Stripe)" button + container
- `app/web/js/pjAdminListings.js` — click handler that loads the Stripe form

## Install

1. Deploy this project (or copy it over your existing v4.1 tree).
2. Run the plugin SQL against your database, in this order:
   - `app/plugins/pjStripe/config/database.sql`
   - `app/plugins/pjStripe/config/updates/update_2026_07_03_10_00_00.sql`
3. Make sure `pjStripe` is in the plugins array of **your** config files
   (`app/config/config.inc.php` and `app/config/config.sample.php`), e.g.
   `$CONFIG['plugins'] = array( ... , 'pjPaypal', 'pjStripe');`
   (the internal defaults list in `options.inc.php` is already updated here).
4. In the admin, open **Options → Payments** and set:
   - **Stripe secret key** (`sk_test_...` / `sk_live_...`) — required
   - **Stripe publishable key** (`pk_test_...` / `pk_live_...`)
   - **Cancel page** (optional) — where a cancelled payment returns to
   Test vs live mode is determined by the key prefix; there is no separate sandbox toggle.

## Payment flow

1. Admin → a listing's **Extend** page → **Buy now (Stripe)** for a paid period.
2. The browser loads the Stripe form, which calls `pjStripe::pjActionCreateOrder`.
   That creates a Checkout Session (amount taken from the period price, in the
   store currency) and the browser is redirected to Stripe's hosted page.
3. On success Stripe returns to `pjListings::pjActionConfirmStripe`, which
   verifies the session server-side (paid status, amount, currency), records the
   transaction in `plugin_stripe`, inserts the `payment` row and extends the
   listing — exactly as the PayPal flow does.

## Notes

- Amounts are converted to the smallest currency unit for Stripe, with
  zero-decimal currencies (JPY, KRW, …) handled correctly.
- All Stripe API calls use the framework's own `pjHttp` component, the same
  transport `pjPaypal` uses for the PayPal REST API — no third-party SDK is bundled.
- The SQL was written to mirror `pjPaypal` but could not be executed here (no
  database in the build environment); run it against a staging database first.
- The confirm step depends on the buyer returning from Stripe. For hardened
  reconciliation you can later add a Stripe webhook that calls the same
  verification path; it is not required for the basic flow.
