"""Mock TEST-MODE payment provider. Never contacts a real processor, never takes real money. Only well-known test card numbers are accepted; any other number is rejected so a real card can never be "charged" even by accident. Card numbers are not stored (last4 only). """ import hashlib, hmac, json, urllib.request from .store import now, new_id TEST_CARDS = { "4242424242424242": ("succeeded", None), "4000000000000002": ("failed", "card_declined"), "4000000000009995": ("failed", "insufficient_funds"), } def sign(secret: str, body: bytes) -> str: return hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() def charge(intent: dict, card_number: str, webhook_url: str, secret: str) -> dict: """Simulate the provider: decide outcome, then deliver a signed webhook to the platform.""" digits = "".join(ch for ch in str(card_number or "") if ch.isdigit()) if digits not in TEST_CARDS: return {"status": "failed", "error": "not_a_test_card", "message": "TEST MODE: only test cards are accepted (use 4242 4242 4242 4242)."} status, err = TEST_CARDS[digits] event = { "id": new_id("evt"), "type": "payment_intent." + ("succeeded" if status == "succeeded" else "payment_failed"), "created": now(), "livemode": False, "data": {"intent_id": intent["id"], "amount_cents": intent["total_cents"], "fee_cents": intent["fee_cents"], "card_last4": digits[-4:], "failure_code": err}, } body = json.dumps(event).encode() req = urllib.request.Request(webhook_url, data=body, method="POST", headers={ "Content-Type": "application/json", "X-Mock-Signature": sign(secret, body)}) with urllib.request.urlopen(req, timeout=10) as r: ack = json.loads(r.read().decode()) return {"status": status, "error": err, "event_id": event["id"], "webhook_ack": ack}