> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/azfar-imtiaz/PayPulse-Cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# S3 buckets

> S3 bucket structure and Lambda deployment via S3 in PayPulse Cloud.

PayPulse Cloud uses two S3 buckets, both defined in `aws-infra-terraform/s3.tf`.

<CardGroup cols={2}>
  <Card title="rental-invoices-bucket" icon="file-invoice">
    Stores all invoice files — rental PDFs and retail HTML — uploaded by ingestion Lambda functions.
  </Card>

  <Card title="wallenstam-lambda-bucket" icon="code">
    Stores zipped Lambda source code. Uploading a new zip triggers a function update.
  </Card>
</CardGroup>

## rental-invoices-bucket

This bucket holds every invoice file processed by PayPulse. It is configured with versioning, AES-256 server-side encryption, and full public access blocking.

```hcl theme={null}
resource "aws_s3_bucket" "rental_invoices" {
  bucket = var.invoices_bucket_name   # default: "rental-invoices-bucket"
}

resource "aws_s3_bucket_versioning" "rental_invoices" {
  bucket = aws_s3_bucket.rental_invoices.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "rental_invoices" {
  bucket = aws_s3_bucket.rental_invoices.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "rental_invoices" {
  bucket                  = aws_s3_bucket.rental_invoices.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
```

### Path structure

All invoices are stored under the `invoices/` prefix. The path encodes the user ID, invoice type, and (for retail) the sub-category and vendor.

**Rental invoices (PDF)**

```
invoices/{user_id}/rental/invoice_0001.pdf
```

**Retail invoices (HTML)**

```
invoices/{user_id}/retail/{sub_type}/{vendor}_{date}_{hash}.html
```

Where `{sub_type}` is one of:

| Sub-type        | Description                                 |
| --------------- | ------------------------------------------- |
| `food-delivery` | Restaurant and food delivery orders         |
| `clothing`      | Clothing and fashion purchases              |
| `technology`    | Electronics and tech products               |
| `subscriptions` | Streaming services, memberships, etc.       |
| `grocery`       | Grocery store purchases                     |
| `utility`       | Electricity, water, internet bills          |
| `miscellaneous` | Other retail purchases                      |
| `travel`        | Flights, trains, buses, and other transport |

User IDs are UUIDs prefixed with `user_`, generated at signup (e.g., `user_a1b2c3d4-...`).

## wallenstam-lambda-bucket

This bucket holds the deployment artifacts for all zip-deployed Lambda functions.

```hcl theme={null}
resource "aws_s3_bucket" "lambda_bucket" {
  bucket = var.lambda_functions_bucket_name   # default: "wallenstam-lambda-bucket"
}

resource "aws_s3_bucket_versioning" "lambda_bucket_versioning" {
  bucket = aws_s3_bucket.lambda_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}
```

A bucket policy grants `lambda.amazonaws.com` `s3:GetObject` access so Lambda can pull deployment packages:

```hcl theme={null}
resource "aws_s3_bucket_policy" "lambda_bucket_policy" {
  bucket = aws_s3_bucket.lambda_bucket.id
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect    = "Allow",
      Principal = { Service = "lambda.amazonaws.com" },
      Action    = "s3:GetObject",
      Resource  = "${aws_s3_bucket.lambda_bucket.arn}/*"
    }]
  })
}
```

### Versioning and deployment

Each Lambda function reads its source code from an S3 object. S3 versioning is enabled so every upload creates a new object version. When a zip file is updated and `terraform apply` is run, Terraform detects the new S3 object version and updates the Lambda function accordingly.

Functions deployed this way include: `login_user`, `signup_user`, `fetch_invoices`, `fetch_latest_invoice`, `fetch_retail_invoices`, `get_rental_invoice`, `get_rental_invoices`, `get_user_profile`, `gmail_store_tokens`, `delete_user`, and `send_invoice_notification`.

<Note>
  The `parse_invoice` function is deployed as a Docker image pushed to an ECR repository, not as a zip file in this bucket.
</Note>

## S3 event triggers

Uploads to `rental-invoices-bucket` trigger Lambda functions automatically via S3 event notifications:

```hcl theme={null}
resource "aws_s3_bucket_notification" "invoice_upload_trigger" {
  bucket = aws_s3_bucket.rental_invoices.id

  # PDF upload triggers parse_invoice
  lambda_function {
    lambda_function_arn = module.lambdas.parse_invoice_arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "invoices/"
    filter_suffix       = ".pdf"
  }

  # HTML upload triggers parse_retail_invoice
  lambda_function {
    lambda_function_arn = module.lambdas.parse_retail_invoice_arn
    events              = ["s3:ObjectCreated:*"]
    filter_prefix       = "invoices/"
    filter_suffix       = ".html"
  }
}
```

| Event                | Filter            | Lambda triggered       |
| -------------------- | ----------------- | ---------------------- |
| `s3:ObjectCreated:*` | `invoices/*.pdf`  | `parse_invoice`        |
| `s3:ObjectCreated:*` | `invoices/*.html` | `parse_retail_invoice` |

When `fetch_latest_invoice` or `fetch_invoices` uploads a PDF to `invoices/{user_id}/rental/`, S3 fires the notification, which invokes `parse_invoice`. That function parses the PDF and writes the result to DynamoDB, which in turn triggers `send_invoice_notification` via the DynamoDB stream.
