v6 Alpha.1 - #1225
v6 Alpha.1#1225descorp wants to merge 21 commits into
Conversation
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.16 to 3.3.18. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/3.3.18/CHANGELOG.md) - [Commits](ai/nanoid@3.3.16...3.3.18) --- updated-dependencies: - dependency-name: nanoid dependency-version: 3.3.18 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
…3.18 chore(deps): bump nanoid from 3.3.16 to 3.3.18
fix(deps): update dependency react-native-screens to v4.26.2
chore(deps): update yarn to v4.17.1
chore(deps): update dependency prettier to ^3.9.5
Rebuilds the bridge on the v6 core SDK: new Checkout/session lifecycle, presenter-free callback bridging via a single CallbackBridge, consolidated native module wrapper hierarchy, checkout.invalidate() for abandoned flows, and the AdyenContext to AdyenCheckout module rename. Updates docs and the architecture contract to match.
Fixes a crash navigating away from an embedded AdyenComponent, a setup/setupAdvanced native method name mismatch, ComponentModule initializing after the first AdyenComponent view could already mount, and missing BIN lookup/value events on iOS.
Consolidates AvailablePaymentComponent/PayableComponent into a single PaymentMethodComponent, adds PayPal and Klarna to Sessions Components and Advanced Checkout, adds testIDs for E2E, fixes the stale @react-native/typescript-config path, and navigates with the SDK-provided session result directly.
Appium tests that fill in a test card and complete a real payment through Sessions Components and Advanced Checkout, on both Android and iOS.
…n pod The umbrella Adyen pod merges every module into one, so canImport(AdyenCard)/canImport(AdyenComponents) are false and Card/Components payment methods fail to build. Vendors 9 separate xcframeworks built via scripts/build-xcframeworks.sh (run automatically by 'yarn app pod'; not committed). Requires use_frameworks! in the Podfile going forward, plus ~20 additional per-module imports throughout the bridge that the umbrella merge previously papered over. Also disables DropIn's delegate conformances (package-scoped symbols can't cross a precompiled xcframework boundary) and works around BeforeSubmitData's inaccessible initializer by mutating the SDK-provided instance in place.
Mounting <AdyenComponent type="applepay"> presented the system Apple Pay sheet immediately: its viewController is the PKPaymentAuthorizationViewController itself, not an inline button like Android's Google Pay. Renders a button that mounts the component only once tapped.
…eadless submit() checkout.submit(type) for headless payment methods (PayPal, Klarna) had no UI hosting the controller, so a resulting action (e.g. a redirect) had nowhere to render. Shows the action in a CheckoutFragment and reports a shopper cancellation if it's closed before completing.
adyen-react-native.podspec vendors per-module xcframeworks instead of the umbrella Adyen pod, built from a local adyen-ios checkout. CI needs the same thing: build_ios_frameworks.yml checks out adyen-ios at the pinned tag on a macOS runner and builds them, caching the result by version so most runs just restore a tarball instead of rebuilding. Wires it into pr_check.yml ahead of build-lib (needed there to package ios/frameworks/ into the tgz for e2e_test.yml, gated by build-tgz) and test-iOS (needed there for pod install against example/ios directly). Both restore the same cache key rather than rebuilding themselves; a cache miss fails loudly with a pointer to run the new workflow's workflow_dispatch trigger rather than silently shipping a broken build. Adds package.json's adyen.ios/adyen.android as the single source of truth for these pinned versions, instead of duplicating them across the podspec comment, three workflow files, and android/dependencies.gradle. Read via jq in workflows, node in scripts/ensure-xcframeworks.sh, and Groovy's built-in JsonSlurper in dependencies.gradle.
🟡 Public API changes detected🆕 Added (10)
❌ Removed (22)
✏️ Modified (7)
🆕 Methods added (3)
❌ Methods removed (1)
🆕 Parameters added (7)
❌ Parameters removed (2)
🆕 Enum cases added (1)
API snapshot diff--- /tmp/base-api-report.md 2026-09-22 13:23:30.381476936 +0000
+++ etc/api/adyen-react-native.api.md 2026-09-22 13:22:56.065561583 +0000
@@ -4,11 +4,7 @@
```ts
-import { JSX } from 'react';
import { default as React_2 } from 'react';
-import { ReactNode } from 'react';
-import { StyleProp } from 'react-native';
-import { ViewStyle } from 'react-native';
// @public
export interface ActionModule {
@@ -17,6 +13,17 @@
threeDS2SdkVersion: string;
}
+// @public
+export interface AdditionalDetailsResult {
+ // (undocumented)
+ readonly resultCode: string;
+}
+
+// @public
+export const AdditionalDetailsResult: {
+ readonly completed: (resultCode: string) => AdditionalDetailsResult;
+};
+
// @public (undocumented)
export const ADDRESS_COMPONENTS: string[];
@@ -39,42 +46,36 @@
export type AddressMode = 'full' | 'postalCode' | 'none' | 'lookup';
// @public
-export const AdyenAction: ActionModule;
+export interface AdvancedCallbacks {
+ onAdditionalDetails(data: PaymentDetailsData): Promise<AdditionalDetailsResult>;
+ onComplete(result: PaymentResult): void;
+ onError(error: AdyenError): void;
+ onSubmit(data: PaymentMethodData_2): Promise<SubmitResult>;
+}
// @public
-export interface AdyenActionComponent extends AdyenComponent {
- handle(action: PaymentAction): void;
+export interface AdvancedPayment {
+ action(action: PaymentAction): void;
+ completion(resultCode: string): void;
+ retry(message?: string): void;
}
// @public
-export const AdyenApplePay: ApplePayModule;
-
-// @public (undocumented)
-export const AdyenCheckout: React_2.FC<AdyenCheckoutProps>;
+export const AdyenAction: ActionModule;
// @public
-export interface AdyenCheckoutContextType {
- config: Configuration;
- isReady: boolean;
- paymentMethods?: PaymentMethodsResponse;
- start: (typeName: string) => void;
+export class AdyenCheckout {
+ static setup(session: SessionConfiguration, configuration: Configuration, callbacks: SessionCallbacks): Promise<Checkout>;
+ static setupAdvanced(paymentMethods: PaymentMethodsResponse, configuration: Configuration, callbacks: AdvancedCallbacks): Promise<Checkout>;
}
// @public
-export type AdyenCheckoutProps = {
- config: Configuration;
- paymentMethods?: PaymentMethodsResponse;
- session?: SessionConfiguration;
- onSubmit?: (data: PaymentMethodData_2, component: AdyenActionComponent, extra?: any) => void;
- onError: (error: AdyenError, component: AdyenComponent) => void;
- onAdditionalDetails?: (data: PaymentDetailsData, component: AdyenActionComponent) => void;
- onComplete?: (result: SessionsResult, component: AdyenComponent) => void;
- children: ReactNode;
-};
+export const AdyenComponent: React_2.FC<AdyenComponentProps>;
// @public
-export interface AdyenComponent {
- hide(success: boolean, option?: HideOption): void;
+export interface AdyenComponentProps {
+ checkout: Checkout;
+ type: string;
}
// @public
@@ -101,12 +102,6 @@
}
// @public
-export const AdyenGooglePay: GooglePayModule;
-
-// @public
-export const AdyenInstant: InstantModule;
-
-// @public
export interface AnalyticsOptions {
enabled?: boolean;
verboseLogs?: boolean;
@@ -123,58 +118,10 @@
// @public
export interface ApplePayAuthorizationResult {
- // (undocumented)
errors?: ApplePayError[];
- // (undocumented)
status: 'success' | 'failure';
}
-// @public (undocumented)
-export const ApplePayButton: (input: ApplePayButtonProps) => JSX.Element;
-
-// @public (undocumented)
-export interface ApplePayButtonProps {
- // (undocumented)
- onPress?: () => void;
- // (undocumented)
- radius?: number;
- // (undocumented)
- style?: StyleProp<ViewStyle>;
- // (undocumented)
- theme?: keyof typeof ApplePayButtonTheme;
- // (undocumented)
- type?: keyof typeof ApplePayButtonType;
-}
-
-// @public (undocumented)
-export const ApplePayButtonTheme: {
- readonly WHITE: 1;
- readonly WHITE_OUTLINE: 2;
- readonly AUTOMATIC: 3;
- readonly BLACK: 4;
-};
-
-// @public (undocumented)
-export const ApplePayButtonType: {
- readonly BUY: 1;
- readonly SETUP: 2;
- readonly INSTORE: 3;
- readonly DONATE: 4;
- readonly CHECKOUT: 5;
- readonly BOOK: 6;
- readonly SUBSCRIBE: 7;
- readonly RELOAD: 8;
- readonly ADDMONEY: 9;
- readonly TOPUP: 10;
- readonly ORDER: 11;
- readonly RENT: 12;
- readonly SUPPORT: 13;
- readonly CONTRIBUTE: 14;
- readonly TIP: 15;
- readonly CONTINUE: 16;
- readonly PLAIN: 0;
-};
-
// @public
export type ApplePayCalendarUnit = `year` | `month` | `day` | `hour` | `minute`;
@@ -183,7 +130,6 @@
allowOnboarding?: boolean;
billingContact?: ApplePayPaymentContact;
couponCode?: string;
- merchantCapabilities?: ApplePayMerchantCapability[];
merchantID: string;
merchantName?: string;
onAuthorize?: (payment: ApplePayPaymentAuthorization, actions: ApplePayAuthorizationActions) => void;
@@ -222,21 +168,6 @@
}
// @public
-export type ApplePayMerchantCapability = 'debit' | 'credit';
-
-// @public (undocumented)
-export interface ApplePayModule extends AdyenComponent, ConditionalPaymentComponent {
- // (undocumented)
- provideAuthorizationResult(result: ApplePayAuthorizationResult): void;
- // (undocumented)
- provideCouponCodeUpdate(update: ApplePayCouponCodeUpdateRequest): void;
- // (undocumented)
- provideShippingContactUpdate(update: ApplePayShippingContactUpdateRequest): void;
- // (undocumented)
- provideShippingMethodUpdate(update: ApplePayShippingMethodUpdateRequest): void;
-}
-
-// @public
export interface ApplePayPaymentAuthorization {
billingContact?: ApplePayPaymentContact;
shippingContact?: ApplePayPaymentContact;
@@ -328,6 +259,36 @@
locale?: string;
}
+// @public
+export interface BeforeSubmitData {
+ // (undocumented)
+ billingAddress?: object;
+ // (undocumented)
+ deliveryAddress?: object;
+ // (undocumented)
+ shopperEmail?: string;
+ // (undocumented)
+ shopperName?: {
+ firstName?: string;
+ lastName?: string;
+ };
+}
+
+// @public
+export type BeforeSubmitResult = {
+ readonly type: 'proceed';
+ readonly data: BeforeSubmitData;
+ readonly sessionData?: string;
+} | {
+ readonly type: 'abort';
+};
+
+// @public
+export const BeforeSubmitResult: {
+ readonly proceed: (data: BeforeSubmitData, sessionData?: string) => BeforeSubmitResult;
+ readonly abort: () => BeforeSubmitResult;
+};
+
// @public (undocumented)
export interface BinLookupData {
// (undocumented)
@@ -365,11 +326,13 @@
}
// @public
-export const CardView: React_2.FC<CardViewProps>;
-
-// @public (undocumented)
-export interface CardViewProps {
- paymentMethod?: PaymentMethod;
+export interface Checkout {
+ readonly configuration: Configuration;
+ invalidate(): void;
+ isAvailable(type: string): Promise<boolean>;
+ readonly paymentMethods: PaymentMethodsResponse;
+ requiresUserInteraction(type: string): Promise<boolean>;
+ submit(type: string): void;
}
// @public
@@ -401,9 +364,10 @@
}
// @public
-export interface DropInModule extends AdyenActionComponent {
+export interface DropInModule extends AdvancedPayment {
getReturnURL: () => Promise<string>;
providePaymentMethods(paymentMethods: PaymentMethodsResponse, order: Order | undefined): void;
+ start(checkout: Checkout): void;
}
// @public
@@ -437,6 +401,7 @@
onApplePayCouponCodeChange = "didUpdateCouponCodeCallback",
onApplePayShippingContactChange = "didUpdateShippingContactCallback",
onApplePayShippingMethodChange = "didUpdateShippingMethodCallback",
+ onBeforeSubmit = "didBeforeSubmitCallback",
onBinLookup = "didBinLookupCallback",
onBinValue = "didChangeBinValueCallback",
onCancelOrder = "didCancelOrderCallback",
@@ -464,41 +429,6 @@
}
// @public (undocumented)
-export const GooglePayButton: (input: GooglePayButtonProps) => JSX.Element;
-
-// @public (undocumented)
-export interface GooglePayButtonProps {
- // (undocumented)
- onPress?: () => void;
- // (undocumented)
- radius?: number;
- // (undocumented)
- style?: StyleProp<ViewStyle>;
- // (undocumented)
- theme?: keyof typeof GooglePayButtonTheme;
- // (undocumented)
- type?: keyof typeof GooglePayButtonType;
-}
-
-// @public (undocumented)
-export const GooglePayButtonTheme: {
- readonly DARK: 1;
- readonly LIGHT: 2;
-};
-
-// @public (undocumented)
-export const GooglePayButtonType: {
- readonly BUY: 1;
- readonly BOOK: 2;
- readonly CHECKOUT: 3;
- readonly DONATE: 4;
- readonly ORDER: 5;
- readonly PAY: 6;
- readonly SUBSCRIBE: 7;
- readonly PLAIN: 8;
-};
-
-// @public (undocumented)
export interface GooglePayConfiguration {
allowCreditCards?: boolean;
allowedAuthMethods?: CardAuthMethod[];
@@ -523,21 +453,12 @@
Test = 3
}
-// @public (undocumented)
-export interface GooglePayModule extends ConditionalPaymentComponent, AdyenActionComponent {
-}
-
// @public
export interface GooglePayShippingAddressParameters {
allowedCountryCodes?: string[];
phoneNumberRequired?: boolean;
}
-// @public
-export interface HideOption {
- message?: string;
-}
-
// @public (undocumented)
export interface InstallmentOption {
// (undocumented)
@@ -556,10 +477,6 @@
export type InstallmentPlan = 'regular' | 'revolving';
// @public (undocumented)
-export interface InstantModule extends AdyenActionComponent {
-}
-
-// @public (undocumented)
export const NATIVE_COMPONENTS: string[];
// @public (undocumented)
@@ -570,7 +487,7 @@
}
// @public (undocumented)
-export interface PartialPaymentComponent extends AdyenComponent {
+export interface PartialPaymentComponent extends AdvancedPayment {
// (undocumented)
provideBalance(success: boolean, balance: Balance | undefined, error: Error | undefined): void;
// (undocumented)
@@ -685,6 +602,15 @@
storedPaymentMethods?: StoredPaymentMethod[];
}
+// @public
+export interface PaymentResult {
+ // (undocumented)
+ [key: string]: unknown;
+ action?: PaymentAction;
+ refusalReason?: string;
+ resultCode?: ResultCode;
+}
+
// @public (undocumented)
export interface PostalAddress {
city?: string;
@@ -712,6 +638,13 @@
}
// @public
+export interface SessionCallbacks {
+ onBeforeSubmit?(data: BeforeSubmitData): Promise<BeforeSubmitResult>;
+ onComplete(result: SessionsResult): void;
+ onError(error: AdyenError): void;
+}
+
+// @public
export interface SessionConfiguration {
// (undocumented)
id: string;
@@ -741,6 +674,25 @@
paymentData: PaymentMethodData_2;
}
+// @public
+export type SubmitResult = {
+ readonly type: 'action';
+ readonly action: PaymentAction;
+} | {
+ readonly type: 'completed';
+ readonly resultCode: string;
+} | {
+ readonly type: 'retry';
+ readonly message?: string;
+};
+
+// @public
+export const SubmitResult: {
+ readonly action: (action: PaymentAction) => SubmitResult;
+ readonly completed: (resultCode: string) => SubmitResult;
+ readonly retry: (message?: string) => SubmitResult;
+};
+
// @public (undocumented)
export interface ThreeDSConfiguration {
requestorAppUrl?: string;
@@ -752,9 +704,6 @@
// @public (undocumented)
export const UNSUPPORTED_PAYMENT_METHODS: string[];
-// @public
-export const useAdyenCheckout: () => AdyenCheckoutContextType;
-
// (No @packageDocumentation comment for this package)
|
There was a problem hiding this comment.
Code Review
This pull request migrates the React Native SDK to Adyen iOS and Android SDKs v6.0.0-alpha.1, introducing a static AdyenCheckout API, return-based callbacks, and a generic <AdyenComponent> view to replace per-method view managers. The review feedback highlights critical areas for improvement, including memory leaks in AdyenComponentViewManager and CheckoutFragment due to missing cleanup on view unmount or fragment dismissal. Additionally, potential runtime crashes were identified from unsafe non-null assertions in CardConfigurationParser and direct appCompatActivity access in ContextModule. Finally, adding cancellation handlers to the suspended continuations in ComponentManager is recommended to prevent illegal state exceptions.
| public override fun createViewInstance(context: ThemedReactContext): DynamicComponentView { | ||
| val view = DynamicComponentView(context) | ||
| val state = CardViewState(context, messageBusEmitter) | ||
| val state = AdyenComponentViewState(context, messageBusEmitter) | ||
| view.layoutListener = state | ||
| viewStates[view] = state | ||
| return view |
There was a problem hiding this comment.
The AdyenComponentViewManager maintains a viewStates map to track the state of each DynamicComponentView. However, it does not override onDropViewInstance to clean up these entries when a view is unmounted and destroyed. This leads to a memory leak where both DynamicComponentView and AdyenComponentViewState instances are retained indefinitely. Additionally, the associated native registries (ComponentModule and ContextModule) are never unregistered, which can cause stale state issues.
| public override fun createViewInstance(context: ThemedReactContext): DynamicComponentView { | |
| val view = DynamicComponentView(context) | |
| val state = CardViewState(context, messageBusEmitter) | |
| val state = AdyenComponentViewState(context, messageBusEmitter) | |
| view.layoutListener = state | |
| viewStates[view] = state | |
| return view | |
| public override fun createViewInstance(context: ThemedReactContext): DynamicComponentView { | |
| val view = DynamicComponentView(context) | |
| val state = AdyenComponentViewState(context, messageBusEmitter) | |
| view.layoutListener = state | |
| viewStates[view] = state | |
| return view | |
| } | |
| override fun onDropViewInstance(view: DynamicComponentView) { | |
| super.onDropViewInstance(view) | |
| viewStates.remove(view)?.dispose(view) | |
| } |
| onSubmit = { data -> | ||
| suspendCancellableCoroutine { continuation -> | ||
| submitContinuation = continuation | ||
| messageBus.onSubmit(data) | ||
| } | ||
| }, | ||
| onAdditionalDetails = { data -> | ||
| suspendCancellableCoroutine { continuation -> | ||
| additionalDetailsContinuation = continuation | ||
| messageBus.onAdditionalDetails(data) | ||
| } | ||
| }, |
There was a problem hiding this comment.
The CancellableContinuation instances (submitContinuation and additionalDetailsContinuation) are stored as properties on ComponentManager. If the coroutine scope is cancelled, these continuations are cancelled but the references are not cleared. Trying to resume them later in dispose() or retry() will throw an IllegalStateException. Registering a cancellation handler using invokeOnCancellation to null out the references when the coroutine is cancelled prevents this.
onSubmit = { data ->
suspendCancellableCoroutine { continuation ->
submitContinuation = continuation
continuation.invokeOnCancellation {
if (submitContinuation == continuation) {
submitContinuation = null
}
}
messageBus.onSubmit(data)
}
},
onAdditionalDetails = { data ->
suspendCancellableCoroutine { continuation ->
additionalDetailsContinuation = continuation
continuation.invokeOnCancellation {
if (additionalDetailsContinuation == continuation) {
additionalDetailsContinuation = null
}
}
messageBus.onAdditionalDetails(data)
}
},| override fun onCancel(dialog: DialogInterface) { | ||
| super.onCancel(dialog) | ||
| val fragmentTag = tag ?: return | ||
| configs[fragmentTag]?.onCancelled?.invoke() | ||
| } |
There was a problem hiding this comment.
The configs map in CheckoutFragment stores the configuration for each fragment tag. When the fragment is cancelled or dismissed by the user, the entry is never removed from configs. This leads to a memory leak because the FragmentConfig captures references to controllerProvider and onCancelled lambdas. Overriding onDismiss to remove the tag from configs ensures proper cleanup.
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
val fragmentTag = tag ?: return
configs[fragmentTag]?.onCancelled?.invoke()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
tag?.let { configs.remove(it) }
}| internal val billingAddressMode: BillingAddressMode? | ||
| get() { | ||
| return when { | ||
| config.hasKey(ADDRESS_VISIBILITY_KEY) -> { | ||
| val value = config.getString(ADDRESS_VISIBILITY_KEY)!! | ||
| when (value.lowercase()) { | ||
| "postal_code", "postal", "postalcode" -> PostalCode() | ||
| "full" -> FullAddress(countryCode, supportedCountries.orEmpty()) | ||
| "lookup" -> AddressConfiguration.Lookup() | ||
| else -> None | ||
| "postal_code", "postal", "postalcode" -> BillingAddressMode.PostalCode() | ||
|
|
||
| "none" -> BillingAddressMode.None() | ||
|
|
||
| // TODO: v6 migration - "lookup" and "full" billing address modes not yet available | ||
| else -> null | ||
| } | ||
| } |
There was a problem hiding this comment.
Using the non-null assertion operator !! on config.getString(ADDRESS_VISIBILITY_KEY) is unsafe. In React Native, a key can exist in the ReadableMap but have a null value (i.e., config.isNull(key) is true). If the property is passed as null from JavaScript, this will throw a NullPointerException and crash the app. Use a safe call or retrieve the value defensively to prevent crashes.
internal val billingAddressMode: BillingAddressMode?
get() {
val value = if (config.hasKey(ADDRESS_VISIBILITY_KEY)) config.getString(ADDRESS_VISIBILITY_KEY) else null
return when (value?.lowercase()) {
"postal_code", "postal", "postalcode" -> BillingAddressMode.PostalCode()
"none" -> BillingAddressMode.None()
else -> null
}
}| fun setup( | ||
| sessionModelJSON: ReadableMap, | ||
| configurationJSON: ReadableMap, | ||
| promise: Promise, | ||
| ) { | ||
| appCompatActivity.lifecycleScope.launch { | ||
| setupSessionAsync(sessionModelJSON, configurationJSON, promise) | ||
| } | ||
| } |
There was a problem hiding this comment.
Accessing appCompatActivity directly inside the @ReactMethod entry points (setup and setupAdvanced) is unsafe. If the React Native application context's current activity is null (e.g., during background transitions or early initialization), appCompatActivity will throw an IllegalStateException and crash the app. It is safer to check if the activity is available first and reject the promise gracefully.
fun setup(
sessionModelJSON: ReadableMap,
configurationJSON: ReadableMap,
promise: Promise,
) {
val activity = reactApplicationContext.currentActivity as? AppCompatActivity
if (activity == null) {
promise.reject(ModuleException.NoActivity())
return
}
activity.lifecycleScope.launch {
setupSessionAsync(sessionModelJSON, configurationJSON, promise)
}
}…meworks @testable import Adyen never works against BUILD_LIBRARY_FOR_DISTRIBUTION=YES: that flag only emits a textual .swiftinterface, and @testable needs the binary .swiftmodule format. Confirmed empirically - adding SWIFT_ENABLE_TESTABILITY alongside it still fails; only dropping it produces a real .swiftmodule. build-xcframeworks.sh gains a TESTABLE=1 mode (no library evolution) for AdyenExampleTests to consume - mutually exclusive with the distribution build per xcframework. build_ios_frameworks.yml builds both via a two-leg matrix, sharing one resolve-package-graph job (cached by Package.swift's hash, since deps change less often than adyen-ios itself) via -clonedSourcePackagesDirPath. Each variant is still cached and consumed independently by its callers. Also fixes EncodablePaymentComponentDataTests.swift: missing AdyenCard/AdyenComponents/AdyenEncryption imports (now separate xcframeworks, not one umbrella module), and a stale assertion expecting checkoutAttemptId - production code stopped emitting it earlier this migration but the test was never updated.
|


Summary
Complete public API redesign for the v6 alpha release of
adyen-react-native. Rewrites the iOS/Android/TS bridge on top of Adyen's v6 core SDKs, replaces the React provider pattern with a staticAdyenCheckoutAPI, aligns callback signatures with the native iOS/Android SDKs, and adds the example app + CI infra needed to build and test it end to end.Changes
Bridge rewrite (v6 core SDK)
Checkout/session lifecycle: presenter-free callback bridging via a singleCallbackBridge, consolidated native module wrapper hierarchy,checkout.invalidate()for abandoned flowsAdyenContext→AdyenCheckout,SetupModule→ContextModule,EmbeddedComponentBusModule→ComponentModule,EmbeddedComponentDelegateProxy→ComponentProxycreateSession→setup,setup→setupAdvanced,open→startCardView,GooglePayButton,ApplePayButton) with a generic<AdyenComponent>GooglePayModule,ApplePayModule,InstantModule,struct Payment(iOS)Static API
<AdyenCheckout>provider,useAdyenCheckouthook, and React contextAdyenCheckout.setup()/setupAdvanced()returning aCheckoutobject;<AdyenComponent>reads from thecheckoutprop directlyNative alignment
CheckoutState(both platforms);isSessionauto-calculated fromCheckoutContexttypecleanup()terminal-only, called only fromonComplete/onErrorcurrentModuledispatch — DropIn routes directly from TSSessionErrorDelegateprotocol (iOS)Return-based callbacks
onSubmitreturnsPromise<SubmitResult>instead of receiving a handler param;onAdditionalDetailsreturnsPromise<AdditionalDetailsResult>;onBeforeSubmitreturnsPromise<BeforeSubmitResult>SubmitResult.action()/.completed()/.retry()DropIn simplification
PaymentComponentWrapper,AddressLookupModule)open()→start()name mismatch between TS and native modulesPost-migration fixes
<AdyenComponent>, asetup/setupAdvancednative method name mismatch,ComponentModuleinitializing after the first view could already mount, and missing BIN lookup/value events on iOSAdyenpod, since the umbrella merges every module together and breakscanImport(AdyenCard)/canImport(AdyenComponents)checks for Card/Components payment methods. Requiresuse_frameworks!going forward, ~20 additional per-module imports, and working aroundBeforeSubmitData's inaccessible initializer by mutating the SDK-provided instance in placecheckout.submit(type)(PayPal, Klarna) now presents any resulting action (e.g. a redirect) in aCheckoutFragmentinstead of having nowhere to render it, and reports a shopper cancellation if it's closed before completing<AdyenComponent>mount behind a tap in the example app — itsviewControlleris thePKPaymentAuthorizationViewControlleritself, so mounting it immediately presented the system sheet, unlike Google Pay's inline buttonExample app & E2E
AvailablePaymentComponent/PayableComponentinto a singlePaymentMethodComponentprocessErrorutility; renameprocessResult→navigateToResultsCI
build_ios_frameworks.ymlchecks outadyen-iosat the pinned tag on a macOS runner, builds the per-module xcframeworks, and caches the result by version so most runs restore a tarball instead of rebuildingpr_check.ymlfor bothbuild-lib(packagingios/frameworks/fore2e_test.yml) andtest-iOS(pod installagainstexample/ios); a cache miss fails loudly with a pointer to the new workflow'sworkflow_dispatchtrigger rather than silently shipping a broken buildpackage.json'sadyen.ios/adyen.androidis now the single source of truth for pinned SDK versions, read viajqin workflows, Node inscripts/ensure-xcframeworks.sh, and Groovy'sJsonSlurperinandroid/dependencies.gradleDocs
docs/Architecture.md, adddocs/native-architecture.md,docs/js-architecture.md,docs/public-api-flows.md,docs/v6-api-migration-summary.md,docs/v6-public-api-proposal.md,docs/consumer-migration-guide.md,docs/android-bridge-migration-guide.md,docs/ios-bridge-migration-guide.mdTesting
yarn lintandyarn typecheckcleanBaseModuleTests,ThreadingSafetyTests,ComponentModuleTests, and others) for the v6 module/delegate changes