Skip to content

feature/INT-1675 - BACS Direct Debit notifications + refactor - #659

Open
david-ruiz-cko wants to merge 6 commits into
masterfrom
feature/INT-1675
Open

feature/INT-1675 - BACS Direct Debit notifications + refactor#659
david-ruiz-cko wants to merge 6 commits into
masterfrom
feature/INT-1675

Conversation

@david-ruiz-cko

@david-ruiz-cko david-ruiz-cko commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Breaking changes (check at the end image)

This pull request introduces Bacs Direct Debit support to the SDK, enabling sending pre-notification (advance notice) events and full instrument lifecycle management for Bacs payment methods. The changes include new client interfaces and implementations, updates to serialization logic, and enhancements to the API surface. The most important changes are grouped below.

Bacs Direct Debit Client and API Enhancements:

  • Added a new BacsClient interface and its implementation BacsClientImpl to support sending Bacs Direct Debit pre-notification events, with both asynchronous and synchronous methods. This client is configured to use secret key authorization only. [1] [2]
  • Integrated the new BacsClient into the main API (AbstractCheckoutApmApi and CheckoutApmApi), making it accessible as bacsClient() alongside other APM clients. [1] [2] [3] [4]

Serialization and Instrument Support:

  • Updated the GsonSerializer to handle new Bacs-related classes, including request and response types for Bacs sources and instruments, ensuring correct (de)serialization of Bacs payment flows. This includes registering new instrument response subtypes for Bacs and updating the runtime type adapter factories to support polymorphic deserialization. [1] [2] [3] [4]

Documentation:

  • Added comprehensive documentation to README.md demonstrating how to use the new Bacs Direct Debit features, including sending pre-notifications, creating, updating, and retrieving Bacs instruments, and taking payments using stored Bacs instruments.

Breaking changes imageimageimage

Property moved off a base class

GetInstrumentResponse.accountHolder removed from the base, now on each concrete variant. Forced by Gson: two fields mapping to the same JSON name throw at runtime, and the spec gives all five retrieve variants a different account-holder shape. Base-typed getAccountHolder() no longer compiles and needs a cast to the concrete variant.

Changed field types

instruments.create.InstrumentData.paymentType: payments.PaymentType → SepaPaymentType. The old enum serialized "Recurring"/"Regular"; StoreSepaInstrumentRequest pins lowercase recurring/regular, so SEPA store could not send a valid value. Callers passing PaymentType.RECURRING must switch enum — though no such call can currently succeed against the API.
GetSepaInstrumentResponse.instrumentData: InstrumentData → GetSepaInstrumentData, which adds the mandate type (Core/B2B) the old type omitted.

Wire value changed

UpdateInstrumentBankAccountRequest sent "type":"token", now sends "type":"bank_account". token is absent from the update discriminator mapping entirely, so PATCH /instruments/{id} could not be validated as a bank-account update. No test referenced the class before this PR.

Deserialization behaviour changed (throw → object)

type: "bacs" and type: "ach" on the create and get instrument responses previously threw JsonParseException, because the three RuntimeTypeAdapterFactory registrations have no default subtype. They now deserialize to the concrete type. Anyone catching that exception as control flow is affected. The factories remain strict for unmodelled types — deliberately, per decision A2.

Deprecated, not removed (non-breaking)

PaymentSourceType.SEPA — zero callers; use ID for the previous platform, SEPAV4 for the current one
CreateInstrumentResponse.customer — moves to the token and bank_account variants in the next major
Correction to something I told you earlier
previous.RequestSepaSource switching from PaymentSourceType.SEPA to PaymentSourceType.ID is wire-neutral, not a breaking change. Both constants already carried @SerializedName("id") on master, so the bytes are identical. I listed it as a wire change twice, including in the ledger — now fixed there too.

@david-ruiz-cko
david-ruiz-cko requested a review from a team August 27, 2026 16:22
@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:1879>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 62


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope1879>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown

🟠 Advisory review: Concerns worth a look

This PR needs a human approval. Before you give it, these are the things I'd want resolved.

This PR adds Bacs Direct Debit notification support and a significant instrument/serialization refactor. The code is generally well-structured, but there are concrete issues in the GsonSerializer change and a potential null-safety regression in CreateCustomerInstrumentRequest.

