Skip to content

feat(setups): add missing payment methods, billing and payment type - #398

Open
rodrigojara-cko wants to merge 8 commits into
masterfrom
feat/INT-1738-payment-setup-gaps
Open

rodrigojara-cko wants to merge 8 commits into
masterfrom
feat/INT-1738-payment-setup-gaps

Conversation

@rodrigojara-cko

@rodrigojara-cko rodrigojara-cko commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Payment Setup now models the 29 payment methods of the spec that the SDK was missing and the billing of the request. payment_type defaults to regular with a constants class for the six spec values (Payments\PaymentType has no pay_later, and PayLater is rejected with 422). Members the spec does not have are marked deprecated.

Changes

  • Setups/Common/PaymentMethods/PaymentMethods.php: paynow, qpay, eps, ideal, knet, bancontact, benefit, blik, vipps, twint, alipay_cn, alipay_hk, gcash, tng, dana, mobilepay, tamara, mbway, multibanco, wechatpay, kakaopay, truemoney, octopus, p24, alma, swish, sequra, ach, sepa; docs completed against the spec.
  • New method classes, one per method with its spec fields: PayNow, Qpay, Eps, Ideal, Knet, Bancontact, Benefit, Blik, Vipps, Twint, AlipayCn, AlipayHk, GCash, Tng, Dana, MobilePay, Tamara, MbWay, Multibanco, WeChatPay, KakaoPay, TrueMoney, Octopus, P24, Alma, Swish, Sequra, Ach, Sepa, each under Setups/Common/PaymentMethods/<Method>/.
  • New nested classes: AchAccountHolder, AchAccountHolderIdentification, P24AccountHolder, SwishAccountHolder, SepaAccountHolder, SepaMandate, TamaraRiskAssessment.
  • New constants classes: KnetLanguage, AchAccountHolderType, PaymentSetupAchAccountType, SepaAccountHolderType, PaymentSetupSepaMandateType, Common/PaymentSetupTerminalType, Common/PaymentSetupOsType.
  • Setups/Common/PaymentType/PaymentSetupPaymentType.php (new) and Setups/Request/PaymentSetupRequest.php: $payment_type default "regular", new $billing.
  • Setups/Common/Billing/PaymentSetupBilling.php (new).
  • Ideal/Ideal.php, Knet/Knet.php, Bancontact/Bancontact.php: the description, language and account_holder_name docs say that the specification marks them optional but the API keeps the method invalid until they are sent.
  • Common/PaymentMethodBase.php, Tamara/Tamara.php: the initialization doc says that an update replaces the payment methods, so enabled must be sent again to keep the method enabled; the specification does not state this, it is the API's behaviour.
  • Bizum, PayByBank, Stablecoin: still extend PaymentMethodBase as before. Their $initialization is redeclared in each class with a deprecation notice, because their schemas do not define it; the property, its name and what is sent do not change. Bizum::$payment_method_options deprecated and documented.
  • Setups/Common/Customer/MerchantAccount.php: every property documented from the spec; $last_payment_amount documented as float (the spec type is number, example 55.99), noting that the API currently stores it as an integer.
  • Common/EmptyAsObject.php (new): class attribute that makes JsonSerializer write an annotated object with nothing set as {} instead of [].
  • JsonSerializer.php: reads #[EmptyAsObject] on the class and its parents, the same way it reads #[DateOnly]. Empty lists and every other object serialize as before.
  • Setups/Common/PaymentMethods/Common/PaymentSetupPaymentMethod.php: annotated #[EmptyAsObject], which covers every payment method class.
  • Annotated #[EmptyAsObject], every other class under Payments/Setups that maps a JSON object: PaymentSetupRequest, PaymentMethods, PaymentSetupBilling, PaymentSetupBillingDescriptor, Customer, Device, Email, MerchantAccount, Industry, AccommodationData, AccommodationHost, AirlineData, AirlineInsurance, AirlineInsurancePrice, PaymentSetupAccommodationAddress, PaymentSetupAccommodationRoom, Order, OrderSubMerchant, PaymentSetupAmountAllocation, AmountAllocationCommission, Settings, PaymentSetupPresentmentDetails, PaymentSetupRisk, PaymentSetupTerminal, AchAccountHolder, AchAccountHolderIdentification, ApplePayTokenData, ApplePayTokenDataHeader, BacsAccountHolder, CardDetails, CardToken, CardPresentPin, CashAppAction, CashAppAddress, CashAppCustomerProfile, CardAccountHolder, PaymentMethodAction, PaymentMethodOption, PaymentMethodOptions, WalletToken, GooglePayTokenData, PaymentSetupInstrumentAction, KlarnaAccountHolder, KlarnaAction, P24AccountHolder, PayByBankAction, PayByBankBank, PaypalAction, SepaAccountHolder, SepaMandate, StcpayAction, SwishAccountHolder, TamaraRiskAssessment. Constants classes are not annotated.
  • PaymentSetupPaymentMethodsSerializationTest.php (new): literal key of every new method, declared properties against the spec, full round trips, constants, the spec examples, every payment_type value and the default, billing.address, deprecated members still sent, Bizum, PayByBank and Stablecoin still instanceof PaymentMethodBase, an empty payment method sent as {} (new and existing methods) while an empty flags list stays [].
  • PaymentSetupEmptyObjectsSerializationTest.php (new): every Payment Setup object class with nothing set is sent as {} at the top level and nested; a scan of Payments/Setups fails if an object class is not annotated; the request with empty payment_methods, settings, customer, order and billing; the empty account holders, SEPA mandate and Tamara risk assessment; an empty shared Address inside billing is unchanged.
  • JsonSerializerTest.php, EmptyAsObjectParentFixture.php, EmptyAsObjectChildFixture.php, PlainObjectFixture.php (new fixtures): the attribute at the top level, nested, inside a list and inherited from a parent; empty lists stay []; an object with values and an object without the attribute are unchanged.
  • PaymentSetupFieldsSerializationTest.php: last_payment_amount round trip and literal key with 55.99.
  • PaymentSetupsIntegrationTest.php: uses the new constants.

