Chapter 3: ECPay Payment Integration & Secure Webhooks

Getting paid is the ultimate goal of any business website. This chapter teaches you to integrate ECPay, Taiwan's leading payment gateway.

Why Payment Integration is Critical

Many beginners create "fake checkout buttons" that just show an alert saying "Payment Successful." In the real world, payment integration involves:

  • Secure communication with the payment gateway
  • Proper encryption of sensitive data
  • Handling asynchronous payment confirmations
  • Dealing with transaction failures gracefully
  • Reconciling payments with orders

Getting this wrong means charging customers without granting access, or worse, granting access without receiving payment.

The Three-Stage Payment Flow

Every online payment system follows three stages:

| Stage | Description | Location | |:-----:|-------------|----------| | 1. Create Order | Generate encrypted order data | Your server | | 2. Redirect | User enters card info on secure page | Payment gateway | | 3. Webhook | Gateway notifies you of result | Background |

Stage 1: Creating a Secure Order

When a user clicks "Buy Now," your frontend must NOT redirect directly. Instead:

  1. Frontend sends a request to your backend API
  2. Backend creates an "unpaid" order in the database with a unique ID
  3. Backend retrieves secret keys from environment variables
  4. Backend sorts all parameters alphabetically per ECPay specifications
  5. Backend encrypts the parameter string with SHA256 to produce CheckMacValue
  6. Backend returns HTML form with hidden fields that auto-submit to ECPay
def generate_check_mac(params: dict, hash_key: str, hash_iv: str) -> str:
    """
    Generate ECPay CheckMacValue for payment verification.
    
    Parameters:
        params: Dictionary of payment parameters
        hash_key: Your ECPay HashKey (from .env)
        hash_iv: Your ECPay HashIV (from .env)
    
    Returns:
        Uppercase SHA256 hex digest
    """
    import hashlib, urllib.parse
    
    # Sort parameters alphabetically by key name
    sorted_str = "".join(f"{k}={v}&" for k, v in sorted(params.items()))
    
    # Wrap with HashKey and HashIV
    raw = f"HashKey={hash_key}&{sorted_str}&HashIV={hash_iv}"
    
    # URL-encode special characters
    encoded = urllib.parse.quote(raw, safe="=&")
    
    # SHA256 encrypt and convert to uppercase
    return hashlib.sha256(encoded.encode("utf-8")).hexdigest().upper()

Stage 2: Redirect to Payment Gateway

The frontend auto-submits the HTML form, redirecting the user to ECPay's secure payment page. At this point:

  • The user enters credit card information on ECPay's server
  • Your server never touches the credit card number
  • PCI DSS compliance is handled entirely by ECPay
  • You simply wait for the result

Stage 3: Async Webhook Notification

This is where most beginners make critical mistakes.

After the user completes payment, they might close the browser before being redirected back, or lose internet connection. If you put the "unlock course" logic on the frontend success page, customers who paid will not get access.

The correct approach: Use a server-side webhook endpoint.

@app.post("/api/ecpay/callback")
async def ecpay_callback():
    """
    Receive ECPay payment notification.
    This endpoint is called by ECPay servers in the background.
    """
    # Get the received CheckMacValue
    received_mac = request.form.get("CheckMacValue")
    
    # Calculate what it should be
    calculated_mac = generate_check_mac(
        request.form.to_dict(), 
        HASH_KEY, 
        HASH_IV
    )
    
    # Verify signature
    if received_mac != calculated_mac:
        return "0|Signature verification failed", 400
    
    # Get the order ID
    order_id = request.form.get("MerchantTradeNo")
    
    # Update order status in database
    await database.execute(
        "UPDATE orders SET status = 'paid' WHERE id = :id",
        {"id": order_id}
    )
    
    # Unlock course access for the user
    await grant_course_access(order_id)
    
    # Tell ECPay we received the notification
    return "1|OK"

Security Best Practices

| Practice | Why It Matters | |----------|---------------| | Never expose HashKey/HashIV in frontend code | Prevents attackers from forging payments | | Always verify CheckMacValue on callback | Ensures notification is from ECPay, not a hacker | | Use environment variables for all secrets | Keeps keys out of your source code | | Use HTTPS everywhere | Encrypts all data in transit | | Make webhook processing idempotent | Same notification received twice should not double-unlock | | Log all transaction events | Provides audit trail for dispute resolution |

Common Implementation Mistakes

Mistake 1: Client-Side Payment Logic

Never generate CheckMacValue in the browser. If you do, your HashKey is exposed to every visitor. Anyone can inspect your JavaScript code and steal your payment keys.

Mistake 2: Skipping Signature Verification

Many beginners skip verifying the CheckMacValue in the callback. This means anyone who discovers your callback URL can send fake payment notifications.

Mistake 3: Synchronous Processing

Do not assume the user will wait for the callback. The callback may arrive minutes after the user closes their browser.

Mistake 4: Missing Idempotency

The same webhook notification may be sent multiple times by ECPay. Always check if the order is already marked as paid before granting access again.

The Vibe Coding Approach

Instead of manually implementing SHA256 encryption, describe the requirements:

"I need to integrate ECPay payment into my Next.js project. Write two API routes:

  1. POST /api/ecpay/create-order: Receives amount and product name, generates CheckMacValue encrypted with HashKey and HashIV, returns an auto-submit form.
  2. POST /api/ecpay/callback: Receives ECPay payment notification, verifies the CheckMacValue signature, updates the order status to paid, and returns 1|OK."

The AI will generate production-ready code following all ECPay specifications.

Chapter Summary

Payment integration follows three stages: encrypted order creation, redirect to payment gateway, and async webhook callback. Security is critical - never expose keys, always verify signatures, and make processing idempotent.

Key takeaways:

  • Three-stage flow: create order -> redirect -> webhook
  • CheckMacValue is SHA256-encrypted parameter string
  • Always verify webhook signatures before processing
  • Never expose payment keys in frontend code
  • Webhooks must be idempotent

What's Next: Production Gotchas

With payment integrated, the next chapter covers real-world production bugs that can crash your site - from Google Translate conflicts to accidental database deletions.

Testing Your Payment Integration

Before going live, thoroughly test your payment flow:

Testing in ECPay Sandbox

ECPay provides a test environment with dedicated MerchantID and HashKey:

  • MerchantID: 2000132 (test account)
  • HashKey: 5294y06JbISpM5x9
  • HashIV: v77hoKGq4kWxNNIS

Use these credentials during development. Never use your production keys for testing.

Test Flow

  1. Create an order via your API
  2. Verify the returned CheckMacValue matches ECPay's format
  3. Submit the form to ECPay's test payment page
  4. Complete payment using ECPay's test card numbers
  5. Verify the callback webhook is received and processed correctly
  6. Check that the order status is updated in your database

Test Card Numbers

ECPay provides test card numbers for different scenarios: | Card Number | Scenario | Expected Result | |-------------|----------|----------------| | 4311-9522-2222-2222 | Successful payment | Webhook received, status = paid | | 4311-9522-2222-2223 | Failed payment | Webhook received, status = failed |

Chapter Summary (Extended)

Payment integration is the most critical business function in any e-commerce or SaaS platform. Proper implementation requires:

  1. Understanding the three-stage flow: create order -> redirect -> webhook
  2. Implementing secure SHA256 encryption for CheckMacValue
  3. Building a robust webhook handler with signature verification
  4. Making webhook processing idempotent
  5. Testing thoroughly in the sandbox environment before going live

When done correctly, your payment system runs reliably, handling thousands of transactions without issues.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!