Concerns

  • In GsonSerializer, four source subtypes are switched from identifier() to serializedName() (PAYMENT_GET_RESPONSE_GIROPAY_SOURCE, PAYMENT_GET_RESPONSE_KLARNA_SOURCE, PAYMENT_GET_RESPONSE_SEPAV4_SOURCE, PAYMENT_RESPONSE_SOURCE) — this is a behaviour change that needs verification; if identifier() and serializedName() produce different strings for these enum values, deserialization of those response sources silently breaks for existing callers.
  • CreateCustomerInstrumentRequest.defaultInstrument changed from primitive boolean to boxed Boolean, and a deprecated isDefaultInstrument() method returning Boolean was added — any existing callers relying on the Lombok-generated isDefaultInstrument() returning primitive boolean will now get a Boolean that can be null, potentially causing NPE on unbox (e.g. in an if statement).
  • The AbstractSource RuntimeTypeAdapterFactory now passes true for the maintainType flag, which is a new behaviour change not explained in the PR description — this needs confirmation that it is intentional and won't break serialization of existing source types that did not previously include a type field in their JSON output.
  • BacsClientImpl is constructed using SdkAuthorizationType.SECRET_KEY which intentionally excludes OAuth; however, AbstractCheckoutApmApi is also the base for OAuth-configured clients — calling bacsClient() on an OAuth-only configured SDK will fail at request time rather than at construction, with no guard or early error to guide callers.
  • The diff is explicitly truncated (58 files omitted), so the GsonSerializer registrations for the new Bacs/ACH instrument response subtypes, the GetBacsInstrumentResponse class, UpdateInstrumentBankAccountRequest wire-value fix, and any integration tests cannot be reviewed — correctness of these claimed changes cannot be confirmed from the diff alone.
  • InstrumentData.paymentType type changed from PaymentType to SepaPaymentType — this is a confirmed breaking change for any caller passing PaymentType.RECURRING; the PR acknowledges it but there is no migration aid (e.g. a deprecated overload) provided.
  • The CreateInstrumentResponse.customer field was removed from the base class, but only CreateInstrumentTokenResponse and CreateInstrumentBankAccountResponse add it back — any caller reading customer from a base-typed CreateInstrumentResponse reference will get a compile error, which is a silent breaking change for users not on the concrete type.

⚠️ The diff was too large to read in full, so this review covers only part of the change.


This is not an approval. wall-e cannot auto-approve this PR — it is an opinion to help whoever does. Advisory review · us.anthropic.claude-sonnet-4-6 · wall-e 2026.06.19-02

Comment thread src/main/java/com/checkout/apm/bacs/BacsNotificationResponse.java Dismissed
Comment thread src/main/java/com/checkout/instruments/create/CreateInstrumentAchRequest.java Dismissed
Comment thread src/main/java/com/checkout/instruments/create/CreateInstrumentAchResponse.java Dismissed
Comment thread src/main/java/com/checkout/instruments/create/CreateInstrumentBacsRequest.java Dismissed
Comment thread src/main/java/com/checkout/instruments/create/CreateInstrumentBacsResponse.java Dismissed
Comment thread src/main/java/com/checkout/instruments/get/GetBacsInstrumentResponse.java Dismissed
Comment thread src/main/java/com/checkout/instruments/update/UpdateInstrumentBacsRequest.java Dismissed
Comment thread src/main/java/com/checkout/instruments/update/UpdateInstrumentBacsResponse.java Dismissed
Comment thread src/main/java/com/checkout/payments/request/source/apm/RequestBacsSource.java Dismissed
Comment thread src/main/java/com/checkout/payments/response/source/BacsResponseSource.java Dismissed
@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:2153>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 79


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope2153>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:2327>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 92


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope2327>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:2890>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 100


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope2890>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:2898>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 100


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 28, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope2898>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@david-ruiz-cko david-ruiz-cko changed the title BACS Direct Debit notifications initial dev + tests feature INT-1675 - BACS Direct Debit notifications + refactor Aug 28, 2026
@david-ruiz-cko david-ruiz-cko changed the title feature INT-1675 - BACS Direct Debit notifications + refactor feature/INT-1675 - BACS Direct Debit notifications + refactor Aug 28, 2026
@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:2964>200

Operational gates

  • ✅ jira_ticket (INT-1675)
  • ✅ independent_review

Files analysed: 100


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope2964>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@sonarqubecloud

Copy link
Copy Markdown

assertEquals("customer@example.com", result.getCustomer().getEmail());
assertEquals("John Smith", result.getCustomer().getName());
assertEquals("cus_udst2tfldj6upmye2reztkmm4i", result.getCustomer().getId());
assertTrue(result.getCustomer().isDefaultInstrument());
assertEquals("customer@example.com", result.getCustomer().getEmail());
assertEquals("John Smith", result.getCustomer().getName());
assertEquals("cus_udst2tfldj6upmye2reztkmm4i", result.getCustomer().getId());
assertTrue(result.getCustomer().isDefaultInstrument());
Comment on lines +97 to +99
final String json = serializer.toJson(request);
final CreateInstrumentSepaRequest result =
serializer.fromJson(json, CreateInstrumentSepaRequest.class);
Comment on lines 116 to 117
@@ -140,7 +197,7 @@ void shouldRoundTripSerialize() {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants