Untitled Session
2026-05-24 17:12 UTC · 65 messages · cli
You2026-05-24 17:14 UTC
GS Code Review: Adversarial review of TB's billing conversion tracking code. Check security, FAULT compliance, edge cases, test quality, and error handling that TA might have missed. APPROVE or REVISE each module.
Donna2026-05-24 17:14 UTC
I'll start by running the verification commands and exploring the directory structure of all three modules simultaneously.
Tool2026-05-24 17:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/GoogleAdsConversionInterface.php\nNo syntax errors detected in /root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/GoogleAdsConversionModule.php\nNo syntax errors detected in /roo...
Tool2026-05-24 17:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/webhook_handler/src/WebhookHandler/WebhookHandler.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/src/WebhookHandler/WebhookHandlerInterface.php\nNo syntax errors detected in /root/profe-modules/webhook_handler/src/We...
Tool2026-05-24 17:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/stripe_service/src/StripeService/StripeService.php\nNo syntax errors detected in /root/profe-modules/stripe_service/src/StripeService/StripeServiceInterface.php\nNo syntax errors detected in /root/profe-modules/stripe_service/src/StripeSer...
Tool2026-05-24 17:14 UTC
{"output": "No syntax errors detected in /root/profe-modules/checkout_service/src/CheckoutService/CheckoutService.php\nNo syntax errors detected in /root/profe-modules/checkout_service/src/CheckoutService/CheckoutServiceInterface.php\nNo syntax errors detected in /root/profe-modules/checkout_service...
Tool2026-05-24 17:14 UTC
{"output": "/root/profe-modules/google_ads_conversion/README.md:85:| FAULT-011 | No quadruple-backslash | Verified: grep shows 0 occurrences of `\\\\\\\\\\\\\\\\` |", "exit_code": 0, "error": null}
Tool2026-05-24 17:14 UTC
{"output": "clean", "exit_code": 0, "error": null}
Tool2026-05-24 17:14 UTC
{"output": "clean", "exit_code": 0, "error": null}
Donna2026-05-24 17:14 UTC
All syntax checks pass. Now let me read all source files and test files in detail.
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionInterface — Google Ads Conversion Tracking Service Contract\n 4| *\n 5| * @package Profe\\GoogleAdsConversion\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\GoogleAdsConversion;\n ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionModule — ModuleInstanceInterface implementation for google_ads_conversion\n 4| *\n 5| * FAULT-01: Factory closure captures $c, never stores container.\n 6| * FAULT-011: Uses \\\\ (2 backslashes) in single-quoted strings ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionService — Google Ads Conversion Tracking Implementation\n 4| *\n 5| * HC-01: Google Ads PHP SDK used (googleads/google-ads-php) — NOT hand-rolled API calls.\n 6| * HC-02: OAuth2 credentials via ConfigManagerInterface — N...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandlerInterface — Stripe webhook event processing service contract\n 4| *\n 5| * @package Profe\\WebhookHandler\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\WebhookHandler;\n 11|\n 12|in...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandler — Stripe webhook event processing implementation\n 4| *\n 5| * HC-01: Stripe SDK used for signature verification via StripeServiceInterface.\n 6| * HC-02: Webhook signature via SDK — NOT hand-rolled HMAC.\n 7| * HC-03: I...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandlerModule — ModuleInstanceInterface implementation for webhook_handler\n 4| *\n 5| * HC-04: Webhook route CSRF/Auth exempt.\n 6| * FAULT-004: Webhook route explicitly exempted.\n 7| * FAULT-01: Factory closure captures $c, n...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeServiceInterface — Stripe API Wrapper Service Contract\n 4| *\n 5| * Thin, type-safe wrapper around the official stripe/stripe-php SDK.\n 6| * This is the ONLY module that interacts with Stripe — no other module\n 7| * may call \...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeService — Stripe API Wrapper Implementation\n 4| *\n 5| * HC-01: Stripe SDK used — NOT hand-rolled curl.\n 6| * HC-02: Webhook signature via SDK — uses \\Stripe\\Webhook::constructEvent().\n 7| * HC-03: API keys via ConfigManager...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeServiceModule — ModuleInstanceInterface implementation for stripe_service\n 4| *\n 5| * HC-01: Stripe SDK used — requires stripe/stripe-php in composer.json.\n 6| * HC-03: API keys via ConfigManagerInterface.\n 7| * HC-04: Test/l...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutServiceInterface — Checkout flow orchestration service contract\n 4| *\n 5| * @package Profe\\CheckoutService\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\CheckoutService;\n 11|\n 12|int...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutService — Checkout flow orchestration implementation\n 4| *\n 5| * HC-02: Tier slug validated before session creation via InputSanitizerInterface.\n 6| * HC-03: Price map maps slugs to Stripe Price IDs from ConfigManager.\n 7| ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutServiceModule — ModuleInstanceInterface implementation for checkout_service\n 4| *\n 5| * HC-01: Payment endpoints CSRF-protected via checkAuthWithCsrf().\n 6| * FAULT-004: CSRF enforced on all mutation POST endpoints.\n 7| * F...
Donna2026-05-24 17:14 UTC
Now let me read the remaining source files and all tests.
Tool2026-05-24 17:14 UTC
{"content": " 501| $tiers = $this->memLevels->getTiers();\n 502| foreach ($tiers as $tier) {\n 503| if ($tier['slug'] === $slug) {\n 504| return (int) $tier['id'];\n 505| }\n 50...
Tool2026-05-24 17:14 UTC
{"total_count": 3, "files": ["/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionModuleTest.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionServiceTest.php", "/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionExceptionTest.php...
Tool2026-05-24 17:14 UTC
{"total_count": 4, "files": ["/root/profe-modules/webhook_handler/tests/Integration/WebhookHandlerIntegrationTest.php", "/root/profe-modules/webhook_handler/tests/Unit/WebhookHandlerModuleTest.php", "/root/profe-modules/webhook_handler/tests/Unit/WebhookHandlerTest.php", "/root/profe-modules/webhook...
Tool2026-05-24 17:14 UTC
{"total_count": 3, "files": ["/root/profe-modules/stripe_service/tests/Unit/StripeServiceTest.php", "/root/profe-modules/stripe_service/tests/Integration/StripeServiceIntegrationTest.php", "/root/profe-modules/stripe_service/tests/Unit/StripeServiceModuleTest.php"]}
Tool2026-05-24 17:14 UTC
{"total_count": 3, "files": ["/root/profe-modules/checkout_service/tests/Unit/CheckoutServiceTest.php", "/root/profe-modules/checkout_service/tests/Integration/CheckoutServiceIntegrationTest.php", "/root/profe-modules/checkout_service/tests/Unit/CheckoutServiceModuleTest.php"]}
Tool2026-05-24 17:14 UTC
{"total_count": 48, "files": ["/root/profe-modules/google_ads_conversion/tests/Unit/GoogleAdsConversionExceptionTest.php", "/root/profe-modules/google_ads_conversion/src/GoogleAdsConversion/Exception/GoogleAdsConversionException.php", "/root/profe-modules/admin_app/src/AdminApp/Exception/AdminAppExc...
Tool2026-05-24 17:14 UTC
{"total_count": 50, "files": ["/root/profe-modules/webhook_handler/src/WebhookHandler/Repository/WebhookEventRepository.php", "/root/profe-modules/admin_app/tests/Unit/AdminAppRepositoryTest.php", "/root/profe-modules/admin_app/src/AdminApp/Repository/AdminAppRepository.php", "/root/profe-modules/au...
Donna2026-05-24 17:14 UTC
Let me read all test files and the exception/repository source files.
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionServiceTest — Unit tests for GoogleAdsConversionService\n 4| *\n 5| * FAULT-005/009: Minimum 12 tests per module.\n 6| * HC-09: Tests ≥ 12.\n 7| *\n 8| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 9| ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionModuleTest — Unit tests for GoogleAdsConversionModule\n 4| *\n 5| * FAULT-005/009: Minimum 12 tests per module.\n 6| *\n 7| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 8| */\n 9|\n 10|declare(stri...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionExceptionTest — Unit tests for GoogleAdsConversionException\n 4| *\n 5| * @package Profe\\GoogleAdsConversion\\Tests\\Unit\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\GoogleAdsConv...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * GoogleAdsConversionException — Typed exception for Google Ads Conversion operations\n 4| *\n 5| * FAULT-017: Exception class lives in ONE canonical location: src/{Module}/Exception/.\n 6| *\n 7| * @package Profe\\GoogleAdsConversion\n ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\WebhookHandler\\WebhookHandler;\n 8|use Profe\\WebhookHandler\\WebhookHandlerInterface;\n 9|use Profe...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|\n 8|class WebhookHandlerModuleTest extends TestCase\n 9|{\n 10| // --- Test 1: Module name ---\n 11|\n...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\WebhookHandler\\Repository\\WebhookEventRepository;\n 8|use Profe\\Chassis\\ProfeDB;\n 9|\n 10|cla...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Integration;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\WebhookHandler\\WebhookHandler;\n 8|use Profe\\WebhookHandler\\Repository\\WebhookEventRepository;...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookHandlerException — Typed exception for webhook event processing\n 4| *\n 5| * @package Profe\\WebhookHandler\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\WebhookHandler;\n 11|\n 12|class ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * WebhookEventRepository — DB operations on profe_webhook_events\n 4| *\n 5| * FAULT-04: Bare table names, no $wpdb->prefix.\n 6| * FAULT-05: All queries use prepared statements.\n 7| *\n 8| * @package Profe\\WebhookHandler\\Reposito...
Donna2026-05-24 17:14 UTC
Now let me read the remaining test files, exception files, and fixture files.
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\StripeService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\StripeService\\StripeService;\n 8|use Profe\\StripeService\\StripeServiceInterface;\n 9|use Profe\\Str...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\StripeService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|\n 8|class StripeServiceModuleTest extends TestCase\n 9|{\n 10| public function testModuleNameIsStripeServi...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * StripeServiceException — Typed exception for Stripe Service operations\n 4| *\n 5| * Wraps Stripe SDK exceptions into domain-specific exceptions.\n 6| *\n 7| * @package Profe\\StripeService\n 8| */\n 9|\n 10|declare(strict_t...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\CheckoutService\\CheckoutService;\n 8|use Profe\\CheckoutService\\CheckoutServiceInterface;\n 9|use ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Unit;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|\n 8|class CheckoutServiceModuleTest extends TestCase\n 9|{\n 10| // --- Test 11: Module name ---\n 11...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * CheckoutServiceException — Typed exception for checkout flow operations\n 4| *\n 5| * @package Profe\\CheckoutService\n 6| */\n 7|\n 8|declare(strict_types=1);\n 9|\n 10|namespace Profe\\CheckoutService;\n 11|\n 12|cla...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Integration;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\CheckoutService\\CheckoutService;\n 8|use Profe\\CheckoutService\\CheckoutServiceInterface;\n ...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\StripeService\\Test\\Integration;\n 5|\n 6|use PHPUnit\\Framework\\TestCase;\n 7|use Profe\\StripeService\\Repository\\SubscriptionRepository;\n 8|use Profe\\Chassis\\ProfeDB;\n 9|\n 1...
Donna2026-05-24 17:14 UTC
Let me check the fixtures and search for specific security patterns.
Tool2026-05-24 17:14 UTC
{"total_count": 37, "files": ["/root/profe-modules/checkout_service/tests/Fixture/CheckoutServiceFixtures.php", "/root/profe-modules/webhook_handler/tests/Fixture/WebhookHandlerFixtures.php", "/root/profe-modules/admin_app/tests/Fixture/AdminAppFixtures.php", "/root/profe-modules/automator/tests/Fix...
Tool2026-05-24 17:14 UTC
{"total_count": 1, "files": ["/root/profe-modules/stripe_service/src/StripeService/Repository/SubscriptionRepository.php"]}
Tool2026-05-24 17:14 UTC
{"total_count": 4, "matches": [{"path": "/root/profe-modules/checkout_service/src/CheckoutService/CheckoutService.php", "line": 173, "content": " if (isset($_COOKIE[$cookieName]) && !empty($_COOKIE[$cookieName])) {"}, {"path": "/root/profe-modules/checkout_service/src/CheckoutService/Chec...
Tool2026-05-24 17:14 UTC
{"total_count": 0}
Tool2026-05-24 17:14 UTC
{"total_count": 21, "matches": [{"path": "/root/profe-modules/brevo_service/src/BrevoService/BrevoApiClient.php", "line": 172, "content": " $retryAfter = (int) wp_remote_retrieve_header($response, 'Retry-After');"}, {"path": "/root/profe-modules/rate_limiter/vendor/composer/platform_check...
Donna2026-05-24 17:14 UTC
Let me read the fixture files and the SubscriptionRepository.
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\CheckoutService\\Test\\Fixture;\n 5|\n 6|/**\n 7| * CheckoutServiceFixtures — Test data fixtures for checkout_service tests\n 8| *\n 9| * FAULT-07: Test fixtures in correct test namespace...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|declare(strict_types=1);\n 3|\n 4|namespace Profe\\WebhookHandler\\Test\\Fixture;\n 5|\n 6|/**\n 7| * WebhookHandlerFixtures — Test data fixtures for webhook_handler tests\n 8| *\n 9| * FAULT-07: Test fixtures in correct test namespace.\n...
Tool2026-05-24 17:14 UTC
{"content": " 1|<?php\n 2|/**\n 3| * SubscriptionRepository — DB operations on profe_subscriptions\n 4| *\n 5| * FAULT-04: Bare table names, no $wpdb->prefix.\n 6| * FAULT-05: All queries use prepared statements with bound parameters.\n 7| *\n 8| * @package Profe\\Str...
Tool2026-05-24 17:14 UTC
{"total_count": 0}
Donna2026-05-24 17:14 UTC
Let me check one more thing — the StripeService's test for a non-existent private method.
Tool2026-05-24 17:14 UTC
{"total_count": 0}
Tool2026-05-24 17:14 UTC
{"total_count": 1, "matches": [{"path": "/root/profe-modules/stripe_service/src/StripeService/StripeService.php", "line": 452, "content": " public function getWebhookSecret(): string"}]}
Donna2026-05-24 17:14 UTC
Now I have a complete picture. Let me compile the adversarial review.
## GS CODE REVIEW — Adversarial Sweep
Starting verification commands first, then deep-dive into all 3 modules.
---
### VERIFICATION RESULTS
```
php -l: All 12 PHP files pass syntax check (0 errors)
grep '\\\\\\\\': 0 occurrences in all modules (FAULT-011 clean)
grep 'eval|assert|exec|system|passthru|shell_exec|unserialize': 0 occurrences
```
---
### T52: Google Ads Conversion — REVISE
**What TA approved:** PHP lint ✓, no quadruple-backslash ✓, 17 tests ✓, DI wiring ✓
**What TA missed:**
**1. SECURITY: Missing currency code validation (minor)**
`buildClickConversion()` passes `strtoupper($currencyCode)` directly to the SDK without validating it's a real ISO 4217 code. Google Ads API will reject bad values, but this creates a different error path than expected. Not exploitable — the SDK is the consumer — but should validate upfront.
**2. EDGE CASE: No test for `enhanced_conversions` = false**
`buildClickConversion()` checks `$this->config->getBool('google_ads.enhanced_conversions', true)`. When disabled, `user_identifiers` is NOT included even if `$hashedEmail` is provided. **Zero tests cover this config path.** A production site that disables enhanced conversions would silently skip hashed email inclusion — and there's no test proving this works as intended.
**3. TEST QUALITY: Weak assertion pattern**
`testUploadConversionAcceptsHashedEmail()` and `testUploadConversionAcceptsCustomConversionActionId()` — both verify only that the return type is string. They **never verify what data was actually passed to the (stub) API**. The values asserted are implicit at best.
**4. TEST QUALITY: Stub method, not a real integration test**
`uploadClickConversion()` on line 183 is a simulation returning a `sprintf()` resource name. The real SDK integration path is commented out. Tests exercise `md5()` + string concatenation, not actual Google Ads API behavior. This is a coverage gap TA should have flagged.
**5. ERROR HANDLING: `uploadConversion()` re-catches `GoogleAdsConversionException` then re-throws it unchanged (line 62-63)**
This is dead code — the catch does nothing except re-throw. Minor but unnecessary.
### T17: WebhookHandler — REVISE
**What TA approved:** Routes correct ✓, idempotency ✓, conversion upload flow ✓, CSRF exemption ✓
**What TA missed:**
**1. SECURITY: Unknown `Throwable` silently returns 200 with `success: true` (line 213-214)**
`handleStripeWebhook()` catches generic `Throwable` and returns `['success' => true]` with HTTP 200. The comment says this is to "avoid Stripe retry spam" — but this **masks real bugs**. If the DI container fails, a dependency throws, or PHP runs out of memory, Stripe gets a 200 OK and assumes everything worked. This should at minimum log the error and return 500. Current behavior is a monitoring blind spot.
**2. EDGE CASE: Non-`GoogleAdsConversionException` failures skip retry queue**
In `uploadConversionToGoogleAds()`, the `catch (GoogleAdsConversionException $e)` block (line 360) enqueues a retry. But `catch (Throwable $e)` (line 372) **only logs** — it does NOT call `enqueueConversionRetry()`. This means if `uploadConversion()` throws a `RuntimeException`, `TypeError`, or any non-typed exception, the conversion data is lost permanently with no retry mechanism. **Data loss path.**
**3. EDGE CASE: `handleInvoicePaid` bypasses `StripeServiceInterface` abstraction**
Line 208: `\Stripe\Subscription::retrieve($invoice->subscription)` — this calls the Stripe SDK directly instead of going through `$this->stripeService`. This breaks the abstraction guarantee (HC-01: "This is the ONLY module that interacts with Stripe"). Every other method delegates through the service layer.
**4. TEST QUALITY: `handleSubscriptionUpdated` registered but untested**
The route `customer.subscription.updated` → `handleSubscriptionUpdated()` is wired up. There is **zero tests** for this handler. The update handler calls `stripeService->syncSubscription()` and emits `subscription_updated` — no test verifies this.
**5. TEST QUALITY: `testConversionUploadFailureEnqueuesRetry` uses `credentialsNotConfigured` exception**
The test (line 384) throws `GoogleAdsConversionException::credentialsNotConfigured()` which has code=500. The real failure path for API errors would use `GoogleAdsConversionException::fromApiError()` (also code=500). The catch block catches `GoogleAdsConversionException` generally, so the test passes — but it's testing a credential error scenario rather than an API error scenario. Subtly wrong scenario testing.
### T15: StripeService + CheckoutService — REVISE
**What TA approved:** CSRF on POST endpoints ✓, backwards compatibility (hosted default) ✓, GCLID cookie reading ✓, ui_mode validation ✓
**What TA missed:**
**1. TEST QUALITY (CRITICAL): False-positive tests for non-existent private methods**
Three tests in `StripeServiceTest` invoke private methods via reflection that **do not exist** in the current code:
- `testIsLiveModeReturnsTrueForLiveKey()` → calls private `isLiveMode()` — **method removed/renamed**. Actual code uses `$config->getEnvironment() === 'production'`.
- `testGetPriceIdReturnsLite()` and `testGetPriceIdReturnsStandard()` → call private `getPriceId()` — **method removed/renamed**. Actual code uses `resolveLevelFromPrice()`.
- `testGetPriceIdThrowsForInvalidTier()` → same issue.
These tests pass because they catch all `Throwable` in their try/catch blocks. **They are false positives** — they do not test anything meaningful and would crash if not wrapped in try/catch-all.
**2. TEST QUALITY (CRITICAL): Try/catch-all pattern masks real failures**
Multiple tests (e.g., `testCreateCustomerStoresAndReturnsCustomerId`, `testCreateCheckoutSessionReturnsSessionData`, `testCreateBillingPortalReturnsUrl`, `testCreateCheckoutSessionHostedDefaultDoesNotIncludeEmbeddedParams`, etc.) use this pattern:
```php
try { ... } catch (Throwable $e) { $this->assertNotEmpty($e->getMessage()); }
```
This means the test **passes even if the method throws an unexpected exception** — it only checks that the exception message is non-empty. These tests provide zero confidence that the code path works correctly. They are **assertion-free pass-throughs**.
**3. TEST QUALITY: `testCreateCustomerStoresAndReturnsCustomerId` passes a stdClass not a ProfeUser**
Line 141-145 creates a `new stdClass()` with properties `uuid`, `email`, `display_name`, `user_name`. The actual method signature expects `ProfeUser`. The test calls via reflection so PHP type-hints are bypassed. However, `stdClass->email` is an untyped dynamic property — if this were a real call with strict_types, it would fail. The test works by accident, not by design.
**4. SECURITY: `$_GET['gclid']` fallback lacks regex validation**
In `CheckoutService::getGclidFromCookie()`:
- Cookie path (line 173-184): validates with `preg_match('/^[a-zA-Z0-9_-]+$/', $gclid)` ✓
- GET parameter fallback (line 189-191): **only `trim()` — no regex validation** ✗
While the GCLID flows through Stripe's API (not SQL/shell), this inconsistency means users can inject arbitrary characters into Stripe session metadata via the URL. Not actively exploitable, but violates the principle of consistent input validation.
**5. FAULT-002: Duplicated `getProfeUser()` in both modules**
Both `StripeServiceModule` (line 385) and `CheckoutServiceModule` (line 261) define an identical `getProfeUser()` method with a direct `profe_users` query. While documented, this is a code smell — a shared helper should exist. TA noted this but didn't flag the code duplication as a maintenance risk.
**6. API CONSISTENCY: `getWebhookSecret()` not in interface**
`StripeService` exposes `public function getWebhookSecret(): string` (line 452) that is **not declared in `StripeServiceInterface`**. This is a leaky abstraction — consumers depending on the concrete class instead of the interface would break if this method is refactored.
**7. EDGE CASE: `assignLevel(0, ...)` passes userId=0**
`updateMembershipFromSubscription()` (line 538) calls `$this->memData->assignLevel(0, $levelId, null)` with userId=0, explicitly documented as a placeholder. This means the primary membership update path silently fails (throws caught by `catch (Throwable)`) and falls through to the direct `profe_users` query workaround. The fallback works, but the primary path is dead code.
---
### SUMMARY
```
### T52: REVISE — Weak test assertions, missing enhanced_conversions=off test, stub-only integration
### T17: REVISE — Silent 200 on Throwable, non-GoogleAdsConversionException data loss, direct Stripe SDK call in handleInvoicePaid, untested subscription.updated handler
### T15: REVISE — 3 false-positive tests testing non-existent methods, try/catch-all pattern in 5+ tests, GET gclid path lacks regex validation, duplicated getProfeUser()
```
**Security concerns:**
1. Webhook handler swallows unknown `Throwable` with 200 + `success:true` — monitoring blind spot
2. `$_GET['gclid']` fallback in CheckoutService lacks regex validation unlike the cookie path
3. `handleInvoicePaid` bypasses StripeServiceInterface abstraction with direct SDK call
**Edge cases missed:**
1. Non-`GoogleAdsConversionException` during conversion upload → data lost (no retry enqueued)
2. `enhanced_conversions` config = false → no test coverage
3. `subscription.updated` handler → no test coverage
4. `getAllTiers()` raw `SELECT *` — potential perf issue on large tier sets
**Test quality issues:**
1. **3 tests call private methods that don't exist** — false positives masked by try/catch-all
2. **5+ tests use try/catch-all pattern** — effectively assertion-free pass-throughs
3. Test creates stdClass instead of ProfeUser, bypassing type hints via reflection
4. Integration test uses wrong exception type for scenario
**Overall: REVISE** — All three modules need test corrections and the critical edge cases addressed before approval. The code logic is sound; the test suite and error handling gaps are the issues.