Skip to content

PDF invoice is not generated in my S3 bucket

0

I wrote a lambda function in using Python where a customer makes an order and the receipt will be generated in form of PDF in S3 This is the code in my lambda function import json import boto3 from datetime import datetime, timedelta import uuid from io import BytesIO import base64

For PDF generation, we'll use a simpler approach with HTML to PDF

Since ReportLab requires additional layers in Lambda

dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') lambda_client = boto3.client('lambda')

orders_table = dynamodb.Table('TechHub-Orders') customers_table = dynamodb.Table('TechHub-Customers')

def lambda_handler(event, context): """ Generate PDF invoice for order """ try: order_id = event.get('order_id')

    if not order_id:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'Order ID is required'})
        }
    
    # Get order details
    order_response = orders_table.get_item(Key={'order_id': order_id})
    if 'Item' not in order_response:
        return {
            'statusCode': 404,
            'body': json.dumps({'error': 'Order not found'})
        }
    
    order = order_response['Item']
    
    # Get customer details
    customer_response = customers_table.get_item(Key={'customer_id': order['customer_id']})
    customer = customer_response.get('Item', {})
    
    # Generate HTML invoice
    html_content = generate_invoice_html(order, customer)
    
    # Convert HTML to PDF (using weasyprint approach for Lambda)
    pdf_content = html_to_pdf_simple(html_content)
    
    # Save to S3
    s3_key = f"invoices/{order_id}/{order_id}_invoice.pdf"
    bucket_name = 'techhub-do5'  # Replace with your bucket name
    
    s3.put_object(
        Bucket=bucket_name,
        Key=s3_key,
        Body=pdf_content,
        ContentType='application/pdf'
    )
    
    # Generate pre-signed URL
    pdf_url = s3.generate_presigned_url(
        'get_object',
        Params={'Bucket': bucket_name, 'Key': s3_key},
        ExpiresIn=86400  # 24 hours
    )
    
    # Trigger notification
    send_invoice_notification(order, customer, pdf_url)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'invoice_url': pdf_url,
            'message': 'PDF Invoice generated successfully',
            'order_id': order_id
        })
    }
    
except Exception as e:
    print(f"Invoice generation error: {str(e)}")
    import traceback
    traceback.print_exc()
    return {
        'statusCode': 500,
        'body': json.dumps({'error': 'Failed to generate PDF invoice', 'details': str(e)})
    }

def generate_invoice_html(order, customer): """ Generate professional HTML invoice """ # Calculate totals subtotal = float(order.get('total_amount', 0)) tax_rate = 0.075 # 7.5% VAT in Nigeria tax_amount = subtotal * tax_rate total_amount = subtotal + tax_amount

