paynode-sdk-python 1.1.3__py3-none-any.whl → 1.4.0__py3-none-any.whl

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.
paynode_sdk/__init__.py CHANGED
@@ -5,17 +5,27 @@ import logging
5
5
  # to ensure a clean experience for PayNode SDK users.
6
6
  warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets.legacy")
7
7
 
8
- from .middleware import PayNodeMiddleware
8
+ from .middleware import PayNodeMiddleware, x402_gate
9
9
  from .verifier import PayNodeVerifier
10
10
  from .errors import ErrorCode, PayNodeException
11
11
  from .idempotency import IdempotencyStore, MemoryIdempotencyStore
12
12
  from .webhook import PayNodeWebhookNotifier, PaymentEvent
13
13
  from .client import PayNodeAgentClient
14
- from .constants import ACCEPTED_TOKENS
14
+ from .constants import (
15
+ PAYNODE_ROUTER_ADDRESS,
16
+ PAYNODE_ROUTER_ADDRESS_SANDBOX,
17
+ BASE_USDC_ADDRESS,
18
+ BASE_USDC_ADDRESS_SANDBOX,
19
+ ACCEPTED_TOKENS,
20
+ MIN_PAYMENT_AMOUNT
21
+ )
15
22
 
16
23
  __all__ = [
17
- "PayNodeMiddleware", "PayNodeVerifier", "ErrorCode", "PayNodeException",
24
+ "PayNodeMiddleware", "x402_gate", "PayNodeVerifier", "ErrorCode", "PayNodeException",
18
25
  "IdempotencyStore", "MemoryIdempotencyStore",
19
26
  "PayNodeWebhookNotifier", "PaymentEvent",
20
- "PayNodeAgentClient", "ACCEPTED_TOKENS"
27
+ "PayNodeAgentClient",
28
+ "PAYNODE_ROUTER_ADDRESS", "PAYNODE_ROUTER_ADDRESS_SANDBOX",
29
+ "BASE_USDC_ADDRESS", "BASE_USDC_ADDRESS_SANDBOX",
30
+ "ACCEPTED_TOKENS", "MIN_PAYMENT_AMOUNT"
21
31
  ]
paynode_sdk/client.py CHANGED
@@ -6,7 +6,7 @@ from eth_account.messages import encode_typed_data
6
6
  from web3 import Web3
7
7
  from requests.adapters import HTTPAdapter
8
8
  from urllib3.util.retry import Retry
9
- from .constants import PAYNODE_ROUTER_ADDRESS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS
9
+ from .constants import PAYNODE_ROUTER_ADDRESS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, BASE_RPC_URLS, ACCEPTED_TOKENS
10
10
  from .errors import PayNodeException, ErrorCode
11
11
 
12
12
  logger = logging.getLogger("paynode_sdk.client")
@@ -17,7 +17,7 @@ class PayNodeAgentClient:
17
17
  Automatically handles the x402 'Payment Required' handshake.
18
18
  Supports RPC redundancy and EIP-2612 Permit-First payments.
