Skip to content

Commit 6d8a37a

Browse files
committed
chore: Revert Session integration changes; scope PR to advanced flow only
Session+PaymentComponentDelegate.swift and Session+ActionComponentDelegate.swift are restored to their pre-refactor shape (matching develop byte-for-byte). The stage-enum migration in Session is deferred to a follow-up PR per PR self-review comments SR1/SR2; Session continues to use its own CheckoutPaymentsResponse-based internal logic. Also: - Remove the 7 SessionTests mapper tests that exercised the reverted mapToSubmitResult / mapToAdditionalDetailsResult. - Resolve M1 via Option 1 (no-op, matching develop): delete the orphaned test_didComplete_emitsFinishedWithEmptyResultCode_callsOnComplete test and its missingResultCode sentinel. didComplete(from:) stays as the pre-existing TODO; action-UI-only onComplete firing is deferred to a follow-up PR with a proper SDK-supplied resultCode source. Regression R1 (refused-partial-payment alert removal) is reverted along with the rest of Session. The refused alert is back until Session-side alignment lands in a follow-up.
1 parent 0959bf8 commit 6d8a37a

4 files changed

Lines changed: 78 additions & 303 deletions

File tree

AdyenSession/Session+ActionComponentDelegate.swift

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -65,64 +65,12 @@ extension Session {
6565
)
6666
Task { [weak self] in
6767
guard let self else { return }
68-
let additionalDetailsResult: AdditionalDetailsResult
6968
do {
7069
let response: PaymentsResponse = try await apiClient.performAsync(request)
71-
additionalDetailsResult = mapToAdditionalDetailsResult(response)
70+
handle(paymentResponse: response, for: component, in: dropInComponent)
7271
} catch {
73-
additionalDetailsResult = .error(error)
72+
finish(with: error, component: component)
7473
}
75-
handle(additionalDetailsResult: additionalDetailsResult, for: component)
7674
}
7775
}
78-
79-
/// Routes an `AdditionalDetailsResult` to the appropriate Session internal helper.
80-
///
81-
/// The enum does not carry `CheckoutResultCode` or `sessionResult`; those are read from
82-
/// `state`, which `SessionAPIClient` updates automatically on every response before the
83-
/// mapper runs (see `SessionResultAware`).
84-
@MainActor
85-
private func handle(
86-
additionalDetailsResult: AdditionalDetailsResult,
87-
for currentComponent: Component
88-
) {
89-
switch additionalDetailsResult {
90-
case let .finished(resultCode):
91-
let result = CheckoutResult(
92-
resultCode: CheckoutResultCode(rawValue: resultCode),
93-
sessionResult: state.sessionResult
94-
)
95-
finish(with: result, component: currentComponent)
96-
97-
case let .error(error):
98-
finish(with: error, component: currentComponent)
99-
100-
@unknown default:
101-
AdyenAssertion.assertionFailure(
102-
message: "Unhandled AdditionalDetailsResult branch in Session; ignored."
103-
)
104-
}
105-
}
106-
107-
/// Pure mapping from a `/payments/details` response to an `AdditionalDetailsResult`.
108-
///
109-
/// Only `.finished(resultCode:)` is produced. The simplified details stage has no `.action`
110-
/// or `.partialPayment` branches (see plan Open Question #3 — the action-after-details loop
111-
/// is intentionally removed on v6). HTTP errors are folded at the Task catch site.
112-
///
113-
/// ⚠️ BEHAVIOR REGRESSION safeguard (v6, Behavioral Regression R2):
114-
/// Pre-v6 Session would recursively trigger action handling when a `/payments/details`
115-
/// response carried an `action`. v6 drops that path by design, but to keep QA loud about
116-
/// any backend flow that still emits an action on `/payments/details`, we hit an
117-
/// `assertionFailure` in debug so the silent drop is visible. Release builds still map
118-
/// to `.finished(...)` — the action is dropped as the plan specifies.
119-
internal func mapToAdditionalDetailsResult(_ response: PaymentsResponse) -> AdditionalDetailsResult {
120-
if response.action != nil {
121-
AdyenAssertion.assertionFailure(
122-
message: "Unexpected action on /payments/details response; v6 drops this branch. " +
123-
"See Behavioral Regression R2 in IOS_ADVANCED_FLOW_CALLBACK_ALIGNMENT_PLAN_2026-04-16.md."
124-
)
125-
}
126-
return .finished(resultCode: response.resultCode.rawValue)
127-
}
12876
}

