diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d51b0c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: ci + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Initialize public plugin submodules + run: git submodule update --init woo prestashop magento + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: pnpm + cache-dependency-path: woo/pnpm-lock.yaml + + - name: Run automated plugin test suite + run: ./scripts/run-plugin-tests.sh diff --git a/DEV-ENV.md b/DEV-ENV.md new file mode 100644 index 0000000..aa3247b --- /dev/null +++ b/DEV-ENV.md @@ -0,0 +1,128 @@ +# Plugin development environments + +Documents what contributors need to run and test lomi. e-commerce plugins locally. Parent epic: [lomi. #45](https://github.com/lomiafrica/lomi./issues/45) · issue **#44**. + +No production store is required — use **lomi. sandbox (test mode)** and a local or staging shop. + +## Access checklist + +| Item | Who provides | Notes | +| --- | --- | --- | +| GitHub org access | lomi. team | Required for private submodules: `shopify`, `bubble`, `odoo` | +| lomi. sandbox org | You or team | [dashboard.lomi.africa](https://dashboard.lomi.africa) — **test** secret key + webhook signing secret | +| HTTPS tunnel | You | Cloudflare Tunnel (or ngrok) so lomi. can POST webhooks to your machine | +| Submodule clone | You | `git clone --recursive` or `git submodule update --init --recursive` | + +Never commit API keys, webhook secrets, or tunnel tokens to git. + +--- + +## WooCommerce (Docker + Cloudflare Tunnel) + +Recommended setup used for manual E2E and plugin UX work. + +### Stack + +- **Docker Compose**: WordPress + MySQL 8 ([`dev/woocommerce/docker-compose.yml`](./dev/woocommerce/docker-compose.yml)) +- **Plugin**: bind-mount `woo/` submodule → `wp-content/plugins/woo-lomi` +- **Store URL (local)**: http://localhost:8080 +- **Public HTTPS**: Cloudflare Tunnel → same WordPress instance +- **lomi.**: sandbox API (`https://sandbox.api.lomi.africa`), test mode ON in Woo settings + +### Start + +```bash +cd dev/woocommerce +docker compose up -d +``` + +1. Complete WordPress install in the browser. +2. Install and activate **WooCommerce** (9.6+). +3. Activate **lomi. for WooCommerce**. +4. In WooCommerce → Settings → Payments → **lomi.**: enable gateway, **test mode**, paste test secret key + test webhook secret. +5. Copy the **webhook URL** from Woo settings into dashboard → **Developers → Webhooks** (events: `PAYMENT_SUCCEEDED`, `REFUND_COMPLETED` recommended). + +### Cloudflare Tunnel + +Expose port **8080** so webhooks reach WordPress: + +```bash +# Quick tunnel (ephemeral URL) +cloudflared tunnel --url http://localhost:8080 +``` + +Or use a named tunnel + `config.yml` if you have a fixed hostname. + +**Important:** In WordPress **Settings → General**, set **Site Address (URL)** to your public tunnel URL when testing checkout return URLs and webhooks. If the site URL stays `http://localhost:8080`, redirects after payment may break for external services. + +### Plugin from zip instead of bind-mount + +```bash +cd woo +pnpm install +pnpm run release +# Upload dist/woo-lomi.zip via Plugins → Add New → Upload +``` + +### Manual E2E + +Follow [E2E.md](./E2E.md) → WooCommerce section after the environment is up. + +### Automated checks (no Docker required) + +```bash +./scripts/verify-lomi-plugins.sh +``` + +CI runs the same script on pull requests. + +--- + +## PrestaShop + +- Module folder: `prestashop/lomi/` — zip **`lomi/`** only (root of archive = `lomi/`). +- Local: Docker or native PrestaShop 1.7+ / 8.x, currency EUR/USD/XOF, HTTPS for webhooks. +- See [prestashop/lomi/README.md](prestashop/lomi/README.md) and [E2E.md](./E2E.md). + +--- + +## Magento 2 + +- Package: `lomi/magento2-payments` — **not on Packagist**; use Composer VCS or copy to `app/code/Lomi/Payments/`. +- Docker dev stack: [`magento/dev`](magento/dev) (see submodule README). +- Webhooks need a public HTTPS URL (Cloudflare Tunnel or ngrok). +- See [E2E.md](./E2E.md). + +--- + +## Shopify + +- Custom install app — no zip. Enable in dashboard → **Settings → Payment channels → Integrations**. +- Requires org access to private `shopify` submodule. +- See [docs: Shopify](https://docs.lomi.africa/build/ecommerce-extensions/shopify). + +--- + +## Bubble + +- Plugin project in `bubble` submodule (private repo). +- CI: JSON validation + `scripts/smoke-test.mjs` on release workflow. +- See [E2E.md](./E2E.md) and submodule README. + +--- + +## What to request from the team + +If you are blocked, ask for: + +1. **Sandbox** lomi. org with test API key and webhook secret confirmed. +2. **GitHub** access to private plugin repos you need (Shopify, Bubble, Odoo). +3. **Shared staging** shop (optional) — not required if Docker + tunnel works for Woo. + +--- + +## Related docs + +- [E2E.md](./E2E.md) — manual smoke matrix per platform +- [docs.lomi.africa — E‑commerce](https://docs.lomi.africa/build/ecommerce-extensions) +- [verify-lomi-plugins.sh](./scripts/verify-lomi-plugins.sh) — static contract gate diff --git a/E2E.md b/E2E.md index 84bde5c..038c6e7 100644 --- a/E2E.md +++ b/E2E.md @@ -13,19 +13,26 @@ Manual checklist per platform. Run in **test mode** first, then repeat critical | Test/live toggle | API base + webhook secret switch together | | Abandon | Back from hosted checkout restores cart / pending order | -Automated static gate: `./scripts/verify-lomi-plugins.sh` +Automated suite: `./scripts/run-plugin-tests.sh` (static parity + webhook contract + Woo zip). Static-only: `./scripts/run-plugin-tests.sh --fast` --- ## WooCommerce -**Environment:** wp-env, staging store, or local WordPress + Woo. +**Environment:** Docker Compose ([`dev/woocommerce`](./dev/woocommerce)) + **Cloudflare Tunnel** for HTTPS webhooks, or shared staging. See [DEV-ENV.md](./DEV-ENV.md). -1. Configure test API key + test webhook secret; enable test mode. -2. Place order → redirect to lomi. checkout → pay (sandbox). -3. Confirm order `processing`/`completed` and webhook note. -4. Toggle live keys (staging only if available); confirm `api.lomi.africa` used. -5. Start checkout, use browser Back → cart restored (abandon JS). +```bash +cd dev/woocommerce && docker compose up -d +# cloudflared tunnel --url http://localhost:8080 +``` + +1. Install WooCommerce; activate `woo-lomi` (bind-mounted from `woo/` submodule). +2. Configure test API key + test webhook secret; enable test mode. +3. Set WordPress site URL to tunnel hostname when testing checkout return + webhooks. +4. Place order → redirect to lomi. checkout → pay (sandbox). +5. Confirm order `processing`/`completed` and webhook note. +6. Toggle live keys (staging only if available); confirm `api.lomi.africa` used. +7. Start checkout, use browser Back → cart restored (abandon JS). --- diff --git a/README.md b/README.md index ecb44d2..d050c57 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,22 @@ If a submodule fails with "repository not found", that platform repo is private ## End-to-end tests and scripts +- **[DEV-ENV.md](./DEV-ENV.md)**: Local dev setup (Docker, Cloudflare Tunnel, sandbox keys) — **start here for new contributors**. - **[E2E.md](./E2E.md)**: Manual smoke matrix per platform (checkout, webhooks, abandon flows, release tags). -- **[scripts/verify-lomi-plugins.sh](./scripts/verify-lomi-plugins.sh)**: Static compliance gate (API contract, `integration_source`, XOF amounts, legacy brand checks, webhook patterns). +- **[scripts/run-plugin-tests.sh](./scripts/run-plugin-tests.sh)**: **Automated CI suite** — static parity, webhook contract, Bubble JSON, Woo build + release zip. +- **[scripts/verify-lomi-plugins.sh](./scripts/verify-lomi-plugins.sh)**: Static compliance gate (also run as step 1 of `run-plugin-tests.sh`). - **[scripts/scan_broken_images.py](./scripts/scan_broken_images.py)**: Scans Magento, PrestaShop, and Woo trees for broken image path references. -Run the static gate from the repository root: +Run the full automated suite from the repository root (requires Node 22+, pnpm 9+, `unzip`): ```bash -./scripts/verify-lomi-plugins.sh +./scripts/run-plugin-tests.sh +``` + +Static checks only (no Woo `pnpm build`): + +```bash +./scripts/run-plugin-tests.sh --fast ``` ## Contributing @@ -63,7 +71,7 @@ Pull requests and issue reports are welcome. - **Platform plugin changes**: work inside the relevant submodule, push to that submodule's repo, then update the submodule pointer here if needed. - **Reference app or shared script changes**: edit files directly in this repository (`direct-charge-integration-reference`, `payment-integration-reference`, `payment-integration-sdk-reference`, `scripts/`, `E2E.md`). -- Run `./scripts/verify-lomi-plugins.sh` before opening a PR. +- Run `./scripts/run-plugin-tests.sh` before opening a PR (`--fast` if Woo assets are unchanged). ## License diff --git a/dev/woocommerce/README.md b/dev/woocommerce/README.md new file mode 100644 index 0000000..235a0cb --- /dev/null +++ b/dev/woocommerce/README.md @@ -0,0 +1,23 @@ +# WooCommerce local dev (Docker) + +Quick start for plugin development. Full checklist: [DEV-ENV.md](../../DEV-ENV.md). + +```bash +cd dev/woocommerce +docker compose up -d +``` + +1. Open http://localhost:8080 and finish WordPress setup. +2. Install **WooCommerce** (Plugins → Add New, or WP-CLI below). +3. Activate **lomi. for WooCommerce** (`woo-lomi`). +4. Expose HTTPS with **Cloudflare Tunnel** (see DEV-ENV.md). +5. Configure sandbox keys in Woo + [dashboard.lomi.africa](https://dashboard.lomi.africa). + +### WP-CLI (optional) + +```bash +docker compose exec wordpress bash -c \ + 'curl -sO https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp' + +docker compose exec wordpress wp plugin install woocommerce --activate --allow-root +``` diff --git a/dev/woocommerce/docker-compose.yml b/dev/woocommerce/docker-compose.yml new file mode 100644 index 0000000..f56a577 --- /dev/null +++ b/dev/woocommerce/docker-compose.yml @@ -0,0 +1,34 @@ +# Local WooCommerce + lomi. plugin (bind-mount from submodule). +# From this directory: docker compose up -d +# Store: http://localhost:8080 + +services: + db: + image: mysql:8.0 + restart: unless-stopped + environment: + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + MYSQL_ROOT_PASSWORD: root + volumes: + - db_data:/var/lib/mysql + + wordpress: + image: wordpress:latest + depends_on: + - db + ports: + - "8080:80" + restart: unless-stopped + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + WORDPRESS_DB_NAME: wordpress + volumes: + # Repo layout: dev/woocommerce → ../../woo + - ../../woo:/var/www/html/wp-content/plugins/woo-lomi + +volumes: + db_data: diff --git a/scripts/run-plugin-tests.sh b/scripts/run-plugin-tests.sh new file mode 100755 index 0000000..3ce1913 --- /dev/null +++ b/scripts/run-plugin-tests.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Automated plugin test suite (issue #39). Runs static parity checks plus smoke tests. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PLUGINS_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +FAST=0 +SKIP_WOO=0 + +usage() { + cat <<'EOF' +Usage: + ./scripts/run-plugin-tests.sh [--fast] [--skip-woo] + +Runs: + 1) verify-lomi-plugins.sh — static API contract, branding, legacy brand scan + 2) test_webhook_signature.mjs — HMAC-SHA256 webhook contract + 3) test_bubble_json.mjs — Bubble plugin JSON parse smoke (skipped if submodule empty) + 4) Woo build + release zip validation (unless --fast or --skip-woo) + +Options: + --fast Static checks only (no Woo pnpm build / zip) + --skip-woo Skip Woo build and zip validation +EOF +} + +log() { + printf "\n[%s] %s\n" "$(date +'%H:%M:%S')" "$1" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --fast) + FAST=1 + shift + ;; + --skip-woo) + SKIP_WOO=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if ! command -v node >/dev/null 2>&1; then + echo "FAIL: node is required" + exit 1 +fi + +log "1/4 Static plugin parity (verify-lomi-plugins.sh)" +bash "${SCRIPT_DIR}/verify-lomi-plugins.sh" + +log "2/4 Webhook signature contract" +node "${SCRIPT_DIR}/test_webhook_signature.mjs" + +log "3/4 Bubble JSON smoke" +node "${SCRIPT_DIR}/test_bubble_json.mjs" + +if [[ "${SKIP_WOO}" == "1" || "${FAST}" == "1" ]]; then + log "4/4 Woo release zip — SKIPPED" +else + log "4/4 Woo build + release zip" + WOO_DIR="${PLUGINS_DIR}/woo" + if [[ ! -f "${WOO_DIR}/woo-lomi.php" ]]; then + echo "FAIL: woo submodule not initialized (${WOO_DIR})" + exit 1 + fi + if [[ ! -f "${WOO_DIR}/pnpm-lock.yaml" ]]; then + echo "FAIL: woo/pnpm-lock.yaml missing" + exit 1 + fi + if ! command -v pnpm >/dev/null 2>&1; then + echo "FAIL: pnpm is required for Woo build tests" + exit 1 + fi + ( + cd "${WOO_DIR}" + pnpm install --frozen-lockfile + pnpm run build + if grep -q '"i18n"' package.json 2>/dev/null; then + pnpm run i18n + fi + ) + RUN_RELEASE=1 WOO_DIR="${WOO_DIR}" bash "${SCRIPT_DIR}/test_woo_release_zip.sh" +fi + +log "All automated plugin tests passed." diff --git a/scripts/test_bubble_json.mjs b/scripts/test_bubble_json.mjs new file mode 100644 index 0000000..86fa8c0 --- /dev/null +++ b/scripts/test_bubble_json.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env node +/** + * Smoke test: every *.json under bubble/ must parse (Bubble plugin editor sync). + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const pluginsDir = path.resolve(__dirname, ".."); +const bubbleDir = path.join(pluginsDir, "bubble"); + +function walkJsonFiles(dir, out = []) { + if (!fs.existsSync(dir)) { + return out; + } + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === "node_modules" || entry.name === ".git") continue; + walkJsonFiles(full, out); + } else if (entry.isFile() && entry.name.endsWith(".json")) { + out.push(full); + } + } + return out; +} + +const jsonFiles = walkJsonFiles(bubbleDir); +if (jsonFiles.length === 0) { + console.log("SKIP: bubble submodule not checked out or has no JSON files"); + process.exit(0); +} + +let failed = 0; +for (const file of jsonFiles) { + try { + JSON.parse(fs.readFileSync(file, "utf8")); + } catch (err) { + console.error(`FAIL: ${path.relative(pluginsDir, file)} — ${err.message}`); + failed += 1; + } +} + +if (failed > 0) { + process.exit(1); +} + +console.log(`PASS: ${jsonFiles.length} Bubble JSON file(s) parse cleanly`); diff --git a/scripts/test_webhook_signature.mjs b/scripts/test_webhook_signature.mjs new file mode 100644 index 0000000..39f373d --- /dev/null +++ b/scripts/test_webhook_signature.mjs @@ -0,0 +1,35 @@ +#!/usr/bin/env node +/** + * Contract test: lomi. webhook signatures use HMAC-SHA256 over the raw body, hex-encoded. + * Reference apps and platform plugins must implement the same algorithm. + */ +import crypto from "node:crypto"; +import assert from "node:assert/strict"; + +function signWebhook(rawBody, secret) { + return crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); +} + +function verifyWebhook(rawBody, signature, secret) { + const expected = signWebhook(rawBody, secret); + const sigBuf = Buffer.from(signature, "utf8"); + const expBuf = Buffer.from(expected, "utf8"); + return sigBuf.length === expBuf.length && crypto.timingSafeEqual(sigBuf, expBuf); +} + +const secret = "whsec_test_contract_secret"; +const payload = JSON.stringify({ + event: "PAYMENT_SUCCEEDED", + data: { checkout_session_id: "cs_test_123", amount: 5000, currency: "XOF" }, +}); + +const signature = signWebhook(payload, secret); + +assert.equal(signature.length, 64, "HMAC-SHA256 hex digest must be 64 chars"); +assert.match(signature, /^[0-9a-f]+$/, "signature must be lowercase hex"); +assert.ok(verifyWebhook(payload, signature, secret), "valid signature must verify"); +assert.ok(!verifyWebhook(payload, signature, "wrong_secret"), "wrong secret must fail"); +assert.ok(!verifyWebhook(payload + " ", signature, secret), "tampered body must fail"); +assert.ok(!verifyWebhook(payload, "deadbeef", secret), "invalid signature must fail"); + +console.log("PASS: webhook HMAC-SHA256 (hex) contract"); diff --git a/scripts/test_woo_release_zip.sh b/scripts/test_woo_release_zip.sh new file mode 100755 index 0000000..5b8027b --- /dev/null +++ b/scripts/test_woo_release_zip.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Build (optional) and validate the WooCommerce release zip structure. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WOO_DIR="${WOO_DIR:-$(cd "${SCRIPT_DIR}/../woo" && pwd)}" +RUN_RELEASE="${RUN_RELEASE:-1}" + +log() { + printf "[%s] %s\n" "$(date +'%H:%M:%S')" "$1" +} + +if [[ ! -f "${WOO_DIR}/woo-lomi.php" ]]; then + echo "FAIL: woo-lomi.php not found at ${WOO_DIR}/woo-lomi.php (initialize woo submodule?)" + exit 1 +fi + +if [[ "${RUN_RELEASE}" == "1" ]]; then + log "Building Woo release zip" + bash "${WOO_DIR}/scripts/release.sh" +fi + +DIST_DIR="${WOO_DIR}/dist" +ZIP_PATH="${DIST_DIR}/woo-lomi.zip" +if [[ ! -f "${ZIP_PATH}" ]]; then + ZIP_PATH="$(find "${DIST_DIR}" -maxdepth 1 -name 'woo-lomi-*.zip' -type f | head -1 || true)" +fi + +if [[ -z "${ZIP_PATH}" || ! -f "${ZIP_PATH}" ]]; then + echo "FAIL: no release zip found under ${DIST_DIR}" + exit 1 +fi + +log "Validating ${ZIP_PATH}" + +if ! command -v unzip >/dev/null 2>&1; then + echo "FAIL: unzip is required to inspect the release archive" + exit 1 +fi + +LISTING="$(unzip -l "${ZIP_PATH}")" + +require_entry() { + local pattern="$1" + local label="$2" + if ! printf '%s\n' "${LISTING}" | grep -qE "${pattern}"; then + echo "FAIL: zip missing ${label}" + exit 1 + fi +} + +require_entry 'woo-lomi/woo-lomi\.php' 'woo-lomi/woo-lomi.php' +require_entry 'woo-lomi/includes/' 'woo-lomi/includes/' +require_entry 'woo-lomi/assets/' 'woo-lomi/assets/' + +if printf '%s\n' "${LISTING}" | grep -q 'node_modules'; then + echo "FAIL: zip must not contain node_modules" + exit 1 +fi + +if printf '%s\n' "${LISTING}" | grep -qE 'woo-lomi/(resources|dist)/'; then + echo "FAIL: zip must not contain resources/ or dist/ build trees" + exit 1 +fi + +log "PASS: Woo release zip structure (${ZIP_PATH})" diff --git a/scripts/verify-lomi-plugins.sh b/scripts/verify-lomi-plugins.sh index d63d96f..d05a6b2 100755 --- a/scripts/verify-lomi-plugins.sh +++ b/scripts/verify-lomi-plugins.sh @@ -99,6 +99,14 @@ file_must_exist() { fi } +# Private platform submodules (shopify, bubble) are not checked out in CI +# because the default token cannot clone them. Skip their checks when absent; +# they are validated where the submodule is available (local/dev with access). +plugin_present() { + local dir="$1" + [[ -d "$dir" ]] && [[ -n "$(ls -A "$dir" 2>/dev/null)" ]] +} + while [[ $# -gt 0 ]]; do case "$1" in --run-runtime) @@ -156,14 +164,22 @@ search_must_exist "/checkout-sessions|X-API-Key|X-Lomi-Signature|X-Lomi-Event|ap echo " PASS" echo "- Shopify" -search_must_exist "/checkout-sessions|integration_source|api\.lomi\.africa|sandbox\.api\.lomi\.africa" \ - "$PLUGINS_DIR/shopify/app" -echo " PASS" +if plugin_present "$PLUGINS_DIR/shopify/app"; then + search_must_exist "/checkout-sessions|integration_source|api\.lomi\.africa|sandbox\.api\.lomi\.africa" \ + "$PLUGINS_DIR/shopify/app" + echo " PASS" +else + echo " SKIP (shopify submodule not checked out)" +fi echo "- Bubble" -search_must_exist "/checkout-sessions|X-API-Key|X-Lomi-Signature|api\.lomi\.africa|sandbox\.api\.lomi\.africa" \ - "$PLUGINS_DIR/bubble" -echo " PASS" +if plugin_present "$PLUGINS_DIR/bubble"; then + search_must_exist "/checkout-sessions|X-API-Key|X-Lomi-Signature|api\.lomi\.africa|sandbox\.api\.lomi\.africa" \ + "$PLUGINS_DIR/bubble" + echo " PASS" +else + echo " SKIP (bubble submodule not checked out)" +fi log "3/8 Validating integration_source attribution (must match DB enum)" @@ -183,22 +199,34 @@ search_must_exist "'integration_source'[[:space:]]*=>[[:space:]]*'magento'" \ echo " PASS" echo "- Shopify → shopify" -search_must_exist 'integration_source:[[:space:]]*"shopify"' \ - "$PLUGINS_DIR/shopify/app" -echo " PASS" +if plugin_present "$PLUGINS_DIR/shopify/app"; then + search_must_exist 'integration_source:[[:space:]]*"shopify"' \ + "$PLUGINS_DIR/shopify/app" + echo " PASS" +else + echo " SKIP (shopify submodule not checked out)" +fi echo "- Bubble → bubble" -search_must_exist "integration_source: 'bubble'|integration_source: \"bubble\"" \ - "$PLUGINS_DIR/bubble" -echo " PASS" +if plugin_present "$PLUGINS_DIR/bubble"; then + search_must_exist "integration_source: 'bubble'|integration_source: \"bubble\"" \ + "$PLUGINS_DIR/bubble" + echo " PASS" +else + echo " SKIP (bubble submodule not checked out)" +fi log "4/8 Validating XOF amount handling (whole francs, not minor units)" search_must_exist "=== 'XOF'" "$PLUGINS_DIR/magento/Gateway/LomiApiClient.php" search_must_exist "=== 'XOF'" "$PLUGINS_DIR/prestashop/lomi/lomi.php" search_must_exist "'XOF' ===" "$PLUGINS_DIR/woo/includes/class-wc-gateway-lomi.php" -search_must_exist "code === 'XOF'" "$PLUGINS_DIR/bubble/lib/lomi-server-helpers.js" -echo "PASS: XOF special-casing present in Magento, PrestaShop, Woo, and Bubble." +if plugin_present "$PLUGINS_DIR/bubble"; then + search_must_exist "code === 'XOF'" "$PLUGINS_DIR/bubble/lib/lomi-server-helpers.js" + echo "PASS: XOF special-casing present in Magento, PrestaShop, Woo, and Bubble." +else + echo "PASS: XOF special-casing present in Magento, PrestaShop, and Woo (Bubble skipped)." +fi log "5/8 Validating checkout branding assets" @@ -208,11 +236,15 @@ file_must_exist "$PLUGINS_DIR/prestashop/lomi/views/css/checkout-branding.css" file_must_exist "$PLUGINS_DIR/prestashop/lomi/views/img/pay-with-lomi.webp" file_must_exist "$PLUGINS_DIR/woo/assets/css/checkout-branding.css" search_must_exist "wc-lomi-checkout-branding" "$PLUGINS_DIR/woo" -file_must_exist "$PLUGINS_DIR/bubble/assets/images/pay-with-lomi.webp" -file_must_exist "$PLUGINS_DIR/bubble/assets/images/secured-by-lomi.webp" -file_must_exist "$PLUGINS_DIR/bubble/assets/js/lomi-embed.js" -file_must_exist "$PLUGINS_DIR/bubble/elements/LOM-branding/params.json" -echo "PASS: branding CSS and pay-with image present on Magento, PrestaShop, Woo, and Bubble." +if plugin_present "$PLUGINS_DIR/bubble"; then + file_must_exist "$PLUGINS_DIR/bubble/assets/images/pay-with-lomi.webp" + file_must_exist "$PLUGINS_DIR/bubble/assets/images/secured-by-lomi.webp" + file_must_exist "$PLUGINS_DIR/bubble/assets/js/lomi-embed.js" + file_must_exist "$PLUGINS_DIR/bubble/elements/LOM-branding/params.json" + echo "PASS: branding CSS and pay-with image present on Magento, PrestaShop, Woo, and Bubble." +else + echo "PASS: branding CSS and pay-with image present on Magento, PrestaShop, and Woo (Bubble skipped)." +fi log "6/8 Validating test/live webhook secret switching" @@ -222,7 +254,9 @@ search_must_exist "REFUND_COMPLETED" "$PLUGINS_DIR/magento/Controller/Payment/We search_must_exist "getWebhookSecret|LOMI_TEST_WEBHOOK_SECRET|LOMI_LIVE_WEBHOOK_SECRET" "$PLUGINS_DIR/prestashop" search_must_exist "test_webhook_secret|live_webhook_secret|webhook_secret" "$PLUGINS_DIR/woo/includes/class-wc-gateway-lomi.php" search_must_exist "sandbox\.api\.lomi\.africa|api\.lomi\.africa" "$PLUGINS_DIR/magento/Gateway/LomiApiClient.php" -search_must_exist "sandbox\.api\.lomi\.africa|api\.lomi\.africa" "$PLUGINS_DIR/bubble/actions/LOM-create_session/server.js" +if plugin_present "$PLUGINS_DIR/bubble"; then + search_must_exist "sandbox\.api\.lomi\.africa|api\.lomi\.africa" "$PLUGINS_DIR/bubble/actions/LOM-create_session/server.js" +fi search_must_exist "sandbox\.api\.lomi\.africa|api\.lomi\.africa" "$PLUGINS_DIR/prestashop/lomi/classes/LomiApiClient.php" echo "PASS: test/live API base URLs and webhook secret fields detected." @@ -232,10 +266,14 @@ file_must_exist "$PLUGINS_DIR/woo/assets/js/checkout-abandon.js" file_must_exist "$PLUGINS_DIR/magento/view/frontend/web/js/checkout-abandon.js" file_must_exist "$PLUGINS_DIR/prestashop/lomi/views/js/checkout-abandon.js" file_must_exist "$PLUGINS_DIR/prestashop/lomi/controllers/front/abandon.php" -file_must_exist "$PLUGINS_DIR/bubble/assets/js/checkout-abandon.js" -file_must_exist "$PLUGINS_DIR/bubble/actions/LOM-parse_webhook/server.js" -search_must_exist "PAYMENT_SUCCEEDED" "$PLUGINS_DIR/bubble" -echo "PASS: abandon recovery assets present on Woo, Magento, PrestaShop, and Bubble." +if plugin_present "$PLUGINS_DIR/bubble"; then + file_must_exist "$PLUGINS_DIR/bubble/assets/js/checkout-abandon.js" + file_must_exist "$PLUGINS_DIR/bubble/actions/LOM-parse_webhook/server.js" + search_must_exist "PAYMENT_SUCCEEDED" "$PLUGINS_DIR/bubble" + echo "PASS: abandon recovery assets present on Woo, Magento, PrestaShop, and Bubble." +else + echo "PASS: abandon recovery assets present on Woo, Magento, and PrestaShop (Bubble skipped)." +fi log "7/8 Scanning plugin trees for broken image path references" python3 "$SCRIPT_DIR/scan_broken_images.py" diff --git a/woo b/woo index dc01338..b825271 160000 --- a/woo +++ b/woo @@ -1 +1 @@ -Subproject commit dc0133881b8ce248e7117877e91e4c6fa3745452 +Subproject commit b8252719258bc4c59a6134ea6ee35ca979b6fd6c