This complete Python 3.11+ standard-library HTTP server relays native Stripe requests to the shared PHP service. PHP remains the native verification, durable fulfillment and mail runtime. This is not an independent Python payment worker or a requirement for shared PHP hosting.
What you will learn
Read and run the exact Store service: Python native ingress with the complete shared application, without exposing private credentials or claiming an unverified payment.
Configure and run
Set private KDP_STORE_SERVICE_URL to your PHP service's exact HTTPS /provider-event URL. KDP_STORE_RELAY_PORT defaults to 8119. Use your own HTTPS reverse proxy in front of this loopback development listener; register its exact public URL with Stripe and preserve raw bytes and relevant headers.
python3 -m py_compile stores/relay.py
python3 stores/relay.py
Expected: a loopback listener, then only a real accepted upstream durable receipt produces HTTP 202. A signature refusal, invalid/oversized reply, redirect or timeout is not acknowledged as success. Run the separate PHP cron for licensing/mail.
Complete source
#!/usr/bin/env python3
"""Optional Python3.11+ loopback ingress; complete PHP service is required.
KDP_STORE_SERVICE_URL=https://YOUR_STORE_BACKEND/provider-event, optional port8119.
Run python3 relay.py. Only the service verifies native payments and commits inbox.
No external dependency, card collection, credential logging or blind retries.
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import os
import urllib.error
import urllib.parse
import urllib.request
TARGET = os.environ['KDP_STORE_SERVICE_URL']
URL = urllib.parse.urlsplit(TARGET)
LOCAL = os.environ.get('KDP_STORE_ENVIRONMENT') == 'sandbox' and os.environ.get('KDP_STORE_ALLOW_LOOPBACK') == '1' and URL.hostname == '127.0.0.1' and URL.scheme == 'http'
if (URL.scheme != 'https' and not LOCAL) or URL.username or URL.password or URL.query or URL.fragment or URL.path != '/provider-event':
raise SystemExit('Private service URL required')
ALLOWED = {'content-type', 'stripe-signature', 'paddle-signature', 'x-signature',
'x-wc-webhook-signature', 'x-wc-webhook-topic', 'x-shopify-hmac-sha256',
'x-shopify-topic', 'x-shopify-shop-domain', 'x-shopify-webhook-id', 'x-fs-signature',
'paypal-auth-algo', 'paypal-cert-url', 'paypal-transmission-id', 'paypal-transmission-sig', 'paypal-transmission-time'}
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, request, response, code, message, headers, target):
return None
class Receiver(BaseHTTPRequestHandler):
def log_message(self, *args):
return
def reply(self, code, value):
raw = json.dumps(value, separators=(',', ':')).encode()
self.send_response(code)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(raw)))
self.send_header('Cache-Control', 'no-store')
self.send_header('X-Content-Type-Options', 'nosniff')
self.end_headers()
self.wfile.write(raw)
def do_POST(self):
self.connection.settimeout(5)
if self.path != '/provider-event':
return self.reply(404, {'accepted': False})
lengths = self.headers.get_all('Content-Length', [])
if self.headers.get('Transfer-Encoding') or len(lengths) != 1 or not lengths[0].isdigit():
return self.reply(400, {'accepted': False})
size = int(lengths[0])
if size > 262144:
return self.reply(413, {'accepted': False})
headers = {}
for name in ALLOWED:
values = self.headers.get_all(name, [])
if len(values) > 1:
return self.reply(401, {'accepted': False})
if values:
headers[name] = values[0]
try:
raw = self.rfile.read(size)
if len(raw) != size:
return self.reply(400, {'accepted': False})
request = urllib.request.Request(TARGET, method='POST', data=raw, headers=headers)
try:
result = urllib.request.build_opener(NoRedirect()).open(request, timeout=12)
except urllib.error.HTTPError as error:
result = error
with result:
body = result.read(4097)
if len(body) > 4096:
raise ValueError('Invalid receipt')
receipt = json.loads(body)
if result.status == 202 and receipt.get('accepted') is True and type(receipt.get('duplicate')) is bool:
return self.reply(202, {'accepted': True, 'duplicate': receipt['duplicate']})
return self.reply(result.status if result.status in {400, 401, 409, 413, 415, 429} else 503, {'accepted': False})
except (OSError, ValueError, TypeError):
return self.reply(503, {'accepted': False})
if __name__ == '__main__':
print('{"listening":true,"loopback_only":true}', flush=True)
with HTTPServer(('127.0.0.1', int(os.environ.get('KDP_STORE_RELAY_PORT', '8119'))), Receiver) as server:
server.serve_forever()
Verification boundary
The exact server was exercised with local Stripe one-time/subscription contracts and actual isolated Sandbox licensing. It is not a hosted merchant checkout or production reverse-proxy test. Optional literal-loopback HTTP overrides require Sandbox and an explicit flag; they are not production TLS exceptions. Keep all secrets and state on the private service, not on the buyer's desktop or browser.
Prerequisites
Complete the private service installation. Keep all files from the exact source archive together, use PHP 8.2+ with its documented extensions and start with an isolated Sandbox product/key. A source fragment alone is not the application.
Expected result
Only a confirmed provider purchase authorizes a licensing effect. A durable HTTP receipt, completed licensing operation and SMTP acceptance are separate outcomes. Inspect aggregate worker states and verify the resulting license with the same buyer email; no full key appears in public replies.
Common mistakes
Do not deploy one module without its archive dependencies, place configuration under the web root, skip provider API verification or clear a durable ledger to bypass review. Optional Node/Python ingress still requires the PHP worker.
Security notes
Use least-privilege private provider and product-scoped licensing credentials, verified HTTPS, owner-only persistent state and separate secrets per account/environment. Never trust browser payment claims. Preserve held operations after an unknown result and reconcile before another write; exactly-once behavior across systems is not guaranteed.
Troubleshooting
For a receipt refusal check raw-byte preservation, the matching endpoint secret/account/mode, server clock where applicable and the private bootstrap layout. For REVIEW/IN_FLIGHT inspect provider state, safe private error codes and the licensing ledger before retrying. Missing permissions, recipient data or an initial mapping are not successful payments. For mail verify the separate SMTP ledger and provider delivery evidence. Do not post raw events, keys or SQLite files publicly.
Next steps
Run the selected provider Sandbox procedure, then test private key email delivery. Review supported recipe limitations before enabling Live.