fix: omit SimpleFin pending param when pending transactions are disabled (#2796)

The SimpleFIN protocol only defines pending=1 (include pending); pending
transactions are excluded by default when the param is absent. Bridges
presence-check the param, so the pending=0 we sent when the 'Include
pending transactions' setting (or SIMPLEFIN_INCLUDE_PENDING=0) was
disabled behaved exactly like pending=1, making the setting a no-op —
pending transactions kept being downloaded, causing pending/posted
duplicates and churn.

Omit the pending query param entirely unless pending is enabled, per
the spec.

Fixes #2440

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
RealDiligent
2026-07-26 07:56:42 +08:00
committed by GitHub
parent 98bf563458
commit d61ee3abea
2 changed files with 38 additions and 1 deletions

View File

@@ -66,7 +66,11 @@ class Provider::Simplefin
query_params["end-date"] = end_timestamp.to_s
end
query_params["pending"] = pending ? "1" : "0" unless pending.nil?
# Per the SimpleFIN protocol, pending transactions are excluded by default
# and only included when `pending=1` is present. Bridges presence-check the
# param, so sending `pending=0` behaves like `pending=1` — the only
# spec-compliant way to exclude pending is to omit the param entirely.
query_params["pending"] = "1" if pending
accounts_url = "#{access_url}/accounts"
accounts_url += "?#{URI.encode_www_form(query_params)}" unless query_params.empty?

View File

@@ -102,6 +102,39 @@ class Provider::SimplefinTest < ActiveSupport::TestCase
assert_equal :server_error, error.error_type
end
test "get_accounts sends pending=1 when pending is enabled" do
mock_response = OpenStruct.new(code: 200, body: '{"accounts": []}')
Provider::Simplefin.expects(:get)
.with { |url| url.include?("pending=1") }
.returns(mock_response)
@provider.get_accounts(@access_url, pending: true)
end
test "get_accounts omits the pending param when pending is disabled" do
# The SimpleFIN protocol has no pending=0 — bridges presence-check the
# param, so pending=0 behaves like pending=1. Disabling pending must omit
# the param entirely.
mock_response = OpenStruct.new(code: 200, body: '{"accounts": []}')
Provider::Simplefin.expects(:get)
.with { |url| !url.include?("pending") }
.returns(mock_response)
@provider.get_accounts(@access_url, pending: false)
end
test "get_accounts omits the pending param when pending is nil" do
mock_response = OpenStruct.new(code: 200, body: '{"accounts": []}')
Provider::Simplefin.expects(:get)
.with { |url| !url.include?("pending") }
.returns(mock_response)
@provider.get_accounts(@access_url, pending: nil)
end
test "claim_access_url retries on network errors" do
setup_token = Base64.encode64("https://example.com/claim")
mock_response = OpenStruct.new(code: 200, body: "https://example.com/access")