19
19
  """
20
- def __init__(self, private_key: str, rpc_urls: list | str = "https://mainnet.base.org"):
20
+ def __init__(self, private_key: str, rpc_urls: list | str = BASE_RPC_URLS):
21
21
  self.rpc_urls = rpc_urls if isinstance(rpc_urls, list) else [rpc_urls]
22
22
  self.w3 = self._init_w3()
23
23
 
@@ -46,7 +46,7 @@ class PayNodeAgentClient:
46
46
  except Exception as e:
47
47
  logger.warning(f"⚠️ [PayNode-PY] RPC {rpc} failed: {str(e)}")
48
48
  continue
49
- raise PayNodeException("Failed to connect to any provided RPC nodes.", ErrorCode.rpc_error)
49
+ raise PayNodeException(ErrorCode.rpc_error)
50
50
 
51
51
  def request_gate(self, url: str, method: str = "GET", **kwargs):
52
52
  """The high-level autonomous method handling 402 loop."""
@@ -67,7 +67,7 @@ class PayNodeAgentClient:
67
67
  kwargs = self._handle_402(response.headers, **kwargs)
68
68
  except Exception as e:
69
69
  if isinstance(e, PayNodeException): raise
70
- raise PayNodeException(f"An unexpected error occurred: {str(e)}", ErrorCode.internal_error)
70
+ raise PayNodeException(ErrorCode.internal_error, message=f"An unexpected error occurred: {str(e)}")
71
71
  continue
72
72
  return response
73
73
  return response
@@ -78,13 +78,29 @@ class PayNodeAgentClient:
78
78
  amount_raw = int(headers.get('x-paynode-amount', 0))
79
79
  token_addr = headers.get('x-paynode-token-address')
80
80
  order_id = headers.get('x-paynode-order-id')
81
+ currency = headers.get('x-paynode-currency', 'USDC')
82
+ chain_id_header = headers.get('x-paynode-chain-id')
81
83
 
82
84
  if not all([router_addr, merchant_addr, amount_raw, token_addr, order_id]):
83
- raise PayNodeException("Malformed 402 headers: missing metadata", ErrorCode.internal_error)
85
+ raise PayNodeException(ErrorCode.internal_error, message="Malformed 402 headers: missing metadata")
86
+
87
+ # Network safety check (v1.4)
88
+ if chain_id_header:
89
+ current_chain_id = self.w3.eth.chain_id
90
+ if int(chain_id_header) != current_chain_id:
91
+ raise PayNodeException(ErrorCode.invalid_receipt, message=f"Network mismatch: Current {current_chain_id}, Request {chain_id_header}.")
92
+
93
+ logger.info(f"💡 [PayNode-PY] Payment request: {amount_raw} {currency} to {merchant_addr}")
84
94
 
85
95
  # v1.3 Constraint: Min payment protection
86
96
  if amount_raw < 1000:
87
- raise PayNodeException("Payment amount is below the protocol minimum (1000).", ErrorCode.amount_too_low)
97
+ raise PayNodeException(ErrorCode.amount_too_low)
98
+
99
+ # v1.4 Constraint: Token whitelist pre-flight (Anti-FakeToken)
100
+ resolved_chain_id = int(chain_id_header) if chain_id_header else 8453
101
+ whitelist = ACCEPTED_TOKENS.get(resolved_chain_id, [])
102
+ if whitelist and token_addr and token_addr.lower() not in [t.lower() for t in whitelist]:
103
+ raise PayNodeException(ErrorCode.token_not_accepted)
88
104
 
89
105
  # Protocol v1.3: Permit-First Execution
90
106
  try:
@@ -94,12 +110,12 @@ class PayNodeAgentClient:
94
110
  tx_hash = self._execute_pay(router_addr, token_addr, merchant_addr, amount_raw, order_id)
95
111
  else:
96
112
  logger.info("⚡ [PayNode-PY] Insufficient allowance. Attempting Permit-First payment...")
97
- tx_hash = self.pay_with_permit_auto(router_addr, token_addr, merchant_addr, amount_raw, order_id)
113
+ tx_hash = self.pay_with_permit(router_addr, token_addr, merchant_addr, amount_raw, order_id)
98
114
 
99
115
  logger.info(f"✅ [PayNode-PY] Payment successful: {tx_hash}")
100
116
  except Exception as e:
101
117
  if isinstance(e, PayNodeException): raise
102
- raise PayNodeException(f"On-chain transaction reverted or failed: {str(e)}", ErrorCode.transaction_failed)
118
+ raise PayNodeException(ErrorCode.transaction_failed, details=e)
103
119
 
104
120
  retry_headers = kwargs.get('headers', {}).copy()
105
121
  retry_headers.update({'x-paynode-receipt': tx_hash, 'x-paynode-order-id': order_id})
@@ -173,7 +189,7 @@ class PayNodeAgentClient:
173
189
  "deadline": deadline
174
190
  }
175
191
 
176
- def pay_with_permit_auto(self, router_addr, token_addr, merchant_addr, amount, order_id):
192
+ def pay_with_permit(self, router_addr, token_addr, merchant_addr, amount, order_id):
177
193
  """Combines sign_permit and on-chain submission."""
178
194
  sig = self.sign_permit(token_addr, router_addr, amount)
179
195
  router_abi = [{"inputs": [{"name": "payer", "type": "address"}, {"name": "token", "type": "address"}, {"name": "merchant", "type": "address"}, {"name": "amount", "type": "uint256"}, {"name": "orderId", "type": "bytes32"}, {"name": "deadline", "type": "uint256"}, {"name": "v", "type": "uint8"}, {"name": "r", "type": "bytes32"}, {"name": "s", "type": "bytes32"}], "name": "payWithPermit", "outputs": [], "stateMutability": "nonpayable", "type": "function"}]
paynode_sdk/constants.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # Generated by scripts/sync-config.py
2
- PAYNODE_ROUTER_ADDRESS = "0x92e20164FC457a2aC35f53D06268168e6352b200"
3
- PAYNODE_ROUTER_ADDRESS_SANDBOX = "0xB587Bc36aaCf65962eCd6Ba59e2DA76f2f575408"
2
+ PAYNODE_ROUTER_ADDRESS = "0x4A73696ccF76E7381b044cB95127B3784369Ed63"
3
+ PAYNODE_ROUTER_ADDRESS_SANDBOX = "0x24cD8b68aaC209217ff5a6ef1Bf55a59f2c8Ca6F"
4
4
  BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
5
- BASE_USDC_ADDRESS_SANDBOX = "0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798"
5
+ BASE_USDC_ADDRESS_SANDBOX = "0x109AEddD656Ed2761d1e210E179329105039c784"
6
6
  BASE_USDC_DECIMALS = 6
7
7
 
8
8
  PROTOCOL_TREASURY = "0x598bF63F5449876efafa7b36b77Deb2070621C0E"
@@ -14,7 +14,7 @@ BASE_RPC_URLS_SANDBOX = ["https://sepolia.base.org", "https://base-sepolia-rpc.p
14
14
 
15
15
  ACCEPTED_TOKENS = {
16
16
  8453: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
17
- 84532: ["0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798"]
17
+ 84532: ["0x109AEddD656Ed2761d1e210E179329105039c784"]
18
18
  }
19
19
 
20
20
  PAYNODE_ROUTER_ABI = [
paynode_sdk/errors.py CHANGED
@@ -1,3 +1,4 @@
1
+ # Generated by scripts/sync-config.py
1
2
  from enum import Enum
2
3
  from typing import Any, Optional
3
4
 
@@ -15,9 +16,24 @@ class ErrorCode(str, Enum):
15
16
  order_mismatch = 'order_mismatch'
16
17
  missing_receipt = 'missing_receipt'
17
18
 
19
+ ERROR_MESSAGES = {
20
+ ErrorCode.rpc_error: "Failed to connect to any provided RPC nodes.",
21
+ ErrorCode.insufficient_funds: "Wallet lacks USDC or ETH for gas.",
22
+ ErrorCode.amount_too_low: "Payment amount is below the protocol minimum (1000).",
23
+ ErrorCode.token_not_accepted: "The provided token address is not in the whitelist.",
24
+ ErrorCode.transaction_failed: "On-chain transaction reverted or failed.",
25
+ ErrorCode.duplicate_transaction: "This transaction hash has already been consumed.",
26
+ ErrorCode.invalid_receipt: "The provided receipt (TxHash) is malformed or invalid.",
27
+ ErrorCode.internal_error: "An unexpected error occurred.",
28
+ ErrorCode.transaction_not_found: "Transaction not found on-chain.",
29
+ ErrorCode.wrong_contract: "Payment event was not emitted by the official PayNode contract.",
30
+ ErrorCode.order_mismatch: "OrderId in receipt does not match requested ID.",
31
+ ErrorCode.missing_receipt: "Please pay to PayNode contract and provide 'x-paynode-receipt' header.",
32
+ }
33
+
18
34
  class PayNodeException(Exception):
19
- def __init__(self, message: str, code: ErrorCode, details: Optional[Any] = None):
20
- super().__init__(message)
21
- self.message = message
35
+ def __init__(self, code: ErrorCode, message: Optional[str] = None, details: Optional[Any] = None):
22
36
  self.code = code
37
+ self.message = message or ERROR_MESSAGES.get(code, "An unexpected error occurred.")
23
38
  self.details = details
39
+ super().__init__(self.message)
paynode_sdk/middleware.py CHANGED
@@ -5,6 +5,12 @@ from fastapi.responses import JSONResponse
5
5
  from .verifier import PayNodeVerifier
6
6
  from .errors import ErrorCode
7
7
  from .idempotency import IdempotencyStore
8
+ from .constants import (
9
+ BASE_RPC_URLS,
10
+ PAYNODE_ROUTER_ADDRESS,
11
+ BASE_USDC_ADDRESS,
12
+ BASE_USDC_DECIMALS
13
+ )
8
14
 
9
15
  from starlette.middleware.base import BaseHTTPMiddleware
10
16
 
@@ -12,14 +18,14 @@ class PayNodeMiddleware(BaseHTTPMiddleware):
12
18
  def __init__(
13
19
  self,
14
20
  app: Any,
15
- rpc_urls: list | str,
16
- contract_address: str,
17
21
  merchant_address: str,
18
- chain_id: int,
19
- currency: str,
20
- token_address: str,
21
22
  price: str,
22
- decimals: int,
23
+ contract_address: str = PAYNODE_ROUTER_ADDRESS,
24
+ chain_id: int = 8453,
25
+ currency: str = "USDC",
26
+ token_address: str = BASE_USDC_ADDRESS,
27
+ decimals: int = BASE_USDC_DECIMALS,
28
+ rpc_urls: list | str = BASE_RPC_URLS,
23
29
  store: Optional[IdempotencyStore] = None,
24
30
  generate_order_id: Optional[Callable[[Request], str]] = None
25
31
  ):
@@ -91,3 +97,14 @@ class PayNodeMiddleware(BaseHTTPMiddleware):
91
97
  "message": str(err)
92
98
  }
93
99
  )
100
+
101
+ def x402_gate(
102
+ merchant_address: str,
103
+ price: str,
104
+ **kwargs
105
+ ) -> Any:
106
+ """
107
+ Semantic helper to mirror JS x402Gate.
108
+ Usage: app.add_middleware(x402_gate, merchant_address=..., price=...)
109
+ """
110
+ return lambda app: PayNodeMiddleware(app, merchant_address=merchant_address, price=price, **kwargs)
paynode_sdk/verifier.py CHANGED
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from .errors import ErrorCode, PayNodeException
2
3
  from .constants import PAYNODE_ROUTER_ABI, ACCEPTED_TOKENS, MIN_PAYMENT_AMOUNT
3
4
  from .idempotency import MemoryIdempotencyStore
@@ -17,7 +18,7 @@ class PayNodeVerifier:
17
18
  except Exception:
18
19
  continue
19
20
  if not self.w3:
20
- raise PayNodeException("Failed to connect to any provided RPC nodes.", ErrorCode.rpc_error)
21
+ raise PayNodeException(ErrorCode.rpc_error)
21
22
  self.contract_address = contract_address
22
23
  self.chain_id = int(chain_id) if chain_id else None
23
24
  self.store = store or MemoryIdempotencyStore()
@@ -34,85 +35,100 @@ class PayNodeVerifier:
34
35
 
35
36
  async def verify_payment(self, tx_hash, expected):
36
37
  if not self.w3:
37
- return {"isValid": False, "error": PayNodeException("Verifier Provider Missing", ErrorCode.rpc_error)}
38
+ return {"isValid": False, "error": PayNodeException(ErrorCode.rpc_error, message="Verifier Provider Missing")}
38
39
 
39
40
  # 0. Dust Exploit Check (Minimum Payment)
40
41
  amount = int(expected.get("amount", 0))
41
42
  if amount < MIN_PAYMENT_AMOUNT:
42
43
  return {"isValid": False, "error": PayNodeException(
43
- f"Payment amount {amount} is below the minimum threshold of {MIN_PAYMENT_AMOUNT}.",
44
- ErrorCode.amount_too_low
44
+ ErrorCode.amount_too_low,
45
+ message=f"Payment amount {amount} is below the minimum threshold of {MIN_PAYMENT_AMOUNT}."
45
46
  )}
46
47
 
47
48
  # 1. Token Whitelist Check (Anti-FakeToken)
48
49
  expected_token = expected.get("tokenAddress", "").lower()
49
50
  if self.accepted_tokens and expected_token not in self.accepted_tokens:
50
51
  return {"isValid": False, "error": PayNodeException(
51
- f"Token {expected.get('tokenAddress')} is not in the accepted whitelist.",
52
- ErrorCode.token_not_accepted
52
+ ErrorCode.token_not_accepted,
53
+ message=f"Token {expected.get('tokenAddress')} is not in the accepted whitelist."
53
54
  )}
54
55
 
55
- try:
56
- is_new = await self.store.check_and_set(tx_hash, 86400) # 24 hour TTL
57
- if not is_new:
58
- return {"isValid": False, "error": PayNodeException("Receipt already used", ErrorCode.receipt_already_used)}
59
- except Exception as e:
60
- return {"isValid": False, "error": PayNodeException("Store Error", ErrorCode.internal_error, details=str(e))}
61
56
 
57
+
58
+ # Wrap synchronous web3 calls in asyncio.to_thread to avoid blocking the event loop
62
59
  try:
63
- receipt = self.w3.eth.get_transaction_receipt(tx_hash)
60
+ receipt = await asyncio.to_thread(self.w3.eth.get_transaction_receipt, tx_hash)
64
61
  except Exception:
65
- return {"isValid": False, "error": PayNodeException("Transaction not found", ErrorCode.transaction_not_found)}
62
+ return {"isValid": False, "error": PayNodeException(ErrorCode.transaction_not_found)}
66
63
 
67
64
  if not receipt:
68
- return {"isValid": False, "error": PayNodeException("Transaction not found", ErrorCode.transaction_not_found)}
65
+ return {"isValid": False, "error": PayNodeException(ErrorCode.transaction_not_found)}
69
66
 
70
67
  if receipt.get("status") == 0:
71
- return {"isValid": False, "error": PayNodeException("Transaction failed", ErrorCode.transaction_failed)}
72
-
73
- if not receipt.get("to") or receipt.get("to", "").lower() != self.contract_address.lower():
74
- return {"isValid": False, "error": PayNodeException("Wrong contract", ErrorCode.wrong_contract)}
68
+ return {"isValid": False, "error": PayNodeException(ErrorCode.transaction_failed)}
75
69
 
76
70
  contract = self.w3.eth.contract(address=Web3.to_checksum_address(self.contract_address), abi=PAYNODE_ROUTER_ABI)
77
71
 
78
72
  try:
79
- logs = contract.events.PaymentReceived().process_receipt(receipt)
73
+ logs = await asyncio.to_thread(contract.events.PaymentReceived().process_receipt, receipt)
80
74
  except Exception:
81
- return {"isValid": False, "error": PayNodeException("Invalid receipt format", ErrorCode.invalid_receipt)}
75
+ return {"isValid": False, "error": PayNodeException(ErrorCode.invalid_receipt)}
82
76
 
83
77
  if not logs:
84
- return {"isValid": False, "error": PayNodeException("No valid PaymentReceived event found", ErrorCode.invalid_receipt)}
78
+ return {"isValid": False, "error": PayNodeException(ErrorCode.invalid_receipt, message="No valid PaymentReceived event found")}
85
79
 
86
- # Find the valid log
80
+ # Find and validate the specific log
87
81
  merchant = expected.get("merchantAddress", "").lower()
88
82
  token = expected.get("tokenAddress", "").lower()
89
83
  amount = int(expected.get("amount", 0))
90
-
91
84
  order_id_bytes = self.w3.keccak(text=expected.get("orderId", ""))
92
85
 
93
- valid = False
86
+ last_error = None
87
+ valid_log_found = False
88
+
94
89
  for log in logs:
90
+ if log.address.lower() != self.contract_address.lower():
91
+ last_error = last_error or PayNodeException(ErrorCode.wrong_contract)
92
+ continue
93
+
95
94
  args = log.args
96
95
 
96
+ # 4. Verify OrderId
97
97
  if args.get("orderId") != order_id_bytes:
98
+ last_error = PayNodeException(ErrorCode.order_mismatch)
98
99
  continue
99
100
 
101
+ # 5. Verify Merchant
100
102
  if args.get("merchant", "").lower() != merchant:
103
+ last_error = PayNodeException(ErrorCode.invalid_receipt, message="Payment went to a different merchant.")
101
104
  continue
102
105
 
106
+ # 6. Verify Token
103
107
  if args.get("token", "").lower() != token:
108
+ last_error = PayNodeException(ErrorCode.invalid_receipt, message="Payment used unexpected token.")
104
109
  continue
105
110
 
111
+ # 7. Verify Amount
106
112
  if args.get("amount", 0) < amount:
113
+ last_error = PayNodeException(ErrorCode.invalid_receipt, message="Payment amount is below required price.")
107
114
  continue
108
115
 
116
+ # 8. Verify ChainId
109
117
  if self.chain_id and args.get("chainId") != self.chain_id:
118
+ last_error = PayNodeException(ErrorCode.invalid_receipt, message="ChainId mismatch. Invalid network.")
110
119
  continue
111
120
 
112
- valid = True
121
+ valid_log_found = True
113
122
  break
114
123
 
115
- if not valid:
116
- return {"isValid": False, "error": PayNodeException("Payment criteria mismatch", ErrorCode.invalid_receipt)}
124
+ if not valid_log_found:
125
+ return {"isValid": False, "error": last_error or PayNodeException(ErrorCode.invalid_receipt, message="No matching payment event found.")}
126
+
127
+ try:
128
+ is_new = await self.store.check_and_set(tx_hash, 86400) # 24 hour TTL
129
+ if not is_new:
130
+ return {"isValid": False, "error": PayNodeException(ErrorCode.duplicate_transaction)}
131
+ except Exception as e:
132
+ return {"isValid": False, "error": PayNodeException(ErrorCode.internal_error, details=str(e))}
117
133
 
118
134
  return {"isValid": True}
paynode_sdk/webhook.py CHANGED
@@ -212,8 +212,8 @@ class PayNodeWebhookNotifier:
212
212
  async with session.post(self.webhook_url, data=payload, headers=headers, timeout=aiohttp.ClientTimeout(total=10)) as resp:
213
213
  if resp.status >= 400:
214
214
  raise PayNodeException(
215
- f"Webhook returned {resp.status}",
216
- ErrorCode.internal_error
215
+ ErrorCode.internal_error,
216
+ message=f"Webhook returned {resp.status}"
217
217
  )
218
218
 
219
219
  logger.info(f"✅ [PayNode Webhook] Delivered tx {event.tx_hash[:10]}... → {resp.status}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paynode-sdk-python
3
- Version: 1.1.3
3
+ Version: 1.4.0
4
4
  Summary: PayNode Protocol Python SDK for AI Agents
5
5
  Author-email: PayNodeLabs <contact@paynode.dev>
6
6
  License: MIT
@@ -67,13 +67,21 @@ cp .env.example .env
67
67
  # Edit .env with your private key and RPC URLs
68
68
  ```
69
69
 
70
- ### 2. Run the Merchant Server (FastAPI)
70
+ ### 2. Get Test Tokens (Required for Base Sepolia)
71
+
72
+ If you're testing on Sepolia, run the helper script to mint 1,000 mock USDC:
73
+
74
+ ```bash
75
+ python examples/mint_test_tokens.py
76
+ ```
77
+
78
+ ### 3. Run the Merchant Server (FastAPI)
71
79
 
72
80
  ```bash
73
81
  python examples/fastapi_server.py
74
82
  ```
75
83
 
76
- ### 3. Run the Agent Client
84
+ ### 4. Run the Agent Client
77
85
 
78
86
  In another terminal:
79
87
 
@@ -0,0 +1,13 @@
1
+ paynode_sdk/__init__.py,sha256=NTyR6AbDSimrLFIeZ4SfJmkesA9J9VQZ3H4vVrhoZKc,1139
2
+ paynode_sdk/client.py,sha256=gqsNJoi72iBuOnw8DMNp-V59qUwOt-vvNd3L_zm9J9Q,11566
3
+ paynode_sdk/constants.py,sha256=-hR1P9B80-aQ0kCbY9hJEb1tpiRia1F0VCki9LpG7zs,1429
4
+ paynode_sdk/errors.py,sha256=9Mnyctt-ekcbNAkgKi45i0UZr8QXDEBwLpQTNtuz9i0,1965
5
+ paynode_sdk/idempotency.py,sha256=od7HuSxFdejBP0oE4QCzbJdrDZWvziiu09d3BRErU2k,999
6
+ paynode_sdk/middleware.py,sha256=ji0P-y1jnu4C4U95ASsylNujqPms5HAY5D6RfJiolgc,4105
7
+ paynode_sdk/verifier.py,sha256=DtsmaqXgHYoW-wQddL5QRoC6nCWr6Lc1_AomcVSvjxY,6071
8
+ paynode_sdk/webhook.py,sha256=xmesxnjnk8KQaqpvby3-uRYrmZbti_dhPw22r4uhwus,8310
9
+ paynode_sdk_python-1.4.0.dist-info/licenses/LICENSE,sha256=U8RjGlEBtXN6PA-qN_N3Uh60jyu3qe26ZBmgt-LAHc4,1069
10
+ paynode_sdk_python-1.4.0.dist-info/METADATA,sha256=eMYqGKWKYePIoYz2SW_8pgb_8T7pIh6f3HF6BOVU6l0,3037
11
+ paynode_sdk_python-1.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ paynode_sdk_python-1.4.0.dist-info/top_level.txt,sha256=c6Skc1Xx-9O-JJ7sHghLW8Kyn4hyJoVPUawH1Mu8iTU,12
13
+ paynode_sdk_python-1.4.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- paynode_sdk/__init__.py,sha256=p6URBqxFz1AErG7rIRWKnfwnzAC19qrMURtG7YhVR1I,821
2
- paynode_sdk/client.py,sha256=tNQE3dizUnIc15Z9M1NU_0jZcoLJe01oN1TI40Ynl2k,10761
3
- paynode_sdk/constants.py,sha256=puqz09qeKcMoigWrqdIzkhtAzFpECxEwHGLvDd3U_CQ,1429
4
- paynode_sdk/errors.py,sha256=m49zByht9_nrmgX4_zHUR5eLsCHX_iR-d_BJZ2VNOx8,829
5
- paynode_sdk/idempotency.py,sha256=od7HuSxFdejBP0oE4QCzbJdrDZWvziiu09d3BRErU2k,999
6
- paynode_sdk/middleware.py,sha256=ubSFv2fiAjJ16cxrORDfWbsUHffhBbD4Wfvt0zM7lvE,3563
7
- paynode_sdk/verifier.py,sha256=CzWO9IsXwc3AeCkmXy8gvcNTUbPhXnbfxmAa7lr1mhE,5274
8
- paynode_sdk/webhook.py,sha256=DaCIuZ_rI7Kynt60Drw2EKQJuhNW0GoMk_u9EZy4Jxs,8302
9
- paynode_sdk_python-1.1.3.dist-info/licenses/LICENSE,sha256=U8RjGlEBtXN6PA-qN_N3Uh60jyu3qe26ZBmgt-LAHc4,1069
10
- paynode_sdk_python-1.1.3.dist-info/METADATA,sha256=nluXsilwB805frjjWE2rKgJ_ZC98hjQK_06O4HGSIao,2858
11
- paynode_sdk_python-1.1.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
- paynode_sdk_python-1.1.3.dist-info/top_level.txt,sha256=c6Skc1Xx-9O-JJ7sHghLW8Kyn4hyJoVPUawH1Mu8iTU,12
13
- paynode_sdk_python-1.1.3.dist-info/RECORD,,