API Reference

Deprecated

All of them will be removed in the next major version.

  • $initialization on Bizum, PayByBank and Stablecoin. Their spec schemas have no initialization and the API ignores it. No replacement.
  • Bizum::$payment_method_options. Not part of the Bizum schema. No replacement.

Breaking changes required by the API

  • PaymentSetupRequest::$payment_type now uses the Payment Setup values, so its default changes from "Regular" to "regular". The API takes only the six lowercase spec values for the new types (PayLater is rejected with 422 payment_type_property_invalid); it still stores "Regular" as regular. Code that compares the default with "Regular" sees the new value. Use PaymentSetupPaymentType for the other values.
  • A Payment Setup object with nothing set is now sent as {} instead of []. In the sandbox, every empty object sent as [] is rejected with 422 <path>_property_invalid and the same request with {} returns 200. Checked for the payment methods (for example payment_methods.alma_property_invalid, sequra, wechatpay), for objects inside them (sepa.account_holder, sepa.mandate, ach.account_holder, swish.account_holder, p24.account_holder, tamara.risk_assessment), for payment_methods itself, and for the top-level billing, customer, order and settings. This also applies to the existing classes when they are sent empty. Objects with at least one value and lists are sent exactly as before.

No class changes its parent: PaymentMethodBase and PaymentSetupPaymentMethod are unchanged, and every existing method keeps its base class.

Follow-ups

  • account_funding_transaction on the Payment Setup is not modelled yet; reported internally and handled separately.
  • Order items and shipping keep the /payments types (Payments\Product, Payments\ShippingDetails) until the next major version; reported internally.
  • The request always sends payment_type (default regular) unless the caller sets it to null; reported internally.
  • Empty shared objects inside a Payment Setup still serialize as []: Common\Address, Common\Phone, Payments\Product (order items), Payments\ShippingDetails, Payments\AccommodationGuest, Payments\FlightLegDetails, Payments\Passenger, Payments\Ticket. They are also used by other endpoints, so they are not changed here; reported internally.

README

No change needed.

@rodrigojara-cko
rodrigojara-cko requested a review from a team October 26, 2026 14:26
@agent-wall-e

agent-wall-e Bot commented Oct 26, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

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

Classification reasons

  • exceeds_bounded_scope:1495>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 54


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 26, 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_scope — 1495>250 classifying §2.1 M8 More than 250 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 Oct 26, 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 29 new payment method classes, an EmptyAsObject serializer attribute, billing and payment_type fields to PaymentSetupRequest, and annotates all existing Setups object classes. The implementation is generally correct and well-documented, but there is one concrete correctness issue in JsonSerializer and one removed property that looks like a regression.

