Skip to content

feat(ios): resolve Stripe iOS SDK via Swift Package Manager - #2587

Draft
gbirch-stripe wants to merge 20 commits into
masterfrom
gbirch/spm-investigation
Draft

feat(ios): resolve Stripe iOS SDK via Swift Package Manager#2587
gbirch-stripe wants to merge 20 commits into
masterfrom
gbirch/spm-investigation

Conversation

@gbirch-stripe

Copy link
Copy Markdown
Collaborator

Summary

Motivation

Testing

  • I tested this manually
  • I added automated tests

Documentation

Select one:

  • I have added relevant documentation for my changes.
  • This PR does not result in any developer-facing changes.

On React Native 0.75 and above, the Stripe iOS SDK is now resolved from
the stripe-ios-spm package through React Native's spm_dependency helper
instead of the CocoaPods registry, ahead of stripe-ios deprecating
CocoaPods support. This requires building with dynamic frameworks;
apps can opt out with $StripeDisableSPM = true to keep CocoaPods
resolution while Stripe continues publishing pods. React Native
versions below 0.75 keep CocoaPods resolution.

stripe_spm.rb hooks Pod::Installer to fail fast on unsupported static
linkage, link the StripeCryptoOnramp product when the Onramp subspec is
installed, and embed SPM-built dynamic frameworks into the app bundle.

Note: example/ios/Podfile.lock still needs regenerating on macOS.
Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Pod::Target#build_type is a private reader in CocoaPods; the public API
is the build_as_* predicate family. Also guard the installer hook
against re-aliasing when the original method is private.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
React Native's prebuilt core only wires framework linkage through the
new-architecture dependency path, so with dynamic frameworks old-arch
pods compile against prebuilt React headers but never link the
framework (undefined RCT* symbols). Building from source restores the
standard CocoaPods linkage and matches what old-architecture apps
actually do. Prebuilt RN stays enabled for new-arch builds.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
The previous fix disabled both prebuilt mechanisms on the old
architecture, but only the prebuilt React core has the linkage gap.
Turning off RCT_USE_RN_DEP reintroduced source builds of React
Native's pinned third-party C++, and fmt does not compile under
Xcode 26's Clang (consteval errors). Prebuilt dependencies are
delivered as a vendored xcframework whose linkage propagates to
dependent pods through standard CocoaPods behavior, so they are safe
on both architectures; only the prebuilt React core stays gated to
the new architecture.

Also documents the Podfile end to end.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Documents stripe_spm.rb end to end (why it exists, the three-layer
mechanism, install lifecycle and ordering, and the reasoning behind
each step), annotates the podspec fallback blocks and CI config, and
adds a maintainer-facing CONTRIBUTING section covering the resolution
modes, development toggles, and the product-list/pod-dependency sync
requirement.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
react-native-test-app cannot build the old architecture with dynamic
frameworks: after the prebuilt-core linkage gap and the fmt source
compatibility issue, its own DevSupport pod fails to link (undefined
facebook::jsi::* symbols) because it under-declares React dependencies
for per-pod linking. These are all harness limitations rather than SDK
signal, so the old-arch jobs now build with STRIPE_DISABLE_SPM=1
(static libraries, both prebuilts — the configuration master already
uses), which also gives the CocoaPods fallback continuous CI coverage.
The SPM resolution mechanism is architecture-independent and remains
covered by the new-arch jobs.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
CocoaPods globs private_header_files against the entire pod root
without intersecting source_files. For a development pod the root is
the whole repo, so '**/*.h' also matched the header-store symlinks in
example/ios/Pods. Once SPM resolution removes the Stripe pods from the
dependency graph, the same install deletes their checkouts, those
symlinks dangle, and pod install crashes mapping private headers
through realpath (Errno::ENOENT). Scoping the pattern to the subspec's
own headers selects the same files without sweeping the repo.

Only machines with a pre-SPM Pods directory were affected; CI missed
it because the changed Podfile.lock checksum caused a Pods cache miss.

Also extends SPM_FINDINGS.md with this failure and a from-scratch
implementation checklist.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
The post_integrate block inherited from the SPM prototype branch
stripped react-native-test-app's -DENABLE_SINGLE_APP_MODE flag, which
dropped the example app into multi-component picker mode: the example
screen gets pushed onto a navigation stack and grows a spurious back
button that master doesn't have. app.json configures singleApp mode,
so keep the flag and launch directly into the example component.

Also records the manual runtime validation results (PaymentSheet
renders correctly under SPM resolution) in SPM_FINDINGS.md.

Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Committed-By-Agent: claude
Orbit-Session-Id: 155f17ce-962c-4e65-9f06-d117f8bc9157
Under dynamic frameworks with Hermes enabled, React Native excludes
jsi.cpp from React-jsi (the JSI symbol definitions live in
hermes-engine's prebuilt dylib per the One Definition Rule), so any pod
whose own C++ references JSI must link hermes.framework directly.
react-native-test-app's DevSupport pod declares React-jsi but not
hermes-engine, which broke old-arch dynamic-framework builds with
undefined facebook::jsi::* symbols. Extend the existing DevSupport
xcconfig hack to also cover old-arch SPM builds, force-linking just
hermes there (the React frameworks already arrive through normal pod
dependencies when the core is built from source).

Committed-By-Agent: claude
Orbit-Session-Id: c161ea21-96c4-456f-b42b-f920cb035486
With the DevSupport hermes fix in place, drop STRIPE_DISABLE_SPM from
e2e-build-ios-old-arch so old arch builds with SPM resolution (dynamic
frameworks) like the new-arch jobs. That removes the only job
exercising the CocoaPods fallback path, so add a build-only
build-ios-fallback workflow (new arch + static libraries + registry
pods — the closest match to today's real-world default configuration).
Also add unit-test-ios-old-arch so the native unit tests run on both
architectures.

Committed-By-Agent: claude
Orbit-Session-Id: c161ea21-96c4-456f-b42b-f920cb035486
Committed-By-Agent: claude
Orbit-Session-Id: c161ea21-96c4-456f-b42b-f920cb035486
The job hung at test launch ("test runner hung before establishing
connection"): CI has no Metro server, the unit-test jobs build no JS
bundle, and with neither available the Debug test app's old-arch launch
path never becomes ready for XCTest. react-native-test-app falls back
to an embedded main.ios.jsbundle when the Metro probe fails, so run
_build_js_bundle before pod install, the same sequence the e2e jobs
use. The new-arch job doesn't need this: the bridgeless launch path
tolerates having no bundle at all.

Committed-By-Agent: claude
Orbit-Session-Id: c161ea21-96c4-456f-b42b-f920cb035486
Old-arch iOS CI now validates SPM mode (build, unit tests, Maestro
e2e), with a build-only fallback job covering the CocoaPods path.
Append validation log entry 7 (the DevSupport hermes fix landing green
first iteration, plus the old-arch Debug launch quirk that made the
unit-test job need an embedded JS bundle), move the old-arch coverage
gap out of Open items, mark the implementation brief completed, and
update the risk assessment: the mechanism is now CI-proven on old
arch.

Committed-By-Agent: claude
Orbit-Session-Id: c161ea21-96c4-456f-b42b-f920cb035486
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant