yansongda/pay
A popular PHP payment library for Laravel and other frameworks, integrating Alipay and WeChat Pay with a clean API. Supports common payment flows, refunds, queries, callbacks/notifications, and flexible configuration for multi-app and sandbox use.
laravel-pay extension) and adheres to PSR standards (PSR-2, PSR-4, PSR-7, PSR-11, PSR-14, PSR-18), fitting Laravel’s container, config, and event systems.StartPlugin → Business Plugins → ParserPlugin pipeline continues to allow provider additions without core changes. This release validates the claim: Apple Pay was added as a first-class provider (Pay::apple()) without breaking the existing Alipay/WeChat/UnionPay call paths.Tenant abstraction unchanged; still bindable via middleware or container. Apple introduces a new dimension — configuration is keyed on bundle_id and environment, so tenant isolation must now account for per-tenant bundle IDs where applicable.src/Crypto/Apple/ (Cryptor, TokenVerifier, JwsVerifier) with AppleTrait retained as a facade. Good separation for review/testability; no public API change.Pay facade, config/pay.php, tenant middleware.issuer_id, bundle_id, api_key_id, api_private_key (ES256 JWT), plus optional apple_root_ca / apple_intermediate_ca overrides. These belong in .env/secret manager, not committed config.AppleRootCA-G3.pem, AppleAAICAG3.pem). This is a strong supply-chain and auditability win — no new transitive risk — but it makes correctness of the in-house crypto a review item.merchantSession requires TLS mutual authentication passed through _http; any outbound egress proxy, service mesh sidecar, or HTTP client swap must support client certificates end-to-end.PayMode (SANDBOX/NORMAL/SERVICE) plus WeChat AppType, PapayScene, ReceiverType, VirtualPayEnv and Alipay ProductCode, BizCode replace magic literals. Improves correctness and IDE support; historic int config values (0/1/2) still accepted for mode, and toJson() still emits 0/1/2, so wire payloads are byte-identical.getMode() Return Type Change (highest priority): ProviderConfigInterface::getMode() now returns the PayMode enum, not int. Any code doing $config->getMode() === 1 or Pay::MODE_SERVICE === $config->getMode() silently evaluates to false — a fail-open condition in callbacks/mode-gating logic that will not throw. Mitigation: grep-and-replace with ->isSandbox() / ->isService() / ->isNormal() before upgrading, and add a regression test asserting mode-gated branches.Pay::MODE_NORMAL / MODE_SANDBOX / MODE_SERVICE: Hard fatal errors at boot/include time. Prior guidance in this assessment (using Pay::MODE_SERVICE for service-account callbacks, Pay::MODE_SANDBOX in test suites) is now invalid — replace with PayMode::* (use Yansongda\Pay\Enum\PayMode;). AbstractConfig::getModeEnum() is also removed.v3.8.0-beta.8 — eight betas into a release line indicates active churn. Treat as non-GA: pin an exact version, no floating constraints, and require a green integration suite before any bump.RSA_v1 token decryption requires PHP ≥ 8.5 (OAEP-SHA256 parameter constraints); EC_v1 has no version requirement. If the platform runs PHP 8.1–8.3 and a merchant/payment token is RSA_v1, DECRYPT_APPLE_FAILED (9612) results. Confirm which token format your Apple Pay merchant configuration issues before committing to a timeline.refund returns refund history only, and cancel/close throw. This invalidates any assumption of a uniform “refund API” across providers; the refund workflow must be redesigned as user-initiated via Apple with server-side reconciliation.apple_root_ca/apple_intermediate_ca config managed by platform engineering, and track upstream cert rotation.x5c verification plus a secondary decode of the embedded transaction JWS, with bundleId/environment ownership checks aligned to Apple’s official library. Strong, but two-stage verification plus idempotency on transactionId is mandatory — Apple retries notifications.merchantSession mTLS and coroutine concurrency is an untested combination — do not enable under Swoole without a spike.merchantSession (mTLS, initiative_context mandatory), (b) in-app/App Store verification via payToken decrypt, or (c) both? (c) doubles the integration surface and the certificate/domain-validation work.EC_v1 or RSA_v1? If RSA_v1, is a PHP 8.5 upgrade on the roadmap — and what is the interim plan?query (_action: transaction/history/subscriptions) reconcile Apple-side state into our payments records?v3.8.0-beta.8 now (pinned) to unblock Apple, or hold at v3.7.x for Alipay/WeChat and schedule Apple for the GA release? If holding, no action is needed on the breaking changes yet — but the audit should still start.bundle_id/environment keyed. Do tenants share one app/bundle, or does each tenant carry its own App Store Connect credentials (issuer_id, api_key_id, api_private_key)? The latter has key-custody and rotation implications..p8 keys and merchant certificates live — env, Vault, KMS? Who can read them, and what is the rotation runbook?9234 URL missing, 9235 token invalid, 9413 config invalid, 9612 decrypt failed) need mapping to alerting/runbooks and to customer-facing messages.PayMode::SANDBOX is wired into CI, plus Apple sandbox environment and fixture JWS/cert chains for verifier unit tests.Pay facade/service with tenant-aware config; no change required for Apple beyond a new config block.config/pay.php; add apple section with issuer_id, bundle_id, api_key_id, api_private_key, optional CA overrides. Migrate mode to 'mode' => PayMode::SANDBOX (int form still accepted).PaymentProcessed / PaymentFailed uniformly for Apple and Alipay/WeChat so downstream fulfillment is provider-agnostic.VerifyPaymentCallback middleware still applies; extend for Apple JWS verification. Apple notification endpoint must be publicly reachable over HTTPS and configured in App Store Connect.merchantSession requires mTLS client certificates — validate that egress path (no certificate-terminating proxy) supports it before design sign-off.pay.apple.callback, pay.alipay.notify) rather than hardcoded URLs.payments table: add provider, out_trade_no, provider_transaction_id (transactionId for Apple), environment (Sandbox/Production), tenant_id, status, metadata JSON. The existing schema shape is sufficient; Apple’s nested transaction JWS payload lands in metadata.provider, provider_transaction_id) as the idempotency backbone.HandleAppleNotificationJob (verify + persist + dispatch) and ReconcileAppleSubscriptionsJob (scheduled query with _status whitelist) alongside existing ProcessPaymentJob / HandleWebhookJob.Pay::MODE_NORMAL|MODE_SANDBOX|MODE_SERVICE, getModeEnum(, and getMode() compared to ints.PayMode::* and ->isSandbox()/isService()/isNormal().false regressions.yansongda/pay + yansongda/laravel-pay to the chosen beta tag; no floating constraints.toJson() payloads remain byte-identical..p8), App Store Server Notifications V2 URL registration.EC_v1 vs RSA_v1) and PHP version decision.payToken verification/decryption; merchantSession mTLS proxy; map new exception codes to domain errors.x5c + nested transaction decode, bundleId/environment ownership validation, idempotent persistence.query (_action: transaction/history/subscriptions, _status whitelist) into scheduled reconciliation.refund history query.TenantMiddleware binding.9612/9413/9235.RSA_v1 token decryption requires PHP ≥ 8.5; EC_v1 has no version constraint. Verify PHP runtime before scoping.openssl, curl, dom (XML for Alipay/UnionPay). Apple path additionally exercises OpenSSL PKCS#7/ECDH/AES-256-GCM — confirm the OpenSSL build supports the required curves/ciphers.cancel/close throw for Apple; refund is history-only. Any provider-agnostic interface in our code (e.g., PaymentProvider::refund($amount)) will need capability flags (supportsServerRefund(), supportsCancel()) rather than assuming parity.mode still accepted; toArray()['mode'] is now an enum (watch any config serialization into logs or admin UI), while toJson()/json_encode() still emit 0/1/2.MODE_* / getMode() audit and remediation — independent of Apple, de-risks the upgrade.v3.8.0-beta.8; regression-test existing Alipay/WeChat flows.payToken verification + merchantSession proxy (or narrow to one of the two per Key Question 1).PayMode enum migration is the pattern to expect — enum-ification of remaining string/int literals is explicitly ongoing. Maintain an internal changelog of every Pay:: constant and $config->get*() call so future removals are greppable.CertManager::reloadWechatPublicCerts() on the scheduler plus Apple root/intermediate CAs. Prefer explicit apple_root_ca / apple_intermediate_ca overrides over bundled defaults so rotation is a config change, not a package bump. Alert on expiry with lead time..p8 keys, merchant certificates, and Alipay/WeChat secrets in a secret manager. Rotate API keys on a schedule; document revocation blast radius (Apple JWT auth failure → all Apple server calls fail).config:cache; ensure enum-valued mode serializes cleanly in any admin/config UI that reads toArray().logs/payments.log + queue:failed, now with new Apple codes — 9234 (missing URL: config/route gap), 9235 (invalid token: client/SDK or merchant config), 9413 (invalid config: credentials/JWT), 9612 (decrypt failed: token format or PHP version). Publish a mapping table to first-response actions.refund query, (b) instructs the customer through Apple’s flow, (c) reconciles state via query. Set expectations that “refund now” is not a server action — this is the single largest operational change in this release.cancel/close throw for Apple. Support scripts must branch on provider capability; subscriptions are managed via App Store, not our backend.bundleId/environment mismatch) so escalations are actionable.web/docs/v3/apple/ pages (payment/query/refund/callback/response/plugin) and updated enum usage docs exist — but they are Chinese-first. Budget for internal translation/summary notes so on-call engineers can self-serve.provider_transaction_id) so concurrency is safe regardless of worker count.merchantSession calls carry client certificates per request. If horizontally scaling behind NAT/proxies, verify certificate presentation is preserved and connection reuse doesn’t leak tenant credentials across tenants.getMode() === 1 returns false — no exception, wrong branch. Could route service-account callbacks through normal-mode logic. Detect via assertion tests; monitor callback success-rate deltas immediately after deploy.Pay::MODE_* references fatal on class load. Caught by CI if the full app boots in tests.9612): RSA_v1 token on PHP < 8.5, or malformed token. Manifests as payment failures with a clear code — but only if error mapping is wired; otherwise it looks like a generic gateway error.bundleId/environment mismatch (common when sandbox and production endpoints are cross-configured) or expired intermediate cert. Apple retries then stops — silent revenue reconciliation drift. Alert on verification-failure rate and on notifications with no matching local transaction.merchantSession failures only at runtime, only on Apple web checkout. Requires an end-to-end smoke test in staging through the real egress path.provider_transaction_id, duplicate fulfillment. Enforce at DB constraint, not application logic.refund on Apple returns history and throws on cancel — user-visible errors. Gate UI actions on capability flags.x5c), PKCS#7 chain validation, ECDH + AES-256-GCM token decryption, mTLS merchant validation. This is a meaningfully steeper learning curve than adding a form-post gateway.How can I help you explore Laravel packages today?