memberflow/plorea
Plorea is a Laravel package for building membership workflows: define plans, manage member access, and add subscription-ready features to your app with a clean, configurable API. It streamlines gated content and member management.
Architecture fit. The package is a thin, idiomatic Laravel SDK over an external payment API: a Plorea facade, DTOs (Amount, Refund, status objects), PHP enums (Channel, RecurringType), typed exceptions (PloreaException, PaymentAlreadyPaidException), and a fluent builder per resource (payments(), payByLink(), paymentMethods(), subscriptions()). That is exactly the shape a Laravel codebase wants — it slots into a service layer without an adapter, and its design assumptions (cache locks, queues, scheduled commands, event listeners) match framework primitives the app already runs. It is a payment-orchestration SDK, not a payment processor: Plorea sits between the app and Adyen, so the integration surface we own is business logic (references, merchant routing, booking, reconciliation), not card data. That is a favourable fit.
Integration feasibility. High for the hosted payment-link path — roughly ten lines to a working link, with firstOrCreate() solving duplicate-reference drift out of the box. Medium for the full surface: webhooks need a durable ingest table, CSRF exclusion, queued deduplicating listeners and a scheduled backstop; embedded web checkout adds Adyen Web v6 client work, CSP rules and origin whitelisting done by a human at Plorea; native adds iOS/Android Drop-in, universal links / App Links files and another whitelisting round-trip. Card storage has no webhook at all and must be polled. None of this is hard, but it is not a one-sprint integration, and several steps have external lead time.
Technical risk. Ranked:
| Risk | Severity | Notes |
|---|---|---|
| Unproven package (0 stars, 0 dependents, single vendor) | High | We are the de facto QA. No community CVE surface, but also no peer review of money-handling code |
Non-atomic firstOrCreate() |
High | Server has no idempotency key; double-submit creates duplicate links unless we hold a per-reference cache lock |
| At-most-once webhooks | High | A 500 permanently loses the event; requires persist-before-process plus a poller backstop |
| Silent environment misconfiguration | High | PLOREA_ENVIRONMENT defaults to test; since 2026-10-13 a live key with the variable unset returns 403 on every call |
| Retry amplification | High | Default retry behaviour on non-idempotent writes double-bills; must set PLOREA_RETRY_TIMES=0 or prove idempotency |
PII leakage via $session->raw |
Medium | Carries tenant, shopper reference and customer id; only toCheckout() may cross the wire |
| Unverified channels | Medium | Android Drop-in on device, native 3DS2 and a live environment value are all unobserved per the vendor's own docs |
| Two-layer vendor dependency | Medium | Package → Plorea → Adyen: three change cadences, one ambiguous escalation path |
The documentation is genuinely above average — the skill files read like operational runbooks (go-live checklist, failure modes, "unverified, do not promise these"). That partially offsets the zero-star signal, but documentation is not a support contract.
Key questions. What SLA and support channel does Plorea offer, and who debugs when Plorea blames Adyen? What is the versioning/deprecation policy — is the API contract semver-stable? Is there an OpenAPI spec or changelog we can diff against? What is our PCI scope in writing (SAQ A for hosted, SAQ A-EP for embedded Drop-in), and will Plorea attest to it? Where do shopper references and customer ids reside, and is that GDPR-defensible for our DPA? What is the error taxonomy — which exceptions are retryable, which are terminal? Can we vendor or fork the package under MIT if upstream stalls? What happens to in-flight sessions if Plorea has an outage — is there a status endpoint we can poll independently?
Stack fit. Requires a distributed cache for locks (Redis, not array/file if we run multiple app nodes), a reliable queue worker, a running scheduler for the backstop poller, and an HTTP client with a circuit breaker. The webhook route must be CSRF-exempt and excluded from any auth middleware. Client-side, Adyen Web v6 is a separate npm dependency with its own upgrade track and its own CSP allowlist (checkoutshopper-* and issuer 3DS frames, live hosts differing from test).
Migration path. Greenfield is clean: build the webhook ingest table and the reconciliation job before the first payment. From an incumbent PSP, run parallel: feature-flag by merchant org number, dual-write payment state, keep the legacy provider live for merchants who have not completed KYC, and reconcile daily for a full billing cycle before cutting over. Preserve external references verbatim — firstOrCreate()'s suffix chain (-1, -2) means a reference can be superseded, so the mapping table must record the suffix chain, not just the base.
Compatibility. Pin the package version exactly and pin Laravel/PHP minors in CI. Client-side Adyen Web v6 is version-pinned independently of the PHP package — treat them as two upgrades. Origin and app-identifier whitelisting is manual and must be requested per environment and per customer-owned domain; agree the process for adding a domain before a merchant asks, because it fails late as a CORS error.
Sequencing.
PLOREA_PLATFORM, webhook secret, test vs live keys, named support contact.status(), PaymentAlreadyPaidException handling, cache-lock wrapper.$event->eventId, queued listeners, CSRF exclusion, assertSent-style contract tests via Plorea::fake().pending_setup past expiresAt, subscriptions()->needingAttention(). Refunds polled in hours, never reversed on timeout.ResponseReceived with status ≥ 400, on ConnectionException, on stuck states; confirm Authorization is not logged.localhost), redirectResult return page.Maintenance. We absorb upstream churn ourselves. Budget for a quarterly dependency review and a "read the diff before bumping" policy on a package that moves money. Vendor or mirror the repo so a yanked tag cannot break builds. The Amount::nok() surface suggests NOK-only — confirm before assuming multi-currency, since a currency expansion would be a package-level change, not a config one.
Support. There is no community: no Stack Overflow corpus, no issue history, no dependents. Escalation is a named Plorea contact or nothing. Negotiate that contact before signing, write an internal runbook with the vendor's own silent-failure list, and assign a named owner. Finance needs a counterpart for KYC onboarding, payout holds and refund adjudication — this is not purely an engineering integration.
Scaling. HTTP-bound, so raw throughput is fine; the real costs are: cache-lock contention per reference under double-submit bursts, one extra status request per firstOrCreate() as the suffix chain lengthens, webhook worker concurrency during payout windows, and the card-setup poller — which has no webhook and therefore scales linearly with pending setups. Size the poller's interval and budget explicitly, and cap it so a stuck setup cannot poll forever.
Failure modes to design against. A 500 on a webhook loses the event permanently (persist first, replay from the backstop). A missing CSRF exclusion yields 419 on every delivery. PLOREA_ENVIRONMENT unset with a live key yields 403 everywhere. PLOREA_RETRY_TIMES left at default can double-create links or charges. Trusting onPaymentCompleted shows "paid" for Pending and Received — the server decides, always. Refund timeouts reported as failures create support tickets and incorrect books. A raw session DTO returned over HTTP leaks tenant and shopper identifiers. An unwhitelisted origin surfaces as a late CORS error in /sessions/{id}/setup.
Ramp-up. One backend engineer, roughly 2–3 weeks to a hardened hosted-link integration including webhook durability and reconciliation; a further 3–4 weeks for embedded web plus native, gated on Adyen client work and external whitelisting. Add finance/ops ramp for KYC and refunds, and support training on the silent-failure list specifically. Treat the vendor's "unverified" items — Android on device, native 3DS2, live environment — as unproven until we have observed them ourselves on a live low-value payment; do not let a product commitment depend on them. App-store policy must be decided early: real-world services and invoices are generally exempt from in-app purchase, digital goods are not.
How can I help you explore Laravel packages today?