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.
Pay::apple() covers the full server-side surface for an iOS/web Apple strategy:
payToken — local verification/decryption of Apple Pay tokens (PKCS#7 cert chain + ECDH + AES-256-GCM) with zero new dependencies, so payment data can be validated in our own infrastructure.merchantSession — Apple Pay web merchant validation proxy (mutual TLS passthrough), removing a custom backend service we would otherwise have had to build.query — transactions, history, and subscription status (SubscriptionStatus enum / 1–5 whitelist).callback — App Store Server Notifications V2 with JWS x5c verification and embedded-transaction JWS decoding, plus bundleId/environment ownership checks and certificate expiry validation.cancel/close parity — those calls raise exceptions. Refunds are queried only, and subscription cancellation is an App Store Connect / client-side flow. Roadmap implication: customer-support tooling and refund policy must be defined outside the app, and any "one-click refund" or "cancel subscription" UX in our admin panel needs a manual/Store-side runbook. Also, digital goods on iOS must use App Store IAP (Apple's commission applies), so pricing/promo decisions differ from Alipay/WeChat._action: subscriptions).ProviderConfigInterface::getMode() returns a PayMode enum instead of int, and Pay::MODE_NORMAL / MODE_SANDBOX / MODE_SERVICE constants are removed (use PayMode::*). Any code doing $config->getMode() === 1 silently evaluates false — a genuine correctness/incident risk for environment routing (e.g., accidentally pointing "sandbox" traffic at production credentials). Must be scheduled as a migration, not a drop-in bump. Mitigating: config still accepts legacy ints, and enum-ification is otherwise wire-compatible (payloads byte-identical); toJson()/json_encode() still emit 0/1/2.PARAMS_APPLE_URL_MISSING, PARAMS_APPLE_TOKEN_INVALID, CONFIG_APPLE_INVALID, DECRYPT_APPLE_FAILED) — supports clean ops alerting and support-script routing.Pay::douyin()->mini(); add Apple as a second provider behind the same Pay:: facade.getMode() to ints or referencing Pay::MODE_* must budget a small, well-scoped refactor (and a test sweep for environment-routing assertions).cancel/close, no server-initiated refunds — cancel/close throw. If the product requires lifecycle control from our backend, plan Store-side operational runbooks or complement with App Store Connect workflows.v2.x is deprecated. PHP < 8.5 is fine unless you specifically need Apple RSA_v1 token decryption."This package lets us launch in China — and now on iOS — significantly faster by eliminating custom payment SDK development. With 5.3K stars, MIT licensing, and an Apple provider that just shipped, it remains the de facto standard for Alipay/WeChat/UnionPay in PHP."
Pay::MODE_* removed; getMode() now returns a PayMode enum). Budget ~1 sprint across integration + QA, not zero.Ask: 'Should we prioritize this for our Q3 China expansion, with Apple as a fast-follow on the v3.8 stable release?'
"This remains the most maintainable way to handle payments in PHP — and the Apple provider removes our two scariest build items: token cryptography and App Store notification verification. But this release has a real breaking change; here's the plan."
Pay::alipay()->web(), Pay::wechat()->mini(), Pay::apple()->payToken() — consistent patterns across gateways, one facade, one config model.x5c) chains + nested transaction JWS.AppleTrait is a thin facade (URL/ES256 JWT/crypto entry points); implementation lives in src/Crypto/Apple/ (Cryptor, TokenVerifier, JwsVerifier). Public API is unchanged by that refactor, and it's unit-testable in isolation — good for our coverage goals.PayMode, WeChat AppType / PapayScene / ReceiverType / VirtualPayEnv, Alipay ProductCode / BizCode, Apple SubscriptionStatus. Payloads are byte-identical and legacy ints/unknown values are still tolerated (unknown _type → MP, unknown env → production, unknown split type passthrough), so this is a typing improvement, not a protocol change.PayEvent::PAYMENT_SUCCESS).src/Certificate/ with apple_root_ca / apple_intermediate_ca overrides and validity checks.Breaking changes to action before upgrade:
Pay::MODE_NORMAL / MODE_SANDBOX / MODE_SERVICE are removed → use Yansongda\Pay\Enum\PayMode; and reference PayMode::*.getMode() now returns PayMode, not int — $config->getMode() === 1 is always false. Replace with ->isSandbox() / isService() / isNormal(). Grep for all mode comparisons in plugins and app code; this is the highest-severity item because a silent false can misroute environments. Add a test asserting sandbox vs. production routing.AbstractConfig::getModeEnum() removed — fold call sites into getMode().'mode' => 1) still work; toJson()/json_encode() still emit 0/1/2 — no storage or wire migration needed.v3.8.0-beta.8 explicitly, validate in staging against real Alipay/WeChat sandboxes and an Apple sandbox environment, and land the mode refactor as its own PR.Trade-offs:
issuer_id, bundle_id, api_key_id, api_private_key).Proposal:
Blockers to address:
vault or AWS KMS).PayMode migration (highest-risk regression).How can I help you explore Laravel packages today?