html_content = f"""
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Invoice - {order['order_id'][:8].upper()}</title> <style> body {{ font-family: 'Arial', sans-serif; margin: 0; padding: 20px; color: #333; line-height: 1.6; }} .header {{ background: linear-gradient(135deg, #1a5490, #2d71b8); color: white; padding: 30px; text-align: center; margin-bottom: 30px; }} .company-name {{ font-size: 28px; font-weight: bold; margin-bottom: 5px; }} .company-tagline {{ font-size: 14px; opacity: 0.9; }} .invoice-title {{ font-size: 24px; font-weight: bold; text-align: center; margin: 20px 0; color: #1a5490; }} .invoice-details {{ display: flex; justify-content: space-between; margin-bottom: 30px; }} .invoice-info, .customer-info {{ width: 45%; }} .invoice-info h3, .customer-info h3 {{ color: #1a5490; border-bottom: 2px solid #e0e0e0; padding-bottom: 5px; }} .items-table {{ width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }} .items-table th {{ background: #1a5490; color: white; padding: 15px; text-align: left; }} .items-table td {{ padding: 12px 15px; border-bottom: 1px solid #e0e0e0; }} .items-table tr:nth-child(even) {{ background-color: #f9f9f9; }} .totals {{ float: right; width: 300px; margin-top: 20px; }} .totals table {{ width: 100%; border-collapse: collapse; }} .totals td {{ padding: 8px 15px; border-bottom: 1px solid #e0e0e0; }} .totals .total-row {{ background: #1a5490; color: white; font-weight: bold; font-size: 16px; }} .footer {{ margin-top: 50px; padding: 20px; background: #f8f9fa; border-radius: 5px; text-align: center; }} .payment-terms {{ margin: 30px 0; padding: 20px; background: #e8f4fd; border-left: 4px solid #1a5490; }} .currency {{ font-weight: bold; }} </style> </head> <body> <div class="header"> <div class="company-name">TECHHUB NIGERIA</div> <div class="company-tagline">Technology Solutions & Services</div> <div style="margin-top: 10px; font-size: 12px;"> Lagos, Nigeria | Email: | </div> </div>
<div class="invoice-title">INVOICE</div>

<div class="invoice-details">
    <div class="invoice-info">
        <h3>Invoice Details</h3>
        <p><strong>Invoice Number:</strong> {order['order_id'][:8].upper()}</p>
        <p><strong>Date:</strong> {datetime.utcnow().strftime('%B %d, %Y')}</p>
        <p><strong>Due Date:</strong> {(datetime.utcnow() + timedelta(days=30)).strftime('%B %d, %Y')}</p>
        <p><strong>Order ID:</strong> {order['order_id']}</p>
    </div>
    
    <div class="customer-info">
        <h3>Bill To</h3>
        <p><strong>{customer.get('name', 'N/A')}</strong></p>
        <p>Email: {customer.get('email', 'N/A')}</p>
        <p>Phone: {customer.get('phone', 'N/A')}</p>
        <p>Customer ID: {customer.get('customer_id', 'N/A')}</p>
    </div>
</div>

<table class="items-table">
    <thead>
        <tr>
            <th>Description</th>
            <th>Quantity</th>
            <th>Unit Price</th>
            <th>Total</th>
        </tr>
    </thead>
    <tbody>

"""

# Add items
for item in order.get('items', []):
    html_content += f"""
        <tr>
            <td>{item.get('product_name', 'N/A')}</td>
            <td>{item.get('quantity', 0)}</td>
            <td class="currency">₦{float(item.get('unit_price', 0)):,.2f}</td>
            <td class="currency">₦{float(item.get('total_price', 0)):,.2f}</td>
        </tr>
    """

html_content += f"""
    </tbody>
</table>

<div class="totals">
    <table>
        <tr>
            <td>Subtotal:</td>
            <td class="currency">₦{subtotal:,.2f}</td>
        </tr>
        <tr>
            <td>VAT (7.5%):</td>
            <td class="currency">₦{tax_amount:,.2f}</td>
        </tr>
        <tr class="total-row">
            <td>Total Amount:</td>
            <td class="currency">₦{total_amount:,.2f}</td>
        </tr>
    </table>
</div>

<div style="clear: both;"></div>

<div class="payment-terms">
    <h3>Payment Terms & Information</h3>
    <p><strong>Payment Due:</strong> Net 30 days from invoice date</p>
    <p><strong>Bank Transfer Details:</strong></p>
    <p>Account Name: TechHub Nigeria Limited<br>
    Account Number: 8<br>
    Bank: Access Bank Nigeria<br>
    Sort Code: </p>
</div>

<div class="footer">
    <p><strong>Thank you for choosing TechHub Nigeria!</strong></p>
    <p>For support or inquiries, contact us at suppog</p>
    <p style="font-size: 11px; color: #666;">
        This is an automatically generated invoice. Please retain this document for your records.
    </p>
</div>
</body> </html> """
return html_content

def html_to_pdf_simple(html_content): """ Convert HTML to PDF using a simple approach For production, you'd want to use a proper PDF generation service """ # This is a simplified approach - for production use weasyprint or similar # For now, we'll save as HTML and let the client handle PDF conversion # or use a PDF generation service

try:
    # For demo purposes, we'll use pdfkit if available in Lambda layer
    # Otherwise, save as HTML with PDF-like styling
    import pdfkit
    pdf_content = pdfkit.from_string(html_content, False)
    return pdf_content
except ImportError:
    # Fallback: return HTML content for now
    # In production, add a PDF generation layer
    print("PDF generation library not available, returning HTML")
    return html_content.encode('utf-8')

