diff --git a/SQUARE_WEBHOOK_SETUP.md b/SQUARE_WEBHOOK_SETUP.md new file mode 100644 index 0000000..e15ddce --- /dev/null +++ b/SQUARE_WEBHOOK_SETUP.md @@ -0,0 +1,191 @@ +# Square Webhook Setup - Quick Reference + +## Complete Setup Process + +### Step 1: Generate SSL Certificate +```powershell +# Generate PEM certificate +python generate_cert.py +# Creates: cert.pem, key.pem +``` + +### Step 2: Convert to P7B for Square +```powershell +# Convert PEM to P7B (PKCS#7) format +python convert_to_p7b.py +# Creates: cert.p7b +``` + +### Step 3: Start HTTPS Server +```powershell +# Start webhook server with HTTPS +python src/test_server.py --ssl-cert cert.pem --ssl-key key.pem --hook-port 8443 --hook-ssl-only + +# Server URLs: +# Main: http://localhost:7432/ +# Webhook: https://localhost:8443/hook (or https://yourdomain.com:8443/hook) +``` + +### Step 4: Configure Square Webhook + +1. **Upload P7B Certificate to Square:** + - Go to: https://developer.squareup.com/apps + - Select your application + - Navigate to: **Webhooks** → **Manage Notification Settings** + - Upload `cert.p7b` file + - This allows Square to verify webhook signatures + +2. **Add Webhook Subscription:** + - Still in Webhooks section + - Click **"Add Subscription"** + - Enter URL: `https://yourdomain.com:8443/hook` + - Select events to subscribe to (e.g., `inventory.count.updated`) + - Click **Save** + +### Step 5: Test the Webhook + +```powershell +# Test from command line +python src/test_webhook.py + +# Or manually with curl +curl -X POST https://localhost:8443/hook \ + -H "Content-Type: application/json" \ + -d '{"type":"inventory.count.updated","merchant_id":"test"}' +``` + +## Production Deployment + +### Using Let's Encrypt (Recommended) + +```bash +# 1. Get Let's Encrypt certificate (Linux/WSL) +sudo certbot certonly --standalone -d yourdomain.com + +# 2. Convert to P7B for Square +python convert_to_p7b.py \ + --cert /etc/letsencrypt/live/yourdomain.com/fullchain.pem \ + --output square.p7b + +# 3. Upload square.p7b to Square Dashboard + +# 4. Run server +python src/test_server.py \ + --ssl-cert /etc/letsencrypt/live/yourdomain.com/fullchain.pem \ + --ssl-key /etc/letsencrypt/live/yourdomain.com/privkey.pem \ + --host 0.0.0.0 \ + --hook-port 443 \ + --hook-ssl-only +``` + +### Using ngrok (Easy Testing) + +```powershell +# Terminal 1: Start your server (HTTP is fine) +python src/test_server.py --hook-port 8080 + +# Terminal 2: Expose with ngrok (provides HTTPS automatically) +ngrok http 8080 + +# Use the ngrok HTTPS URL in Square: +# Example: https://abc123.ngrok.io/hook + +# For P7B certificate with ngrok: +# ngrok provides its own certificate, so you don't need to upload one to Square +``` + +## Webhook Event Types + +Common Square webhook events you can subscribe to: + +- `inventory.count.updated` - Inventory quantity changes +- `catalog.version.updated` - Catalog item updates +- `payment.created` - New payment received +- `payment.updated` - Payment status changes +- `order.created` - New order created +- `order.updated` - Order status changes + +## Troubleshooting + +### Certificate Upload Fails +- Ensure file is in P7B format (not PEM) +- File must be valid PKCS#7 structure +- Check file isn't corrupted: `openssl pkcs7 -print_certs -in cert.p7b` + +### Webhook Not Receiving Data +1. Check server is running and accessible from internet +2. Verify URL in Square Dashboard is correct +3. Check firewall allows incoming traffic on webhook port +4. Review server logs: `./logs/studspider.log` + +### SSL Certificate Errors +- Self-signed certificates won't work with Square in production +- Use Let's Encrypt or commercial CA for production +- Use ngrok for quick testing (provides valid HTTPS) + +### Testing Locally +```powershell +# Square can't reach localhost - use one of these: +# 1. ngrok (easiest) +ngrok http 8443 + +# 2. Cloudflare Tunnel +cloudflared tunnel --url http://localhost:8443 + +# 3. Deploy to cloud (AWS, Azure, DigitalOcean, etc.) +``` + +## Verifying Webhook Signatures + +Square signs webhook requests. Your server should verify signatures: + +```python +# TODO: Add to hook_handler in test_server.py +import hmac +import hashlib + +def verify_square_signature(payload: bytes, signature: str, webhook_signature_key: str) -> bool: + """Verify Square webhook signature""" + computed_signature = hmac.new( + webhook_signature_key.encode('utf-8'), + payload, + hashlib.sha256 + ).digest() + + expected_signature = base64.b64decode(signature) + return hmac.compare_digest(computed_signature, expected_signature) +``` + +Get your webhook signature key from Square Dashboard → Webhooks → Signature Key. + +## Environment Variables + +Add to your `.env` file: + +```env +# Square Webhook Configuration +SQUARE_WEBHOOK_SIGNATURE_KEY=your_signature_key_from_square_dashboard + +# SSL Certificate Paths (production) +SSL_CERT_PATH=/path/to/fullchain.pem +SSL_KEY_PATH=/path/to/privkey.pem +``` + +## Commands Quick Reference + +```powershell +# Generate certificate +python generate_cert.py + +# Convert to P7B +python convert_to_p7b.py + +# Start HTTPS server +python src/test_server.py --ssl-cert cert.pem --ssl-key key.pem --hook-port 8443 --hook-ssl-only + +# Test webhook +python src/test_webhook.py + +# Start with ngrok +ngrok http 8443 +``` diff --git a/convert_to_p7b.py b/convert_to_p7b.py new file mode 100644 index 0000000..3373cc7 --- /dev/null +++ b/convert_to_p7b.py @@ -0,0 +1,166 @@ +""" +Convert PEM certificate to P7B (PKCS#7) format for Square webhooks +Square requires P7B format for webhook signature verification +""" + +import subprocess +import sys +import os + + +def convert_pem_to_p7b(cert_file="cert.pem", output_file="cert.p7b"): + """ + Convert PEM certificate to P7B (PKCS#7) format + + Args: + cert_file: Input PEM certificate file + output_file: Output P7B file + """ + + # Check if OpenSSL is available + try: + subprocess.run(["openssl", "version"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + print("ERROR: OpenSSL not found!") + print("\nFor Windows, you can:") + print( + "1. Install OpenSSL from: https://slproweb.com/products/Win32OpenSSL.html" + ) + print("2. Or use Git Bash which includes OpenSSL") + print("3. Or use WSL (Windows Subsystem for Linux)") + return False + + # Check if input file exists + if not os.path.exists(cert_file): + print(f"ERROR: Certificate file not found: {cert_file}") + print(f"Current directory: {os.getcwd()}") + print("\nMake sure you've generated the certificate first:") + print(" python generate_cert.py") + return False + + print(f"Converting {cert_file} to P7B format...") + + # Convert PEM to P7B (PKCS#7) + # -certfile can include intermediate certificates if needed + cmd = [ + "openssl", + "crl2pkcs7", + "-nocrl", + "-certfile", + cert_file, + "-out", + output_file, + ] + + try: + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + + if os.path.exists(output_file): + print(f"\n✓ P7B certificate created: {os.path.abspath(output_file)}") + print(f" File size: {os.path.getsize(output_file)} bytes") + + print("\n" + "=" * 60) + print("Square Webhook Configuration:") + print("=" * 60) + print(f"1. Upload this file to Square Developer Dashboard:") + print(f" {os.path.abspath(output_file)}") + print("\n2. In Square Dashboard:") + print(" - Go to Webhooks settings") + print(" - Click 'Manage Notification Settings'") + print(" - Upload the P7B file for signature verification") + print("\n3. Square will use this to verify webhook signatures") + print("=" * 60) + + # Show file contents preview + print("\nP7B file preview (first 500 chars):") + with open(output_file, "r") as f: + content = f.read(500) + print(content) + if len(content) == 500: + print("...") + + return True + else: + print("ERROR: Output file was not created") + return False + + except subprocess.CalledProcessError as e: + print(f"ERROR: Conversion failed: {e}") + if e.stderr: + print(f"OpenSSL error: {e.stderr}") + return False + + +def convert_with_chain(cert_file="cert.pem", chain_file=None, output_file="cert.p7b"): + """ + Convert PEM certificate with certificate chain to P7B format + Use this if you have intermediate certificates + + Args: + cert_file: Your certificate file + chain_file: Intermediate/chain certificate file (optional) + output_file: Output P7B file + """ + + if not chain_file: + return convert_pem_to_p7b(cert_file, output_file) + + # Check files exist + for f in [cert_file, chain_file]: + if not os.path.exists(f): + print(f"ERROR: File not found: {f}") + return False + + print(f"Converting {cert_file} with chain {chain_file} to P7B format...") + + # Create combined cert file + combined = "combined_cert.pem" + with open(combined, "w") as out: + with open(cert_file, "r") as f: + out.write(f.read()) + with open(chain_file, "r") as f: + out.write(f.read()) + + result = convert_pem_to_p7b(combined, output_file) + + # Clean up combined file + if os.path.exists(combined): + os.remove(combined) + + return result + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Convert PEM certificate to P7B (PKCS#7) for Square webhooks" + ) + parser.add_argument( + "--cert", + default="cert.pem", + help="Input PEM certificate file (default: cert.pem)", + ) + parser.add_argument( + "--chain", default=None, help="Optional: Intermediate certificate chain file" + ) + parser.add_argument( + "--output", default="cert.p7b", help="Output P7B file (default: cert.p7b)" + ) + + args = parser.parse_args() + + print("=" * 60) + print("PEM to P7B Certificate Converter for Square Webhooks") + print("=" * 60) + print() + + if args.chain: + success = convert_with_chain(args.cert, args.chain, args.output) + else: + success = convert_pem_to_p7b(args.cert, args.output) + + if not success: + sys.exit(1) + + print("\n✓ Conversion complete!") diff --git a/src/test_server.py b/src/test_server.py index 8012f0d..0bf6b7f 100644 --- a/src/test_server.py +++ b/src/test_server.py @@ -267,12 +267,42 @@ if __name__ == "__main__": # Setup SSL context if certificates provided ssl_context = None if args.ssl_cert and args.ssl_key: - ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_context.load_cert_chain(args.ssl_cert, args.ssl_key) - print(f"SSL enabled with cert: {args.ssl_cert}") + # Check if trying to use P7B file + if args.ssl_cert.endswith(".p7b") or args.ssl_key.endswith(".p7b"): + print("ERROR: Cannot use P7B files directly for HTTPS server!") + print("\nP7B files are for Square's webhook signature verification only.") + print("Your server needs PEM format files (.pem)") + print("\nWorkflow:") + print("1. Generate PEM files: python generate_cert.py") + print("2. Convert to P7B for Square: python convert_to_p7b.py") + print("3. Upload P7B to Square Dashboard") + print("4. Run server with PEM files:") + print( + " python src/test_server.py --ssl-cert cert.pem --ssl-key key.pem --hook-port 8443" + ) + print("\nIf you have a P7B and need PEM:") + print(" python convert_from_p7b.py your_cert.p7b") + exit(1) + + try: + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(args.ssl_cert, args.ssl_key) + print(f"✓ SSL enabled with cert: {args.ssl_cert}") + except FileNotFoundError as e: + print(f"ERROR: Certificate file not found: {e}") + exit(1) + except ssl.SSLError as e: + print(f"ERROR: Invalid certificate or key file: {e}") + print("\nMake sure you're using PEM format files.") + print("Generate certificates with: python generate_cert.py") + exit(1) elif args.ssl_cert or args.ssl_key: print("ERROR: Both --ssl-cert and --ssl-key must be provided together") exit(1) + else: + print("ℹ️ Running without SSL (HTTP only)") + print(" For production with nginx reverse proxy, this is recommended.") + print(" Nginx will handle HTTPS, forward HTTP to this server.") # If hook-port is specified, run a separate server for webhooks if args.hook_port: