Developer Console
感谢您的访问。此页面目前仅提供英语版本。我们正在开发中文版本。谢谢您的理解。

Verify Identity Key API

Python Code

"""
Identity Service Lambda Handler
Handles shopper identity management with DynamoDB
"""

import json
import os
import base64
import logging
from typing import Dict, Any, Optional, Union
from datetime import datetime
import boto3
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
from aws_xray_sdk.core import patch_all
import jsonschema
from aws_lambda_powertools import Logger, Metrics, Tracer
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import validate_request_schema
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent

# Initialize AWS X-Ray
patch_all()

# Initialize PowerTools
logger = Logger(service="IdentityService")
metrics = Metrics(namespace="IdentityService")
tracer = Tracer(service="IdentityService")

# Load configuration from AWS Parameter Store
def get_parameter(name: str) -> str:
    ssm = boto3.client('ssm')
    response = ssm.get_parameter(Name=name, WithDecryption=True)
    return response['Parameter']['Value']

def is_hex(s):
    """
    Check if a string is hexadecimal

    Args:
        s (str): String to check

    Returns:
        bool: True if string is hexadecimal, False otherwise
    """
    return all(c in '0123456789ABCDEFabcdef' for c in s)

class Config:
    """Configuration management"""
    SHOPPER_TABLE = os.environ.get("shopperInfoTable")
    IDENTITY_TABLE = os.environ.get("identityInfoTable")
    FAILED_IDENTITY_TABLE = os.environ.get("failedIdentityInfoTable")
    MIN_DECODED_LENGTH = 39
    MAX_RETRIES = 3
    RETRY_DELAY = 0.5  # seconds

# Request validation schema
REQUEST_SCHEMA = {
    "type": "object",
    "properties": {
        "authEvent": {
            "type": "object",
            "properties": {
                "id": {"type": "string"},
                "timestamp": {"type": "string"},
                "location": {"type": "string", "enum": ["ENTRY", "EXIT"]}
            },
            "required": ["id", "timestamp", "location"]
        },
        "identityKey": {"type": "string"},
        "requestId": {"type": "string"},
        "storeId": {"type": "string"}
    },
    "required": ["authEvent", "identityKey", "requestId", "storeId"]
}

# Custom Exceptions
class IdentityServiceError(Exception):
    """Base exception for identity service"""
    pass

class InvalidInputError(IdentityServiceError):
    """Invalid input exception"""
    pass

class DatabaseError(IdentityServiceError):
    """Database operation exception"""
    pass

# Database connection management
class DynamoDBConnection:
    """DynamoDB connection manager"""
    _instance = None
    _tables = {}

    @classmethod
    def get_table(cls, table_name: str):
        """Get or create table connection"""
        if table_name not in cls._tables:
            if not cls._instance:
                cls._instance = boto3.resource('dynamodb')
            cls._tables[table_name] = cls._instance.Table(table_name)
        return cls._tables[table_name]

    @classmethod
    def close_connections(cls):
        """Close all connections"""
        cls._tables.clear()
        cls._instance = None

# Utility functions
@tracer.capture_method
def identity_decode(temp_identity_key: str) -> str:
    """
    Decode identity key using hex or base64
    
    Args:
        temp_identity_key: The identity key to decode
        
    Returns:
        str: Decoded value
        
    Raises:
        InvalidInputError: If decoding fails
    """
    try:
        # Try hex first
    # Check if it's a hex string
        if is_hex(temp_identity_key):
            logger.info("Input appears to be hexadecimal, returning as-is")
            return temp_identity_key
    except Exception:
        try:
            # Fallback to base64
            message_bytes = base64.b64decode(temp_identity_key)
            return message_bytes.decode('utf-8', errors='ignore')
        except Exception as e:
            raise InvalidInputError(f"Failed to decode identity key: {str(e)}")

@tracer.capture_method
def extract_recognition_token(decoded_value: str) -> str:
    """Extract recognition token from decoded value"""
    if len(decoded_value) < Config.MIN_DECODED_LENGTH:
        raise InvalidInputError(f"Decoded value too short: {len(decoded_value)} characters")
    return decoded_value[7:39]

