Skip to main content

Notify Lambda Function

Quick Reference

  • Function Name: payment3-notify-${BRANCH}
  • Handler: notify.lambda_handler
  • Runtime: Python 3.12 (Docker Image)
  • Timeout: 240 seconds
  • Trigger: API Gateway (POST /notify)
  • Template.yaml: Lines 348-413

Function Overview

The Notify function receives webhook notifications from KBank payment gateway for credit card payments. It performs checksum verification, saves notification records, processes paid orders, and triggers delayed notification processing. This is the primary entry point for credit card payment webhooks.

Entry Point

File: functions/callback/notify.py
Handler: lambda_handler(event, _)

def lambda_handler(event, _):
"""Record credit card callback."""
# Reset delayed notify flag for this invocation
reset_delayed_notify_flag()

processor = None
try:
# Verify checksum FIRST before any other processing
checksum_valid, checksum_error, charge_id = verify_and_handle_checksum(
event=event,
function_name="notify",
branch=BRANCH,
debug_mode=DEBUG_MODE
)

if not checksum_valid:
# Log warning but continue processing
print(f"[WARNING] Checksum verification failed: {checksum_error}. Continuing with processing.")

# Process credit card callback
processor = CreditCardCallbackProcessor(
event=event,
branch=BRANCH,
debug_mode=DEBUG_MODE
)
print("start saving")
processor.save()

if processor.card_payment_record:
print("start processing paid order")
r = processor.process_paid_order()
print("process paid order response", r)
else:
print("card payment record not found, skipping process_paid_order")

# Call delayed notify AFTER all processing is complete (only once)
call_delayed_notify(event, BRANCH, DEBUG_MODE)

return processor.response
except Exception as e:
# Handle error (logging and notifications)
handle_notify_error(e, event, processor, function_name="notify")
# Re-raise the error
raise

Event Structure

API Gateway Event

Path: POST /notify
Auth: NONE (public endpoint - called by KBank)

Request Body (JSON):

{
"id": "chrg_prod_1143084777d1604dd41328c9a760e1eeb2fbe",
"object": "charge",
"amount": 1583.0,
"currency": "THB",
"status": "success",
"transaction_state": "Settled",
...
}

Key Fields:

  • id or objectId - KBank charge ID (required)
  • status - Payment status: "success" or "failure" (required)
  • amount - Charged amount
  • currency - Currency code (typically "THB")

Response (Success - HTML redirect):

<html>
<head>
<meta http-equiv="Refresh" content="0; URL='https://shop.villamarket.com/thankyou?orderId=...&status=success&amount=...'">
</head>
<body>
<h2>Payment Processing Complete</h2>
<p>Redirecting to Villa Market...</p>
</body>
</html>

Checksum Verification

Critical: This function performs checksum verification FIRST before any processing.

Process:

  1. Extract charge_id from event body
  2. Verify checksum using KBank secret key
  3. Update checksumVerified field in CardPaymentRecordTable
  4. Log errors if checksum fails (but continue processing)

See: functions/callback/docs/CHECKSUM_MARKING_DOCUMENTATION.md for detailed documentation.

DynamoDB Tables

CreditCardNotifyTable

Table Name: payment3-credit-card-notify-&#123;BRANCH&#125;
Region: ap-southeast-1

PynamoDB Model:

class CreditCardNotifyTable(Model):
class Meta:
table_name = f"payment3-credit-card-notify-{BRANCH}"
region = "ap-southeast-1"

id = UnicodeAttribute(hash_key=True)
data = JSONAttribute()
chargeId = UnicodeAttribute(null=True)
raw_input = JSONAttribute(null=True)
orderId = UnicodeAttribute(null=True)
note = UnicodeAttribute(default="")

Save Pattern:

from src.credit_card_notify_table import CreditCardNotifyTable

CreditCardNotifyTable(
id=charge_id,
chargeId=charge_id,
data=body_data,
raw_input=event,
orderId=order_id,
note=""
).save()

CardPaymentRecordTable

