Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ okhttp
opentelemetry
otelgrpc
otelhttp
otherpisp
Otherville
OURCYGPATTERN
Palo
Expand All @@ -127,6 +128,10 @@ Payoneer
paypal
Payplug
pids
PISP
Pisp
pisp
Pisps
pmezard
proguard
Proguard
Expand All @@ -149,6 +154,7 @@ ropeproject
RPCURL
Rulebook
screenreaders
SECP
setlocal
sharedpref
Shopcider
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ jobs:
LOG_LEVEL: WARN
SHELLCHECK_OPTS: -e SC1091 -e 2086
VALIDATE_ALL_CODEBASE: false
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/).*|CODE_OF_CONDUCT.md|CHANGELOG.md"
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/|code/web-client/).*|CODE_OF_CONDUCT.md|CHANGELOG.md"
VALIDATE_BIOME_LINT: false
VALIDATE_BIOME_FORMAT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_FLAKE8: false
Expand Down
12 changes: 10 additions & 2 deletions code/sdk/python/ap2/sdk/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,20 @@ def evaluate(
instrument = closed_mandate.payment_instrument
if not instrument:
return ['Missing payment instrument in closed mandate']
# Match on both type and id. Matching on id alone lets a closed
# instrument of a different type (e.g. {id: 'x', type: 'bank'}) satisfy
# an allowed {id: 'x', type: 'card'}, since id uniqueness is not defined
# across types. Requiring (type, id) prevents that instrument-type
# confusion.
if any(
allowed.id == instrument.id
allowed.type == instrument.type and allowed.id == instrument.id
for allowed in self.constraint.allowed
):
return []
return [f'Payment instrument {instrument.id} not in allowed list']
return [
f'Payment instrument (type={instrument.type}, id={instrument.id}) '
'not in allowed list'
]


class AllowedPispEvaluator(PaymentConstraintEvaluator):
Expand Down
44 changes: 44 additions & 0 deletions code/sdk/python/ap2/tests/constraints_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,50 @@ def test_payment_allowed_payment_instrument_mismatch():
assert any('not in allowed list' in v for v in violations)


def test_payment_allowed_payment_instrument_same_id_different_type_rejected():
"""A closed instrument with an allowed id but a different type is rejected.

id uniqueness is not defined across instrument types, so matching on id
alone would let {id: 'pi-1', type: 'bank'} satisfy an allowed
{id: 'pi-1', type: 'card'}. Matching requires (type, id).
"""
violations = check_payment_constraints(
_open_payment(
constraints=[
AllowedPaymentInstruments(
allowed=[
PaymentInstrument(id='pi-1', type='card')
],
),
]
),
_closed_payment(
payment_instrument=PaymentInstrument(id='pi-1', type='bank')
),
)
assert any('not in allowed list' in v for v in violations)


def test_payment_allowed_payment_instrument_type_and_id_match():
"""A closed instrument matching an allowed (type, id) passes."""
violations = check_payment_constraints(
_open_payment(
constraints=[
AllowedPaymentInstruments(
allowed=[
PaymentInstrument(id='pi-1', type='card'),
PaymentInstrument(id='pi-1', type='bank'),
],
),
]
),
_closed_payment(
payment_instrument=PaymentInstrument(id='pi-1', type='bank')
),
)
assert violations == []


# ── check_payment_constraints – budget ───────────────────────────────────


Expand Down
Loading