# Database operations
class IdentityRepository:
    """Repository for identity operations"""

    @staticmethod
    @tracer.capture_method
    def create_identity_record(record: Dict[str, Any]) -> Dict[str, Any]:
        """Create new identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        try:
            table.put_item(
                Item=record,
                ConditionExpression='attribute_not_exists(recognitionToken)'
            )
            metrics.add_metric(name="IdentityRecordCreated", unit="Count", value=1)
            return {"new_shopper": True, "shopper_details": record["recognitionToken"]}
        except ClientError as e:
            if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
                return IdentityRepository.get_existing_record(record["recognitionToken"])
            raise DatabaseError(f"Failed to create record: {str(e)}")

    @staticmethod
    @tracer.capture_method
    def get_existing_record(recognition_token: str) -> Dict[str, Any]:
        """Retrieve existing identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        try:
            response = table.query(
                KeyConditionExpression=Key('recognitionToken').eq(recognition_token)
            )
            if response['Items']:
                return {"new_shopper": False, "shopper_details": response['Items'][0]}
            raise DatabaseError(f"Record not found: {recognition_token}")
        except ClientError as e:
            raise DatabaseError(f"Failed to retrieve record: {str(e)}")

    @staticmethod
    @tracer.capture_method
    def update_record(recognition_token: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
        """Update existing identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        update_expression = "SET " + ", ".join(f"#{k} = :{k}" for k in update_data.keys())
        expression_attribute_names = {f"#{k}": k for k in update_data.keys()}
        expression_attribute_values = {f":{k}": v for k, v in update_data.items()}

        try:
            response = table.update_item(
                Key={"recognitionToken": recognition_token},
                UpdateExpression=update_expression,
                ExpressionAttributeNames=expression_attribute_names,
                ExpressionAttributeValues=expression_attribute_values,
                ReturnValues="ALL_NEW"
            )
            return response['Attributes']
        except ClientError as e:
            raise DatabaseError(f"Failed to update record: {str(e)}")

# Request handlers
class IdentityService:
    """Identity service business logic"""

    @staticmethod
    @tracer.capture_method
    def handle_entry_event(request_body: Dict[str, Any], recognition_token: str) -> Dict[str, Any]:
        """Handle entry event"""
        identity_record = {
            "recognitionToken": recognition_token,
            "entry_auth_event_id": request_body['authEvent']['id'],
            "entry_time_stamp": request_body['authEvent']['timestamp'],
            "entry_event_location": request_body['authEvent']['location'],
            "identity_key": request_body['identityKey'],
            "entry_request_id": request_body['requestId'],
            "entry_store_id": request_body['storeId'],
            "loyalty_level": "None",
            "loyalty_spend": 0,
            "exit_time_stamp": "",
            "exit_auth_event_id": "",
            "exit_request_id": ""
        }

        result = IdentityRepository.create_identity_record(identity_record)

        return {
            "shopperDeviceId": "bcdf6897-a7b8-4074-b980-46bb5b737ba7",
            "visitorDetails": {
                "id": recognition_token,
                "type": "SHOPPER"
            }
        }

    @staticmethod
    @tracer.capture_method
    def handle_exit_event(request_body: Dict[str, Any], recognition_token: str) -> Dict[str, Any]:
        """Handle exit event"""
        update_data = {
            "exit_time_stamp": request_body['authEvent']['timestamp'],
            "exit_auth_event_id": request_body['authEvent']['id'],
            "exit_request_id": request_body['requestId']
        }

        return IdentityRepository.update_record(recognition_token, update_data)

# Lambda handler
@logger.inject_lambda_context
@tracer.capture_lambda_handler
@metrics.log_metrics
def lambda_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
    """
    Main Lambda handler
    """
    try:
        # Validate request
        validate_request_schema(REQUEST_SCHEMA, event['body'])
        request_body = json.loads(event['body'])

        # Process identity
        decoded_value = identity_decode(request_body['identityKey'])
        recognition_token = extract_recognition_token(decoded_value)

        # Handle event based on location
        if request_body['authEvent']['location'] == "ENTRY":
            response_data = IdentityService.handle_entry_event(request_body, recognition_token)
        else:
            response_data = IdentityService.handle_exit_event(request_body, recognition_token)

        return {
            "statusCode": 200,
            "headers": {
                "Content-Type": "application/json",
                "Access-Control-Allow-Origin": "*"
            },
            "body": json.dumps(response_data)
        }

    except jsonschema.exceptions.ValidationError as ve:
        logger.error("Schema validation error", exc_info=True)
        return {
            "statusCode": 400,
            "body": json.dumps({
                "reasonCode": "SCHEMA_VALIDATION_ERROR",
                "errorMsg": str(ve)
            })
        }
    except InvalidInputError as ie:
        logger.error("Invalid input error", exc_info=True)
        return {
            "statusCode": 400,
            "body": json.dumps({
                "reasonCode": "INVALID_INPUT",
                "errorMsg": str(ie)
            })
        }
    except DatabaseError as de:
        logger.error("Database error", exc_info=True)
        return {
            "statusCode": 500,
            "body": json.dumps({
                "reasonCode": "DATABASE_ERROR",
                "errorMsg": str(de)
            })
        }
    except Exception as e:
        logger.error("Unexpected error", exc_info=True)
        return {
            "statusCode": 500,
            "body": json.dumps({
                "reasonCode": "INTERNAL_ERROR",
                "errorMsg": "An internal error occurred"
            })
        }
    finally:
        DynamoDBConnection.close_connections()




Unit Tests

"""
Identity Service Lambda Handler
Handles shopper identity management with DynamoDB
"""

import json
import os
import base64
import logging
from typing import Dict, Any, Optional, Union
from datetime import datetime
import boto3
from boto3.dynamodb.conditions import Key
from botocore.exceptions import ClientError
from aws_xray_sdk.core import patch_all
import jsonschema
from aws_lambda_powertools import Logger, Metrics, Tracer
from aws_lambda_powertools.utilities.typing import LambdaContext
from aws_lambda_powertools.utilities.validation import validate_request_schema
from aws_lambda_powertools.utilities.data_classes import APIGatewayProxyEvent

# Initialize AWS X-Ray
patch_all()

# Initialize PowerTools
logger = Logger(service="IdentityService")
metrics = Metrics(namespace="IdentityService")
tracer = Tracer(service="IdentityService")

# Load configuration from AWS Parameter Store
def get_parameter(name: str) -> str:
    ssm = boto3.client('ssm')
    response = ssm.get_parameter(Name=name, WithDecryption=True)
    return response['Parameter']['Value']

def is_hex(s):
    """
    Check if a string is hexadecimal

    Args:
        s (str): String to check

    Returns:
        bool: True if string is hexadecimal, False otherwise
    """
    return all(c in '0123456789ABCDEFabcdef' for c in s)

class Config:
    """Configuration management"""
    SHOPPER_TABLE = os.environ.get("shopperInfoTable")
    IDENTITY_TABLE = os.environ.get("identityInfoTable")
    FAILED_IDENTITY_TABLE = os.environ.get("failedIdentityInfoTable")
    MIN_DECODED_LENGTH = 39
    MAX_RETRIES = 3
    RETRY_DELAY = 0.5  # seconds

# Request validation schema
REQUEST_SCHEMA = {
    "type": "object",
    "properties": {
        "authEvent": {
            "type": "object",
            "properties": {
                "id": {"type": "string"},
                "timestamp": {"type": "string"},
                "location": {"type": "string", "enum": ["ENTRY", "EXIT"]}
            },
            "required": ["id", "timestamp", "location"]
        },
        "identityKey": {"type": "string"},
        "requestId": {"type": "string"},
        "storeId": {"type": "string"}
    },
    "required": ["authEvent", "identityKey", "requestId", "storeId"]
}

# Custom Exceptions
class IdentityServiceError(Exception):
    """Base exception for identity service"""
    pass

class InvalidInputError(IdentityServiceError):
    """Invalid input exception"""
    pass

class DatabaseError(IdentityServiceError):
    """Database operation exception"""
    pass

# Database connection management
class DynamoDBConnection:
    """DynamoDB connection manager"""
    _instance = None
    _tables = {}

    @classmethod
    def get_table(cls, table_name: str):
        """Get or create table connection"""
        if table_name not in cls._tables:
            if not cls._instance:
                cls._instance = boto3.resource('dynamodb')
            cls._tables[table_name] = cls._instance.Table(table_name)
        return cls._tables[table_name]

    @classmethod
    def close_connections(cls):
        """Close all connections"""
        cls._tables.clear()
        cls._instance = None

# Utility functions
@tracer.capture_method
def identity_decode(temp_identity_key: str) -> str:
    """
    Decode identity key using hex or base64
    
    Args:
        temp_identity_key: The identity key to decode
        
    Returns:
        str: Decoded value
        
    Raises:
        InvalidInputError: If decoding fails
    """
    try:
        # Try hex first
        # Check if it's a hex string
        if is_hex(temp_identity_key):
            logger.info("Input appears to be hexadecimal, returning as-is")
            return temp_identity_key
    except Exception:
        try:
            # Fallback to base64
            message_bytes = base64.b64decode(temp_identity_key)
            return message_bytes.decode('utf-8', errors='ignore')
        except Exception as e:
            raise InvalidInputError(f"Failed to decode identity key: {str(e)}")

@tracer.capture_method
def extract_recognition_token(decoded_value: str) -> str:
    """Extract recognition token from decoded value"""
    if len(decoded_value) < Config.MIN_DECODED_LENGTH:
        raise InvalidInputError(f"Decoded value too short: {len(decoded_value)} characters")
    return decoded_value[7:39]

# Database operations
class IdentityRepository:
    """Repository for identity operations"""

    @staticmethod
    @tracer.capture_method
    def create_identity_record(record: Dict[str, Any]) -> Dict[str, Any]:
        """Create new identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        try:
            table.put_item(
                Item=record,
                ConditionExpression='attribute_not_exists(recognitionToken)'
            )
            metrics.add_metric(name="IdentityRecordCreated", unit="Count", value=1)
            return {"new_shopper": True, "shopper_details": record["recognitionToken"]}
        except ClientError as e:
            if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
                return IdentityRepository.get_existing_record(record["recognitionToken"])
            raise DatabaseError(f"Failed to create record: {str(e)}")

    @staticmethod
    @tracer.capture_method
    def get_existing_record(recognition_token: str) -> Dict[str, Any]:
        """Retrieve existing identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        try:
            response = table.query(
                KeyConditionExpression=Key('recognitionToken').eq(recognition_token)
            )
            if response['Items']:
                return {"new_shopper": False, "shopper_details": response['Items'][0]}
            raise DatabaseError(f"Record not found: {recognition_token}")
        except ClientError as e:
            raise DatabaseError(f"Failed to retrieve record: {str(e)}")

    @staticmethod
    @tracer.capture_method
    def update_record(recognition_token: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
        """Update existing identity record"""
        table = DynamoDBConnection.get_table(Config.IDENTITY_TABLE)

        update_expression = "SET " + ", ".join(f"#{k} = :{k}" for k in update_data.keys())
        expression_attribute_names = {f"#{k}": k for k in update_data.keys()}
        expression_attribute_values = {f":{k}": v for k, v in update_data.items()}

        try:
            response = table.update_item(
                Key={"recognitionToken": recognition_token},
                UpdateExpression=update_expression,
                ExpressionAttributeNames=expression_attribute_names,
                ExpressionAttributeValues=expression_attribute_values,
                ReturnValues="ALL_NEW"
            )
            return response['Attributes']
        except ClientError as e:
            raise DatabaseError(f"Failed to update record: {str(e)}")

# Request handlers
class IdentityService:
    """Identity service business logic"""

    @staticmethod
    @tracer.capture_method
    def handle_entry_event(request_body: Dict[str, Any], recognition_token: str) -> Dict[str, Any]:
        """Handle entry event"""
        identity_record = {
            "recognitionToken": recognition_token,
            "entry_auth_event_id": request_body['authEvent']['id'],
            "entry_time_stamp": request_body['authEvent']['timestamp'],
            "entry_event_location": request_body['authEvent']['location'],
            "identity_key": request_body['identityKey'],
            "entry_request_id": request_body['requestId'],
            "entry_store_id": request_body['storeId'],
            "loyalty_level": "None",
            "loyalty_spend": 0,
            "exit_time_stamp": "",
            "exit_auth_event_id": "",
            "exit_request_id": ""
        }

        result = IdentityRepository.create_identity_record(identity_record)

        return {
            "shopperDeviceId": "bcdf6897-a7b8-4074-b980-46bb5b737ba7",
            "visitorDetails": {
                "id": recognition_token,
                "type": "SHOPPER"
            }
        }

    @staticmethod
    @tracer.capture_method
    def handle_exit_event(request_body: Dict[str, Any], recognition_token: str) -> Dict[str, Any]:
        """Handle exit event"""
        update_data = {
            "exit_time_stamp": request_body['authEvent']['timestamp'],
            "exit_auth_event_id": request_body['authEvent']['id'],
            "exit_request_id": request_body['requestId']
        }

        return IdentityRepository.update_record(recognition_token, update_data)

# Lambda handler
@logger.inject_lambda_context
@tracer.capture_lambda_handler
@metrics.log_metrics
def lambda_handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
    """
    Main Lambda handler
    """
    try:
        # Validate request
        validate_request_schema(REQUEST_SCHEMA, event['body'])
        request_body = json.loads(event['body'])

        # Process identity
        decoded_value = identity_decode(request_body['identityKey'])
        recognition_token = extract_recognition_token(decoded_value)

        # Handle event based on location
        if request_body['authEvent']['location'] == "ENTRY":
            response_data = IdentityService.handle_entry_event(request_body, recognition_token)
        else:
            response_data = IdentityService.handle_exit_event(request_body, recognition_token)

        return {
            "statusCode": 200,
            "headers": {
                "Content-Type": "application/json",
                "Access-Control-Allow-Origin": "*"
            },
            "body": json.dumps(response_data)
        }

    except jsonschema.exceptions.ValidationError as ve:
        logger.error("Schema validation error", exc_info=True)
        return {
            "statusCode": 400,
            "body": json.dumps({
                "reasonCode": "SCHEMA_VALIDATION_ERROR",
                "errorMsg": str(ve)
            })
        }
    except InvalidInputError as ie:
        logger.error("Invalid input error", exc_info=True)
        return {
            "statusCode": 400,
            "body": json.dumps({
                "reasonCode": "INVALID_INPUT",
                "errorMsg": str(ie)
            })
        }
    except DatabaseError as de:
        logger.error("Database error", exc_info=True)
        return {
            "statusCode": 500,
            "body": json.dumps({
                "reasonCode": "DATABASE_ERROR",
                "errorMsg": str(de)
            })
        }
    except Exception as e:
        logger.error("Unexpected error", exc_info=True)
        return {
            "statusCode": 500,
            "body": json.dumps({
                "reasonCode": "INTERNAL_ERROR",
                "errorMsg": "An internal error occurred"
            })
        }
    finally:
        DynamoDBConnection.close_connections()


JavaScript Code

/**
 * Identity Service Lambda Handler
 * Handles shopper identity management with DynamoDB
 */

const AWS = require('aws-sdk');
const AWSXRay = require('aws-xray-sdk');
const jsonschema = require('jsonschema');
const { Logger, Metrics, Tracer } = require('aws-lambda-powertools');

// Initialize AWS X-Ray
AWSXRay.captureAWS(AWS);

// Initialize PowerTools
const logger = new Logger({ serviceName: 'IdentityService' });
const metrics = new Metrics({ namespace: 'IdentityService' });
const tracer = new Tracer({ serviceName: 'IdentityService' });

// Load configuration from environment variables
const Config = {
    SHOPPER_TABLE: process.env.shopperInfoTable,
    IDENTITY_TABLE: process.env.identityInfoTable,
    FAILED_IDENTITY_TABLE: process.env.failedIdentityInfoTable,
    MIN_DECODED_LENGTH: 39,
    MAX_RETRIES: 3,
    RETRY_DELAY: 500 // milliseconds
};

// Request validation schema
const REQUEST_SCHEMA = {
    type: 'object',
    properties: {
        authEvent: {
            type: 'object',
            properties: {
                id: { type: 'string' },
                timestamp: { type: 'string' },
                location: { type: 'string', enum: ['ENTRY', 'EXIT'] }
            },
            required: ['id', 'timestamp', 'location']
        },
        identityKey: { type: 'string' },
        requestId: { type: 'string' },
        storeId: { type: 'string' }
    },
    required: ['authEvent', 'identityKey', 'requestId', 'storeId']
};

// Custom Errors
class IdentityServiceError extends Error {
    constructor(message) {
        super(message);
        this.name = 'IdentityServiceError';
    }
}

class InvalidInputError extends IdentityServiceError {
    constructor(message) {
        super(message);
        this.name = 'InvalidInputError';
    }
}

class DatabaseError extends IdentityServiceError {
    constructor(message) {
        super(message);
        this.name = 'DatabaseError';
    }
}

// Database connection management
class DynamoDBConnection {
    static _instance = null;
    static _tables = {};

    static getTable(tableName) {
        if (!this._tables[tableName]) {
            if (!this._instance) {
                this._instance = new AWS.DynamoDB.DocumentClient();
            }
            this._tables[tableName] = this._instance;
        }
        return this._tables[tableName];
    }

    static closeConnections() {
        this._tables = {};
        this._instance = null;
    }
}

// Utility functions
function isHex(str) {
    return /^[0-9A-Fa-f]+$/.test(str);
}

function identityDecode(tempIdentityKey) {
    try {
        if (isHex(tempIdentityKey)) {
            logger.info('Input appears to be hexadecimal, returning as-is');
            return tempIdentityKey;
        }
        // Fallback to base64
        return Buffer.from(tempIdentityKey, 'base64').toString('utf-8');
    } catch (error) {
        throw new InvalidInputError(`Failed to decode identity key: ${error.message}`);
    }
}

function extractRecognitionToken(decodedValue) {
    if (decodedValue.length < Config.MIN_DECODED_LENGTH) {
        throw new InvalidInputError(`Decoded value too short: ${decodedValue.length} characters`);
    }
    return decodedValue.substring(7, 39);
}

// Database operations
class IdentityRepository {
    static async createIdentityRecord(record) {
        const table = DynamoDBConnection.getTable(Config.IDENTITY_TABLE);

        try {
            await table.put({
                TableName: Config.IDENTITY_TABLE,
                Item: record,
                ConditionExpression: 'attribute_not_exists(recognitionToken)'
            }).promise();

            metrics.addMetric('IdentityRecordCreated', 'Count', 1);
            return { new_shopper: true, shopper_details: record.recognitionToken };
        } catch (error) {
            if (error.code === 'ConditionalCheckFailedException') {
                return this.getExistingRecord(record.recognitionToken);
            }
            throw new DatabaseError(`Failed to create record: ${error.message}`);
        }
    }

    static async getExistingRecord(recognitionToken) {
        const table = DynamoDBConnection.getTable(Config.IDENTITY_TABLE);

        try {
            const response = await table.query({
                TableName: Config.IDENTITY_TABLE,
                KeyConditionExpression: 'recognitionToken = :token',
                ExpressionAttributeValues: {
                    ':token': recognitionToken
                }
            }).promise();

            if (response.Items && response.Items.length > 0) {
                return { new_shopper: false, shopper_details: response.Items[0] };
            }
            throw new DatabaseError(`Record not found: ${recognitionToken}`);
        } catch (error) {
            throw new DatabaseError(`Failed to retrieve record: ${error.message}`);
        }
    }

    static async updateRecord(recognitionToken, updateData) {
        const table = DynamoDBConnection.getTable(Config.IDENTITY_TABLE);

        const updateExpression = 'SET ' + Object.keys(updateData).map(key => `#${key} = :${key}`).join(', ');
        const expressionAttributeNames = Object.keys(updateData).reduce((acc, key) => {
            acc[`#${key}`] = key;
            return acc;
        }, {});
        const expressionAttributeValues = Object.keys(updateData).reduce((acc, key) => {
            acc[`:${key}`] = updateData[key];
            return acc;
        }, {});

        try {
            const response = await table.update({
                TableName: Config.IDENTITY_TABLE,
                Key: { recognitionToken },
                UpdateExpression: updateExpression,
                ExpressionAttributeNames: expressionAttributeNames,
                ExpressionAttributeValues: expressionAttributeValues,
                ReturnValues: 'ALL_NEW'
            }).promise();

            return response.Attributes;
        } catch (error) {
            throw new DatabaseError(`Failed to update record: ${error.message}`);
        }
    }
}

// Request handlers
class IdentityService {
    static async handleEntryEvent(requestBody, recognitionToken) {
        const identityRecord = {
            recognitionToken,
            entry_auth_event_id: requestBody.authEvent.id,
            entry_time_stamp: requestBody.authEvent.timestamp,
            entry_event_location: requestBody.authEvent.location,
            identity_key: requestBody.identityKey,
            entry_request_id: requestBody.requestId,
            entry_store_id: requestBody.storeId,
            loyalty_level: 'None',
            loyalty_spend: 0,
            exit_time_stamp: '',
            exit_auth_event_id: '',
            exit_request_id: ''
        };

        const result = await IdentityRepository.createIdentityRecord(identityRecord);

        return {
            shopperDeviceId: 'bcdf6897-a7b8-4074-b980-46bb5b737ba7',
            visitorDetails: {
                id: recognitionToken,
                type: 'SHOPPER'
            }
        };
    }

    static async handleExitEvent(requestBody, recognitionToken) {
        const updateData = {
            exit_time_stamp: requestBody.authEvent.timestamp,
            exit_auth_event_id: requestBody.authEvent.id,
            exit_request_id: requestBody.requestId
        };

        return IdentityRepository.updateRecord(recognitionToken, updateData);
    }
}

// Lambda handler
exports.handler = async (event, context) => {
    try {
        // Validate request
        const requestBody = JSON.parse(event.body);
        const validationResult = jsonschema.validate(requestBody, REQUEST_SCHEMA);
        if (!validationResult.valid) {
            throw new InvalidInputError(validationResult.errors.map(e => e.stack).join(', '));
        }

        // Process identity
        const decodedValue = identityDecode(requestBody.identityKey);
        const recognitionToken = extractRecognitionToken(decodedValue);

        // Handle event based on location
        let responseData;
        if (requestBody.authEvent.location === 'ENTRY') {
            responseData = await IdentityService.handleEntryEvent(requestBody, recognitionToken);
        } else {
            responseData = await IdentityService.handleExitEvent(requestBody, recognitionToken);
        }

        return {
            statusCode: 200,
            headers: {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            body: JSON.stringify(responseData)
        };
    } catch (error) {
        logger.error('Error in lambda handler', { error });

        if (error instanceof InvalidInputError) {
            return {
                statusCode: 400,
                body: JSON.stringify({
                    reasonCode: 'INVALID_INPUT',
                    errorMsg: error.message
                })
            };
        } else if (error instanceof DatabaseError) {
            return {
                statusCode: 500,
                body: JSON.stringify({
                    reasonCode: 'DATABASE_ERROR',
                    errorMsg: error.message
                })
            };
        } else {
            return {
                statusCode: 500,
                body: JSON.stringify({
                    reasonCode: 'INTERNAL_ERROR',
                    errorMsg: 'An internal error occurred'
                })
            };
        }
    } finally {
        DynamoDBConnection.closeConnections();
    }
};