transaction: Unnamed cart items fall back to the transaction subject.
This commit is contained in:
parent
23608de591
commit
0bd6353105
2 changed files with 71 additions and 44 deletions
|
@ -47,7 +47,7 @@ class CartItem(NamedTuple):
|
||||||
total_price: Amount
|
total_price: Amount
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, source: APIResponse) -> 'CartItem':
|
def from_api(cls, source: APIResponse, default_name: Optional[str]=None) -> 'CartItem':
|
||||||
total_price = Amount.from_api(source['item_amount'])
|
total_price = Amount.from_api(source['item_amount'])
|
||||||
quantity = Decimal(source.get('item_quantity', 1))
|
quantity = Decimal(source.get('item_quantity', 1))
|
||||||
try:
|
try:
|
||||||
|
@ -56,7 +56,7 @@ class CartItem(NamedTuple):
|
||||||
unit_price = total_price._replace(number=total_price.number / quantity)
|
unit_price = total_price._replace(number=total_price.number / quantity)
|
||||||
return cls(
|
return cls(
|
||||||
source.get('item_code'),
|
source.get('item_code'),
|
||||||
source.get('item_name'),
|
source.get('item_name', default_name),
|
||||||
source.get('item_description'),
|
source.get('item_description'),
|
||||||
quantity,
|
quantity,
|
||||||
unit_price,
|
unit_price,
|
||||||
|
@ -93,41 +93,32 @@ class Transaction(APIResponse):
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self._response)
|
return len(self._response)
|
||||||
|
|
||||||
def _from_response( # type:ignore[misc]
|
def _get_from_response(self, *keys: str) -> Any:
|
||||||
|
retval = self._response
|
||||||
|
for index, key in enumerate(keys):
|
||||||
|
try:
|
||||||
|
retval = retval[key]
|
||||||
|
except KeyError as error:
|
||||||
|
try:
|
||||||
|
txn_id = f"Transaction {self['transaction_info']['transaction_id']}"
|
||||||
|
except KeyError:
|
||||||
|
txn_id = "Transaction"
|
||||||
|
if index:
|
||||||
|
key_s = '→'.join(repr(key) for key in keys[:index + 1])
|
||||||
|
raise KeyError(f"{txn_id} {key_s}") from None
|
||||||
|
else:
|
||||||
|
raise errors.MissingFieldError(
|
||||||
|
f"{txn_id} was not loaded with {error.args[0]!r} field",
|
||||||
|
) from None
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def _wrap_response( # type:ignore[misc]
|
||||||
func: Callable[[Any], T],
|
func: Callable[[Any], T],
|
||||||
*keys: str,
|
*keys: str,
|
||||||
) -> Callable[['Transaction'], T]:
|
) -> Callable[['Transaction'], T]:
|
||||||
def _load_from_response(self: 'Transaction') -> T:
|
def response_wrapper(self: 'Transaction') -> T:
|
||||||
source = self._response
|
return func(self._get_from_response(*keys))
|
||||||
for index, key in enumerate(keys):
|
return response_wrapper
|
||||||
try:
|
|
||||||
source = source[key]
|
|
||||||
except KeyError as error:
|
|
||||||
try:
|
|
||||||
txn_id = f"Transaction {self['transaction_info']['transaction_id']}"
|
|
||||||
except KeyError:
|
|
||||||
txn_id = "Transaction"
|
|
||||||
if source is self._response:
|
|
||||||
raise errors.MissingFieldError(
|
|
||||||
f"{txn_id} was not loaded with {error.args[0]!r} field",
|
|
||||||
) from None
|
|
||||||
else:
|
|
||||||
key_s = '→'.join(repr(key) for key in keys[:index + 1])
|
|
||||||
raise KeyError(f"{txn_id} {key_s}") from None
|
|
||||||
return func(source)
|
|
||||||
return _load_from_response
|
|
||||||
|
|
||||||
def _cart_items(cart_info: APIResponse) -> Iterator[CartItem]: # type:ignore[misc]
|
|
||||||
try:
|
|
||||||
item_seq = cart_info['item_details']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for source in item_seq:
|
|
||||||
try:
|
|
||||||
yield CartItem.from_api(source)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _fee_amount(txn_info: APIResponse) -> Optional[Amount]: # type:ignore[misc]
|
def _fee_amount(txn_info: APIResponse) -> Optional[Amount]: # type:ignore[misc]
|
||||||
try:
|
try:
|
||||||
|
@ -137,28 +128,44 @@ class Transaction(APIResponse):
|
||||||
else:
|
else:
|
||||||
return Amount.from_api(raw_fee)
|
return Amount.from_api(raw_fee)
|
||||||
|
|
||||||
amount = _from_response(
|
amount = _wrap_response(
|
||||||
Amount.from_api,
|
Amount.from_api,
|
||||||
'transaction_info',
|
'transaction_info',
|
||||||
'transaction_amount',
|
'transaction_amount',
|
||||||
)
|
)
|
||||||
cart_items = _from_response(_cart_items, 'cart_info')
|
fee_amount = _wrap_response(_fee_amount, 'transaction_info')
|
||||||
fee_amount = _from_response(_fee_amount, 'transaction_info')
|
initiation_date = _wrap_response(
|
||||||
initiation_date = _from_response(
|
|
||||||
parse_datetime,
|
parse_datetime,
|
||||||
'transaction_info',
|
'transaction_info',
|
||||||
'transaction_initiation_date',
|
'transaction_initiation_date',
|
||||||
)
|
)
|
||||||
payer_email = _from_response(str, 'payer_info', 'email_address')
|
payer_email = _wrap_response(str, 'payer_info', 'email_address')
|
||||||
payer_fullname = _from_response(str, 'payer_info', 'payer_name', 'alternate_full_name')
|
payer_fullname = _wrap_response(str, 'payer_info', 'payer_name', 'alternate_full_name')
|
||||||
status = _from_response(
|
status = _wrap_response(
|
||||||
TransactionStatus.__getitem__,
|
TransactionStatus.__getitem__,
|
||||||
'transaction_info',
|
'transaction_info',
|
||||||
'transaction_status',
|
'transaction_status',
|
||||||
)
|
)
|
||||||
transaction_id = _from_response(str, 'transaction_info', 'transaction_id')
|
transaction_id = _wrap_response(str, 'transaction_info', 'transaction_id')
|
||||||
updated_date = _from_response(
|
updated_date = _wrap_response(
|
||||||
parse_datetime,
|
parse_datetime,
|
||||||
'transaction_info',
|
'transaction_info',
|
||||||
'transaction_updated_date',
|
'transaction_updated_date',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def cart_items(self) -> Iterator[CartItem]:
|
||||||
|
cart_info = self._get_from_response('cart_info')
|
||||||
|
try:
|
||||||
|
item_seq = cart_info['item_details']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
default_name = self['transaction_info']['transaction_subject']
|
||||||
|
except KeyError:
|
||||||
|
default_name = None
|
||||||
|
for source in item_seq:
|
||||||
|
try:
|
||||||
|
yield CartItem.from_api(source, default_name)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
|
@ -144,6 +144,20 @@ def test_cart_items():
|
||||||
(Decimal('15.98'), 'USD'),
|
(Decimal('15.98'), 'USD'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('subject', [
|
||||||
|
'test subject',
|
||||||
|
'$6.99 Subscription',
|
||||||
|
None,
|
||||||
|
])
|
||||||
|
def test_unnamed_cart_item_uses_transaction_subject(subject):
|
||||||
|
source = {
|
||||||
|
'cart_info': cart_info({'number': '6.99'}),
|
||||||
|
'transaction_info': transaction_info(subject=subject),
|
||||||
|
}
|
||||||
|
txn = txn_mod.Transaction(source)
|
||||||
|
item1, = txn.cart_items()
|
||||||
|
assert item1.name == subject
|
||||||
|
|
||||||
def test_cart_empty():
|
def test_cart_empty():
|
||||||
txn = txn_mod.Transaction({'cart_info': {}})
|
txn = txn_mod.Transaction({'cart_info': {}})
|
||||||
assert not any(txn.cart_items())
|
assert not any(txn.cart_items())
|
||||||
|
@ -217,4 +231,10 @@ def test_updated_date():
|
||||||
def test_info_missing(method_name):
|
def test_info_missing(method_name):
|
||||||
txn = txn_mod.Transaction({})
|
txn = txn_mod.Transaction({})
|
||||||
with pytest.raises(errors.MissingFieldError):
|
with pytest.raises(errors.MissingFieldError):
|
||||||
getattr(txn, method_name)()
|
result = getattr(txn, method_name)()
|
||||||
|
try:
|
||||||
|
do_next = iter(result) is result
|
||||||
|
except TypeError:
|
||||||
|
do_next = False
|
||||||
|
if do_next:
|
||||||
|
next(result)
|
||||||
|
|
Loading…
Reference in a new issue