def send_invoice_notification(order, customer, pdf_url): """ Send invoice notification to customer """ try: # Trigger notification Lambda payload = { 'type': 'invoice_ready', 'customer_email': customer.get('email'), 'customer_phone': customer.get('phone'), 'data': { 'order_id': order['order_id'], 'invoice_url': pdf_url, 'total_amount': float(order.get('total_amount', 0)) } def send_invoice_notification(email, phone, data): """ Send invoice ready notification with PDF attachment """ if email: try: # Email with invoice link html_body = f""" <html> <body> <h2>Your TechHub Nigeria Invoice is Ready!</h2> <p>Dear Valued Customer,</p> <p>Your invoice for Order #{data.get('order_id', '')[:8].upper()} is now ready.</p> <p><strong>Total Amount:</strong> ₦{data.get('total_amount', 0):,.2f}</p> <p> <a href="{data.get('invoice_url', '')}" style="background-color: #1a5490; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;"> Download Invoice PDF </a> </p> <p>Payment is due within 30 days. Thank you for your business!</p> <p>Best regards,<br>TechHub Nigeria Team</p> </body> </html> """

        ses.send_email(
            Source=',  # Replace with your verified email
            Destination={'ToAddresses': [email]},
            Message={
                'Subject': {'Data': f'Invoice Ready - Order #{data.get("order_id", "")[:8].upper()}'},
                'Body': {
                    'Html': {'Data': html_body},
                    'Text': {'Data': f'Your invoice is ready! Download: {data.get("invoice_url", "")}'}
                }
            }
        )
    except Exception as e:
        print(f"Email send error: {e}")

if phone:
    try:
        sns.publish(
            PhoneNumber=phone,
            Message=f'TechHub: Invoice ready for Order #{data.get("order_id", "")[:8]}. Total: ₦{data.get("total_amount", 0):,.2f}. Check your email for PDF.'
        )
    except Exception as e:
        print(f"SMS send error: {e}")
    }
    
    lambda_client.invoke(
        FunctionName='techhub-notifications',
        InvocationType='Event',
        Payload=json.dumps(payload)
    )
    
except Exception as e:
    print(f"Notification error: {str(e)}")
1 Answer
0

Based on your code, there are several issues that could be preventing the PDF invoice from being generated in your S3 bucket:

  1. The most critical issue is in your html_to_pdf_simple function. You're trying to use pdfkit which requires external dependencies like wkhtmltopdf. These aren't available in the standard Lambda environment unless you've added them as a Lambda layer. When the import fails, your fallback is to simply return the HTML content encoded as bytes, not an actual PDF.

  2. Your error handling might be hiding the actual error. While you're printing the error message and traceback, you should check CloudWatch logs to see what specific error is occurring.

  3. The send_invoice_notification function has a nested function definition inside it, which would cause a syntax error. This nested function is also trying to use ses and sns clients that aren't defined in your code.

Here are some recommendations to fix the issues:

  1. For PDF generation in Lambda, consider one of these approaches:
  • Use a Lambda layer that includes wkhtmltopdf and pdfkit
  • Use a simpler PDF generation library like ReportLab that has fewer dependencies
  • Use AWS services like API Gateway to call a containerized service that can handle PDF generation
  • Consider using a third-party PDF generation service
  1. Check your Lambda function's IAM permissions to ensure it has proper access to write to the S3 bucket.

  2. Verify that the DynamoDB tables exist and that your Lambda function has permissions to read from them.

  3. Fix the nested function definition in send_invoice_notification.

  4. Increase your Lambda timeout if the PDF generation is taking longer than expected.

  5. Add more detailed logging throughout your code to identify exactly where the process is failing.

To debug this issue, I recommend adding explicit logging statements before and after each major operation (retrieving data from DynamoDB, generating HTML, converting to PDF, uploading to S3) to pinpoint where the process is breaking down.
Sources
AWS Lambda Not Writing to DynamoDB After S3 Upload Trigger | AWS re:Post
Define Lambda function handler in Python - AWS Lambda
Create a serverless file-processing app - AWS Lambda

answered 9 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.