Table Name: payment3-card-payment-record-&#123;BRANCH&#125; or payment3-card-payment-record-master
Region: ap-southeast-1

Update: checksumVerified field is updated during checksum verification.

Query: Records are queried by chargeId using GSI chargeId_index.

Lambda Invocations

ProcessPaidOrder

Function: payment3-process-paid-order-$&#123;BRANCH&#125;
Invoked When: processor.card_payment_record exists

Payload: DynamoDB Stream event format

Invocation Type: Synchronous (via processor.process_paid_order())

DelayedNotify

Function: payment3-delayed-notify-$&#123;BRANCH&#125;
Invoked When: After all processing is complete

Payload: Original event

Invocation Type: Event (asynchronous)

Note: Only called once per invocation (uses environment variable flag)

Processing Flow

Main Flow

  1. Reset Flag: Reset delayed notify flag for new invocation
  2. Verify Checksum: Verify webhook checksum (updates checksumVerified)
  3. Create Processor: Initialize CreditCardCallbackProcessor
  4. Save Notification: Save to CreditCardNotifyTable
  5. Process Paid Order: Invoke ProcessPaidOrder if card_payment_record exists
  6. Call Delayed Notify: Invoke DelayedNotify (only once)
  7. Return Response: Return HTML redirect response

Detailed Algorithm

1. Reset DELAYED_NOTIFY_CALLED environment variable
2. Extract charge_id from event body
3. Verify checksum:
- Get KBank secret from Secrets Manager
- Calculate expected checksum
- Compare with received checksum
- Update CardPaymentRecordTable.checksumVerified
4. Create CreditCardCallbackProcessor:
- Parse event body (JSON or URL-encoded)
- Query CardPaymentRecordTable by chargeId
- Get orderId from card_payment_record
5. Save notification:
- Create CreditCardNotifyTable record
- id = charge_id
- data = body data
- orderId = order_id
6. If card_payment_record exists:
- Invoke ProcessPaidOrder synchronously
7. Call delayed notify:
- Set DELAYED_NOTIFY_CALLED = "true"
- Invoke DelayedNotify asynchronously
8. Return HTML redirect response

Error Handling

Checksum Verification Failures

Warning: Checksum failures are logged but processing continues

if not checksum_valid:
# Log warning but continue processing
print(f"[WARNING] Checksum verification failed: {checksum_error}. Continuing with processing.")

Handling:

  • Checksum error logged
  • checksumVerified set to False in CardPaymentRecordTable
  • Error logged to CallbackErrorLogTable
  • Error email sent via SES
  • Exception captured in Sentry
  • Processing continues

Processor Errors

Exception: Caught in lambda_handler

Handling:

  • Error logged via handle_notify_error()
  • Logged to CallbackErrorLogTable
  • Error email sent via SES
  • Exception captured in Sentry
  • Error re-raised

IAM Policies

From template.yaml (lines 358-399):

  • DynamoDBWritePolicy for CreditCardNotifyTable (line 360-361)
  • DynamoDBReadPolicy for order-table-dev (line 363-364)
  • DynamoDBWritePolicy for CreditCardCallbackTable (line 366-367)
  • DynamoDBWritePolicy for QRCallbackRecordTable (line 369-370)
  • DynamoDBCrudPolicy for CardPaymentRecordTable (line 372-373)
  • DynamoDBCrudPolicy for payment3-card-payment-record-master (line 375-376)
  • DynamoDBWritePolicy for PaymentErrorLogTable (line 378-379)
  • DynamoDBWritePolicy for CallbackErrorLogTable (line 381-382)
  • LambdaInvokePolicy for ProcessPaidOrder (line 384-385)
  • LambdaInvokePolicy for ProcessPaidOrderInternal (line 387-388)
  • LambdaInvokePolicy for DelayedNotify (line 390-391)
  • SESCrudPolicy for sending error emails (line 393-394)
  • AWSSecretsManagerGetSecretValuePolicy for kbank-dev (line 396-397)
  • AWSSecretsManagerGetSecretValuePolicy for kbank-prod (line 398-399)

Dependencies