AdyenSession/Session+PaymentComponentDelegate.swift

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -67,83 +67,49 @@ extension Session {
6767
)
6868
Task { [weak self] in
6969
guard let self else { return }
70-
let submitResult: SubmitResult
7170
do {
7271
let response: PaymentsResponse = try await apiClient.performAsync(request)
73-
submitResult = mapToSubmitResult(response)
72+
handle(paymentResponse: response, for: component, in: dropInComponent)
7473
} catch {
75-
submitResult = .error(error)
74+
finish(with: error, component: component)
7675
}
77-
handle(submitResult: submitResult, for: component, in: dropInComponent)
7876
}
7977
}
80-
78+
8179
@MainActor
82-
private func handle(
83-
submitResult: SubmitResult,
80+
internal func handle(
81+
paymentResponse response: PaymentsResponse,
8482
for currentComponent: Component,
8583
in dropInComponent: AnyDropInComponent?
8684
) {
87-
switch submitResult {
88-
case let .action(action):
85+
if let action = response.action {
8986
handle(action: action, for: currentComponent, in: dropInComponent)
87+
} else if let order = response.order,
88+
let remainingAmount = order.remainingAmount,
89+
remainingAmount.value > 0 {
9090

91-
case let .partialPayment(payload):
9291
guard let dropInComponent else {
9392
finish(
9493
with: PartialPaymentError.notSupportedForComponent,
9594
component: currentComponent
9695
)
9796
return
9897
}
99-
// ⚠️ BEHAVIOR REGRESSION R1 (v6, intentional — Android parity).
100-
// Pre-v6 Session showed a "Payment refused" UIAlertController here whenever the
101-
// `/payments` response had `resultCode == .refused` and a non-zero
102-
// `order.remainingAmount` (e.g. gift card declined mid-flow). That alert is now
103-
// removed to match Android's `SessionInteractor`, which routes the same condition
104-
// (`RefusedPartialPayment`) through its `onFinished(...)` callback without
105-
// showing UI.
106-
//
107-
// Shoppers no longer receive a Session-level "Payment refused" notification on
108-
// gift-card decline; the drop-in simply reloads with updated payment methods.
109-
// If UX wants the alert back, it should live in the drop-in layer (not Session).
110-
updateDropIn(
111-
dropInComponent,
112-
with: payload.order,
113-
currentComponent: currentComponent
98+
99+
handle(
100+
order: order,
101+
resultCode: response.resultCode,
102+
currentComponent: currentComponent,
103+
dropInComponent: dropInComponent
114104
)
115105

116-
case let .finished(resultCode):
106+
} else {
117107
let result = CheckoutResult(
118-
resultCode: CheckoutResultCode(rawValue: resultCode),
119-
sessionResult: state.sessionResult
108+
resultCode: response.resultCode,
109+
sessionResult: response.sessionResult
120110
)
121111
finish(with: result, component: currentComponent)
122-
123-
case let .error(error):
124-
finish(with: error, component: currentComponent)
125-
126-
@unknown default:
127-
AdyenAssertion.assertionFailure(
128-
message: "Unhandled SubmitResult branch in Session; ignored."
129-
)
130-
}
131-
}
132-
133-
/// Pure mapping from a `/payments` response to a `SubmitResult`.
134-
/// The mapper is intentionally pure; HTTP / merchant errors are folded at the Task catch site.
135-
/// `paymentMethodsUpdate` is emitted as `nil` — it is produced later by the session reload and
136-
/// read by consumers from `state.paymentMethods` at that point.
137-
internal func mapToSubmitResult(_ response: PaymentsResponse) -> SubmitResult {
138-
if let action = response.action {
139-
return .action(action)
140112
}
141-
if let order = response.order,
142-
let remainingAmount = order.remainingAmount,
143-
remainingAmount.value > 0 {
144-
return .partialPayment(PartialPayment(order: order, paymentMethodsUpdate: nil))
145-
}
146-
return .finished(resultCode: response.resultCode.rawValue)
147113
}
148114

149115
@MainActor
@@ -159,6 +125,46 @@ extension Session {
159125
}
160126
}
161127

128+
@MainActor
129+
private func handle(
130+
order: PartialPaymentOrder,
131+
resultCode: CheckoutResultCode,
132+
currentComponent: Component,
133+
dropInComponent: AnyDropInComponent
134+
) {
135+
let updateDropInBlock: (() -> Void) = { [weak self] in
136+
self?.updateDropIn(dropInComponent, with: order, currentComponent: currentComponent)
137+
}
138+
139+
// dropIn needs to be updated in both cases
140+
if resultCode == .refused {
141+
showPaymentFailedAlert(on: dropInComponent, completion: updateDropInBlock)
142+
} else {
143+
updateDropInBlock()
144+
}
145+
}
146+
147+
@MainActor
148+
private func showPaymentFailedAlert(on dropInComponent: AnyDropInComponent, completion: @escaping (() -> Void)) {
149+
let localizationParameters = (dropInComponent as? Localizable)?.localizationParameters
150+
let title = localizedString(.errorTitle, localizationParameters)
151+
let message = localizedString(.paymentRefusedMessage, localizationParameters)
152+
153+
let alertController = UIAlertController(
154+
title: title,
155+
message: message,
156+
preferredStyle: .alert
157+
)
158+
159+
let doneTitle = localizedString(.dismissButton, localizationParameters)
160+
let doneAction = UIAlertAction(title: doneTitle, style: .default) { _ in
161+
completion()
162+
}
163+
alertController.addAction(doneAction)
164+
165+
dropInComponent.viewController.present(alertController, animated: true)
166+
}
167+
162168
@MainActor
163169
private func updateDropIn(_ dropInComponent: AnyDropInComponent, with order: PartialPaymentOrder, currentComponent: Component) {
164170
let initialInfo = SessionResponse(

0 commit comments

Comments
 (0)