oktocode/sham-cash-sdk
oktocode/sham-cash-sdk is a lightweight PHP SDK for integrating Sham Cash payments into Laravel applications. Send transfers, verify transactions, and manage wallet operations through a clean API with minimal configuration.
Architecture fit
oktocode/sham-cash-sdk is a framework-free Composer library with a documented policy that Laravel/Symfony/WordPress bridges belong in other repositories. For a Laravel codebase this is a library, not a package with a service provider, config, or facade. We own the glue.Client) with createBill, getBill, refundBill, listTransactions, eachTransaction, parseWebhook. Public types are limited to Client, models, enums, exceptions. Crypto, Http, and Support are @internal — correct encapsulation, and it means we must never reach past Client.illuminate/http in every Laravel app, and the constructor accepts a PSR-18 client plus an optional PSR-3 logger. That is exactly the seam needed to bind Client as a container singleton fed by config('services.shamcash.*') and Laravel's LogManager.secretKey decoded to 32 raw bytes) is encapsulated. That is the right design and the strongest argument for using the SDK rather than calling the API directly — but it also concentrates risk in a package with 12 stars and 0 dependents. Treat the crypto path as third-party code under review, not as a black box.Integration feasibility
Technical risk
parseWebhook takes the raw body string and must not be re-encoded. Any Laravel middleware, request validator, or $request->all() call silently breaks verification. This will fail 100% of the time and look like a crypto bug.exp/iat tolerance is 30 seconds and token TTL is 300 seconds. Multi-node deployments, container drift, or serverless cold starts will produce intermittent CryptoExceptions that masquerade as "bad token." NTP/chrony discipline is a launch blocker, not a nice-to-have.idempotencyKey is 10–100 characters, supplied by us, and the SDK explicitly does not retry money calls. Laravel queue $tries/$backoff retries will double-refund unless the key is derived deterministically and persisted before dispatch.getBill is a one-shot fallback ≥10 minutes after create, only when the webhook never arrived. Webhook loss therefore means silent unreconciled state. A scheduled eachTransaction sweep is mandatory, not optional.createBill can return 1704; we must catch it and call getBill. Easy to omit, easy to miss in QA.encData. Explicit redaction (and a CI assertion) is required.^1.0 for a money path.ApiException + ResultCode with no per-code subclasses is a clean design, but we need to confirm forward-compatibility for codes we haven't seen.Key questions
local/sandbox.php the only route?agentKey per-merchant or per-integration? This determines whether we need multi-tenant client resolution or a single singleton.{"alg":"dir","enc":"A256GCM","cty":"json"} header?idempotencyKey scoped per bill or globally?ResultCode exhaustive and documented, and how should clients handle unmapped codes?Stack fit
config.platform.php pinned to 8.2.0 aligns with Laravel 10/11. CI covers PHPUnit, PHPStan level 8 on src, and PSR-12 across PHP 8.2/8.3/8.4 — engineering hygiene is better than the star count implies.illuminate/http; PSR-18 and PSR-3 match the container and LogManager. No adapter indirection needed for HTTP.Migration path
acme/shamcash-laravel) that depends on oktocode/sham-cash-sdk and exposes:
ShamCashServiceProvider — config merge/publish, singleton Client binding, PSR-3 logger injection, TLS verification left on (the SDK enforces this even for injected PSR-18 clients).WebhookController — reads $request->getContent(), is registered outside the web middleware group (no CSRF), verifies synchronously, persists the raw body, dispatches a job, returns 200.CreateBillJob / RefundBillJob — idempotency key generated and persisted before dispatch; no blind retry on money calls.ReconcileShamCashTransactions — scheduled command streaming via eachTransaction.ApiException / TransportException / CryptoException / InvalidArgumentException → our domain exceptions and HTTP problem responses.ShamCash::fake() — a test double so feature tests never hit the network.PaymentGateway interface and route a small percentage of traffic first.Compatibility
version field in composer.json; Packagist reads git tags. Pin 1.0.0 exactly for the money path. dev-master via a path repository is acceptable only in the spike branch.POST {baseUrl}/api/ElectronicPayment/{action}, {"encData","agentKey"}, success = result === 2500 && succeeded === true). A future 2.x could change exception taxonomy or the Client signature; read the changelog before any bump.Sequencing (~4 weeks, one backend engineer)
createBill → webhook → parseWebhook → refund in a throwaway branch. Prove raw-body handling and clock behavior. Read Crypto and Http internals; decide fork vs. upstream-pin.ShamCash::fake().1704 handling, idempotency persistence, failed_jobs triage.succeeded:false).agentKey, low value cap, dashboards, on-call runbook.Maintenance
Client, a fixed wire protocol. Two recurring duties: (a) track upstream tags on a low-traffic repo, and (b) own the Laravel glue. Budget 1–2 engineer-days per quarter plus a quarterly fork-rebase ritual if we fork.2500/1704 codes, the no-polling rule, the raw-body requirement, the 10-minute getBill window) lives in the maintainer-facing agents/AGENTS.md, not in consumer-facing docs. We should convert that into an ADR and a runbook on day one.Support
Scaling
Client is stateless; horizontal scaling is fine.eachTransaction is a generator, which is the right primitive for reconciliation without memory blowup, but it must be rate-limited and batched.Failure modes
CryptoException on an otherwise valid webhook. Mitigate with NTP enforcement and a skew alert; detect by monitoring crypto-failure rate.pending. Mitigate with a scheduled eachTransaction reconciliation sweep and the one-shot getBill ≥10 minutes after create.getContent() is passed through untouched.succeeded:false → silent business failure. Mitigate by mapping ResultCode to domain errors and alerting on unmapped codes.TransportException. Circuit-break, queue non-money work with backoff, surface a clear user-facing message.secretKey, encData, or decrypted payload in logs. Mitigate with redaction rules, Laravel's dontFlash, Telescope/Clockwork exclusions, and a CI assertion.Ramp-up
2500/1704, the no-polling directive, the 10-minute getBill window, and the raw-body constraint. None of that is discoverable from the type signatures. Capture it in an ADR and a runbook before the pilot, or the first on-call rotation will rediscover it at 3 a.m.How can I help you explore Laravel packages today?