python-getpaid-paynow 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/PKG-INFO +3 -3
  2. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/README.md +2 -2
  3. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/getting-started.md +2 -2
  4. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/pyproject.toml +1 -1
  5. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/src/getpaid_paynow/processor.py +22 -3
  6. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/test_callback.py +22 -0
  7. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/test_processor.py +2 -0
  8. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/uv.lock +1 -1
  9. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/.gitignore +0 -0
  10. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/.readthedocs.yml +0 -0
  11. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/CODE_OF_CONDUCT.md +0 -0
  12. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/CONTRIBUTING.md +0 -0
  13. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/LICENSE +0 -0
  14. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/changelog.md +0 -0
  15. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/codeofconduct.md +0 -0
  16. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/concepts.md +0 -0
  17. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/conf.py +0 -0
  18. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/configuration.md +0 -0
  19. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/contributing.md +0 -0
  20. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/index.md +0 -0
  21. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/license.md +0 -0
  22. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/reference.md +0 -0
  23. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/docs/requirements.txt +0 -0
  24. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/src/getpaid_paynow/__init__.py +0 -0
  25. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/src/getpaid_paynow/client.py +0 -0
  26. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/src/getpaid_paynow/py.typed +0 -0
  27. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/src/getpaid_paynow/types.py +0 -0
  28. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/__init__.py +0 -0
  29. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/conftest.py +0 -0
  30. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/test_client.py +0 -0
  31. {python_getpaid_paynow-0.1.0 → python_getpaid_paynow-0.1.2}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-getpaid-paynow
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Paynow payment gateway integration for python-getpaid ecosystem.
5
5
  Project-URL: Homepage, https://github.com/django-getpaid/python-getpaid-paynow
6
6
  Project-URL: Repository, https://github.com/django-getpaid/python-getpaid-paynow
@@ -61,7 +61,7 @@ The plugin is split into two layers:
61
61
  ### Standalone Client
62
62
 
63
63
  ```python
64
- import asyncio
64
+ import anyio
65
65
  from decimal import Decimal
66
66
  from getpaid_paynow import PaynowClient
67
67
 
@@ -83,7 +83,7 @@ async def main():
83
83
  redirect_url = response["redirectUrl"]
84
84
  print(f"Redirect buyer to: {redirect_url}")
85
85
 
86
- asyncio.run(main())
86
+ anyio.run(main)
87
87
  ```
88
88
 
89
89
  ### With django-getpaid
@@ -38,7 +38,7 @@ The plugin is split into two layers:
38
38
  ### Standalone Client
39
39
 
40
40
  ```python
41
- import asyncio
41
+ import anyio
42
42
  from decimal import Decimal
43
43
  from getpaid_paynow import PaynowClient
44
44
 
@@ -60,7 +60,7 @@ async def main():
60
60
  redirect_url = response["redirectUrl"]
61
61
  print(f"Redirect buyer to: {redirect_url}")
62
62
 
63
- asyncio.run(main())
63
+ anyio.run(main)
64
64
  ```
65
65
 
66
66
  ### With django-getpaid
@@ -32,7 +32,7 @@ The `PaynowClient` is an async HTTP client that wraps the Paynow V3 REST API.
32
32
  Use it as an async context manager for connection reuse:
33
33
 
34
34
  ```python
35
- import asyncio
35
+ import anyio
36
36
  from decimal import Decimal
37
37
  from getpaid_paynow import PaynowClient
38
38
 
@@ -56,7 +56,7 @@ async def main():
56
56
  redirect_url = response["redirectUrl"]
57
57
  print(f"Redirect buyer to: {redirect_url}")
58
58
 
59
- asyncio.run(main())
59
+ anyio.run(main)
60
60
  ```
61
61
 
62
62
  ## Using with django-getpaid
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = 'python-getpaid-paynow'
3
- version = '0.1.0'
3
+ version = "0.1.2"
4
4
  description = 'Paynow payment gateway integration for python-getpaid ecosystem.'
5
5
  readme = 'README.md'
6
6
  license = {text = 'MIT'}