Concerns

  • In JsonSerializer::serialize(), the top-level path for a non-array body now calls normalizeObject($body), but if $body itself is an empty object annotated #[EmptyAsObject], json_encode(new \stdClass(), JSON_UNESCAPED_SLASHES) returns {} — that is likely correct — however the previous code path called normalize(get_object_vars($body), $body) which returned an array, and json_encode([]) returns []. The concern is not the empty case but the non-empty case: the old code returned array, which json_encode writes as a JSON object if the keys are non-numeric. The new code also returns array from normalizeObject when non-empty, so that is fine. BUT: the old top-level call passed $body as the $owner argument to normalize, which is used by isDateOnly for property-level annotation lookup. The new call also does this inside normalizeObject, so DateOnly annotations still work. This appears safe, but it is a subtle change in the hot path of the serializer and worth a focused review.
  • In PaymentMethods.php, the $paypal property (with its @var Paypal and doc comment) has been removed entirely from the class. The diff shows the klarna property followed by stcpay with no paypal in between. If PayByBank::$bank_id and PayByBank::$action are still present (they are, per the PayByBank diff), this looks like an unintentional deletion of the paypal field rather than a deprecation — it is not mentioned in the deprecation section of the PR description either. Callers who set $payment_methods->paypal will now get a runtime 'undefined property' notice and the field will silently not serialize.
  • The isEmptyAsObject cache is stored in a static class property (self::$emptyAsObjectCache), which is shared across all instances of JsonSerializer. This is the same pattern used for $dateOnlyCache, so it is consistent, but it means the cache is never invalidated within a PHP process — acceptable for production, but test authors should be aware that tests that rely on the cache being cold may interact.
  • The diff is truncated (12 files omitted); the test files (PaymentSetupPaymentMethodsSerializationTest, PaymentSetupEmptyObjectsSerializationTest, JsonSerializerTest) are not visible, so it is not possible to confirm that the paypal removal or the serializer change is covered by tests.

⚠️ 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

Adds the 29 payment methods of the spec that were missing and the request
billing; payment_type defaults to regular with constants for the six spec
values. Members the spec does not have are deprecated.
…loat

Bizum, PayByBank and Stablecoin extend PaymentMethodBase again, as before, so instanceof checks keep working. Their initialization property is redeclared with a deprecation notice because their schemas do not define it.

Remove the Tamara initialization note that is not in the specification.

MerchantAccount last_payment_amount is a number in the specification (example 55.99), so it is documented as float.
@rodrigojara-cko
rodrigojara-cko force-pushed the feat/INT-1738-payment-setup-gaps branch from 52464c2 to 1b84ae6 Compare October 27, 2026 10:48
@agent-wall-e

agent-wall-e Bot commented Oct 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:1495>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 56


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1495>250 classifying §2.1 M8 More than 250 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

The specification types last_payment_amount as a number (example 55.99), but the sandbox stores and returns 55 for 55.99. The doc says so, so the type is not changed back and forth; the specification is reported separately.
@agent-wall-e

agent-wall-e Bot commented Oct 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:1496>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 56


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1496>250 classifying §2.1 M8 More than 250 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

json_encode writes an object with nothing set as [], and the API rejects that for alma, sequra and wechatpay with 422 payment_methods.<method>_property_invalid, while {} is accepted. A new EmptyAsObject class attribute, read by JsonSerializer like DateOnly, makes an annotated object with nothing set serialize as {}. It is applied to PaymentSetupPaymentMethod, so every Payment Setup payment method is covered. Empty lists and other objects are unchanged.

Document every MerchantAccount property from the specification.
@agent-wall-e

agent-wall-e Bot commented Oct 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:1600>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 59


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1600>250 classifying §2.1 M8 More than 250 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

card_present is only hidden from the public API reference; the API still supports it. PaymentMethods::$card_present, CardPresent and CardPresentPin are restored as on master, and the test still checks it is sent under its key.

The MerchantAccount test also asserts the literal last_payment_amount key with 55.99.
A PUT that omits initialization leaves the method disabled, so the initialization doc says to send enabled again on update, and that this is the API's behaviour, not stated in the specification.
@agent-wall-e

agent-wall-e Bot commented Oct 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:1587>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 57


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1587>250 classifying §2.1 M8 More than 250 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

The API rejects any empty object sent as [] with 422 <path>_property_invalid and accepts {}, not only on payment methods: also the account holders, the SEPA mandate, the Tamara risk assessment, payment_methods, billing, customer, order and settings. Every class under Payments/Setups that maps a JSON object is now annotated #[EmptyAsObject]. Constants classes and classes shared with /payments are unchanged.

Tests cover every annotated class with nothing set, a scan that fails if a new Setups object class is not annotated, the request with empty top-level objects, the nested objects of the payment methods, and a generic JsonSerializer test with fixtures (inheritance, top level, nested, inside a list, empty lists stay []).
@agent-wall-e

agent-wall-e Bot commented Oct 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:1733>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 100


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1733>250 classifying §2.1 M8 More than 250 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

The API keeps ideal, knet and bancontact invalid until description, language and account_holder_name are sent, although the specification marks them optional; their docs say so. The PaymentMethodBase initialization doc now says that an update replaces the payment methods, as on Tamara.
@agent-wall-e

agent-wall-e Bot commented Oct 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:1739>250

Operational gates

  • ✅ jira_ticket (INT-1738)
  • ✅ independent_review

Files analysed: 100


wall-e 2026.06.19-02 · policy 6b4ce2b3b45a…

@agent-wall-e

agent-wall-e Bot commented Oct 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_scope — 1739>250 classifying §2.1 M8 More than 250 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

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.

1 participant