External Lambda Functions

  • payment3-process-paid-order-$&#123;BRANCH&#125; - Process paid orders (invoked)
  • payment3-delayed-notify-$&#123;BRANCH&#125; - Delayed notification processing (invoked)

DynamoDB Tables

  • payment3-credit-card-notify-$&#123;BRANCH&#125; - Notification records (write)
  • payment3-card-payment-record-$&#123;BRANCH&#125; - Payment records (read/update)
  • payment3-card-payment-record-master - Master payment records (read/update)
  • order-table-dev - Order data (read)
  • payment3-error-log-$&#123;BRANCH&#125; - Error logs (write)
  • payment3-callback-error-log-$&#123;BRANCH&#125; - Callback error logs (write)

External Services

  • KBank Payment Gateway: Webhook source
  • AWS Secrets Manager: KBank API credentials for checksum verification
  • SES: Send error notification emails

Environment Variables

  • BRANCH - Git branch name
  • DEBUG_MODE - Debug mode flag (skip table writes if True)
  • DELAYED_NOTIFY_CALLED - Flag to prevent duplicate delayed notify calls

Testing

Local Testing

File: functions/callback/notify.py (lines 58-106)

if __name__ == "__main__":
import json
from src.credit_card_callback_processor import CreditCardCallbackProcessor

# Enable debug mode
os.environ["DEBUG_MODE"] = "true"
os.environ["BRANCH"] = "master"

# Sample input
sample_body = {
"id": "chrg_prod_1143084777d1604dd41328c9a760e1eeb2fbe",
"object": "charge",
"amount": 1583.0,
"currency": "THB"
}

event = {"body": json.dumps(sample_body)}
result = lambda_handler(event, None)

Test Event Example

{
"body": "{\"id\": \"chrg_prod_1143084777d1604dd41328c9a760e1eeb2fbe\", \"object\": \"charge\", \"amount\": 1583.0, \"currency\": \"THB\", \"status\": \"success\"}"
}

Code Structure

File Organization:

functions/callback/
├── notify.py # Main handler
└── src/
├── checksum_handler.py # Checksum verification
├── credit_card_callback_processor.py # Callback processor
├── delayed_notify_handler.py # Delayed notify invocation
├── notify_processor.py # Error handling
└── credit_card_notify_table.py # Notification table
  • DelayedNotify - Called after processing completes
  • ProcessPaidOrder - Processes successfully paid orders
  • Callback - Handles 3D Secure callbacks
  • Checksum Documentation: functions/callback/docs/CHECKSUM_MARKING_DOCUMENTATION.md

Common Patterns

Checksum Verification Pattern

# Verify checksum FIRST before any other processing
checksum_valid, checksum_error, charge_id = verify_and_handle_checksum(
event=event,
function_name="notify",
branch=BRANCH,
debug_mode=DEBUG_MODE
)

Delayed Notify Pattern

# Call delayed notify AFTER all processing is complete (only once)
call_delayed_notify(event, BRANCH, DEBUG_MODE)

Troubleshooting

Common Issues

  1. Checksum Verification Failures

    • Check KBank secret keys in Secrets Manager
    • Verify checksum algorithm matches KBank specification
    • Check CloudWatch logs for checksum calculation details
  2. Card Payment Record Not Found

    • Verify chargeId exists in CardPaymentRecordTable
    • Check if record was created in master table
    • Verify chargeId matches between CardPayment and Notify
  3. Delayed Notify Not Called

    • Check DELAYED_NOTIFY_CALLED flag is reset
    • Verify Lambda invocation permissions
    • Check CloudWatch logs for invocation errors

Debugging

  • Check CloudWatch logs for checksum verification details
  • Review CallbackErrorLogTable for error history
  • Check Sentry for exception tracking
  • Verify CreditCardNotifyTable records are being saved
  • Test with known good charge IDs

References

  • Template.yaml: Lines 348-413
  • Checksum Documentation: functions/callback/docs/CHECKSUM_MARKING_DOCUMENTATION.md
  • Related Functions: DelayedNotify, ProcessPaidOrder, Callback