fix(pay): handle nullable options for terminal payment states#474
fix(pay): handle nullable options for terminal payment states#474ignaciosantise wants to merge 5 commits into
Conversation
The API returns `options: null` for payments in terminal states (succeeded, failed, expired, cancelled). Mark the field as nullable in the OpenAPI spec and coerce null to an empty vec so callers can check `info.status` instead of hitting an "Invalid Response Payload" deserialization error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Claude finished @ignaciosantise's task in 1m 6s —— View job Review complete
Found 1 issue(s)Issue 1: No unit test for null options (terminal state) caseID: mod-null-options-test-a3f1 Context:
Recommendation: Add a test alongside the existing ones: #[tokio::test]
async fn test_get_payment_options_null_options_terminal_state() {
// mock returns {"options": null, "info": {...}}
// assert response.options.is_empty()
// assert response.info.is_some() // status shows terminal state
}The core fix is correct — marking |
There was a problem hiding this comment.
Pull request overview
This PR updates the Pay SDK’s get_payment_options handling and the embedded OpenAPI spec to support payments in terminal states where the backend may return options: null, avoiding “Invalid Response Payload” decoding failures.
Changes:
- Mark
GetPaymentOptionsResponse.optionsasnullablein the OpenAPI spec. - Coerce
nulloptions into an empty vector inWalletConnectPay::get_payment_optionsand use that for logging/caching/response mapping.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
crates/yttrium/src/pay/openapi.json |
Marks options as nullable in GetPaymentOptionsResponse to reflect backend behavior in terminal states. |
crates/yttrium/src/pay/mod.rs |
Adjusts get_payment_options to handle nullable options by defaulting to an empty list for downstream logic. |
Comments suppressed due to low confidence (1)
crates/yttrium/src/pay/mod.rs:1001
- This change introduces new behavior for when the API returns
options: null, but the existingget_payment_optionstests only cover non-null arrays. Add a test case that mocks a 200 response with"options": null(and any other required fields) and asserts the SDK returns an emptyoptionsvec and clears the cache accordingly.
let api_response = response.into_inner();
let options = api_response.options.unwrap_or_default();
pay_debug!(
"get_payment_options: success, {} options",
options.len()
);
// Cache the options with their raw actions
let cached: Vec<CachedPaymentOption> = options
.iter()
.map(|o| CachedPaymentOption {
option_id: o.id.clone(),
actions: o.actions.clone(),
})
.collect();
let mut cache = self.cached_options.write();
*cache = cached;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Verifies that get_payment_options handles options: null correctly when the API returns a 200 for a succeeded payment with includePaymentInfo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| "get_payment_options: success, {} options", | ||
| api_response.options.len() | ||
| ); | ||
| let options = api_response.options.unwrap_or_default(); |
There was a problem hiding this comment.
Instead of defaulting to empty set of options, I think you should early return w/ the status indicating that the payment is no longer available? Idk anything other than defaulting to empty
There was a problem hiding this comment.
i think i will just return the null value, and on wallet side check the payment status
Preserve the distinction between null (terminal payment state) and empty array (no viable options for the provided accounts) instead of coercing null to an empty vec. Callers can now branch on info.status when options is None. BREAKING: PaymentOptionsResponse.options is now Option<Vec<PaymentOption>>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
get_payment_optionson payments in terminal states (succeeded, failed, expired, cancelled) — the API returns"options": nullin that case, which progenitor previously failed to deserialize intoVec<PaymentOption>optionsfield as nullable in the OpenAPI spec forGetPaymentOptionsResponseoptionsasOption<Vec<PaymentOption>>end-to-end so callers can distinguishnull(terminal state — checkinfo.status) from[](no viable options, e.g. insufficient balance)nulloptions terminal-state pathBreaking change
PaymentOptionsResponse.optionsis nowOption<Vec<PaymentOption>>(nullable in FFI/JSON consumers). Swift/Kotlin wallets will need to handle the optional.Caveats
nullable: trueonoptions; once that's released, this spec file should be replaced wholesale with the official version before merging.Test plan
"options": nullpath with a succeeded payment responseopenapi.jsonwith the official spec once released by the backend teamoptions: None+ correctinfo.status🤖 Generated with Claude Code