@@ -110,8 +110,24 @@ class PaynowProcessor(BaseProcessor):
110
110
  :param headers: HTTP headers (must include 'Signature').
111
111
  :raises InvalidCallbackError: On missing/invalid signature.
112
112
  """
113
- raw_body: str = kwargs.get("raw_body", "")
114
- received_sig = headers.get("Signature", "")
113
+ raw_body = kwargs.get("raw_body")
114
+ if raw_body is None:
115
+ raise InvalidCallbackError(
116
+ "Missing raw_body in callback kwargs. "
117
+ "The framework adapter must pass the raw HTTP body."
118
+ )
119
+ if isinstance(raw_body, (bytes, bytearray)):
120
+ raw_body = raw_body.decode("utf-8")
121
+ if not isinstance(raw_body, str):
122
+ raise InvalidCallbackError(
123
+ "raw_body must be a str or bytes value."
124
+ )
125
+
126
+ received_sig = ""
127
+ for key, value in headers.items():
128
+ if key.lower() == "signature":
129
+ received_sig = value
130
+ break
115
131
 
116
132
  if not received_sig:
117
133
  raise InvalidCallbackError(
@@ -224,10 +240,13 @@ class PaynowProcessor(BaseProcessor):
224
240
  """Start a refund via Paynow API."""
225
241
  client = self._get_client()
226
242
  refund_amount = amount or self.payment.amount_paid
227
- await client.create_refund(
243
+ response = await client.create_refund(
228
244
  payment_id=self.payment.external_id,
229
245
  amount=refund_amount,
230
246
  )
247
+ refund_id = response.get("refundId", "")
248
+ if refund_id:
249
+ self.payment.external_refund_id = refund_id
231
250
  return refund_amount
232
251
 
233
252
  async def cancel_refund(self, **kwargs) -> bool:
@@ -86,6 +86,28 @@ class TestVerifyCallback:
86
86
  raw_body=raw_body,
87
87
  )
88
88
 
89
+ async def test_lowercase_signature_header_is_accepted(self):
90
+ data, raw_body = _notification()
91
+ signature = _sign_body(raw_body)
92
+ headers = {"signature": signature}
93
+ processor = _make_processor()
94
+ await processor.verify_callback(
95
+ data=data,
96
+ headers=headers,
97
+ raw_body=raw_body.encode("utf-8"),
98
+ )
99
+
100
+ async def test_missing_raw_body_raises(self):
101
+ data, raw_body = _notification()
102
+ signature = _sign_body(raw_body)
103
+ headers = {"Signature": signature}
104
+ processor = _make_processor()
105
+ with pytest.raises(InvalidCallbackError, match="Missing raw_body"):
106
+ await processor.verify_callback(
107
+ data=data,
108
+ headers=headers,
109
+ )
110
+
89
111
  async def test_bad_signature_raises(self):
90
112
  data, raw_body = _notification()
91
113
  headers = {"Signature": "bad_signature"}
@@ -312,6 +312,7 @@ class TestStartRefund:
312
312
  amount=Decimal("50.00"),
313
313
  )
314
314
  assert result == Decimal("50.00")
315
+ assert payment.external_refund_id == "REF-456"
315
316
 
316
317
  async def test_start_refund_full_amount(self, respx_mock):
317
318
  url = f"{SANDBOX_URL}/v3/payments/PAY-123/refunds"
@@ -324,6 +325,7 @@ class TestStartRefund:
324
325
  processor = _make_processor(payment=payment)
325
326
  result = await processor.start_refund()
326
327
  assert result == Decimal("100.00")
328
+ assert payment.external_refund_id == "REF-456"
327
329
 
328
330
  async def test_start_refund_sends_correct_body(self, respx_mock):
329
331
  url = f"{SANDBOX_URL}/v3/payments/PAY-123/refunds"
@@ -587,7 +587,7 @@ wheels = [
587
587
 
588
588
  [[package]]
589
589
  name = "python-getpaid-paynow"
590
- version = "0.1.0"
590
+ version = "0.1.2"
591
591
  source = { editable = "." }
592
592
  dependencies = [
593
593
  { name = "httpx" },