efrilmandClaude Opus 5.5 4b3beaed41 feat(loyalty): pay orders with EnakPoint at the cashier
Adds paying with the EnakPoint method (docs/prd-point-coin.md F9, K7,
PC-305).

POST /payments with the EnakPoint method now takes points and the
customer's payment code and goes through PointPaymentProcessor instead of
the generic path, which would record a payment without taking any balance.
After checking the order, its customer (not walk-in, active), the outlet
(accepts EnakPoint, minimum) and the method, it redeems the code, then in
one transaction locks the order row and the wallet, recomputes the F9
limits from fresh data, inserts the payment with points_used and the frozen
point_value, writes the PAYMENT ledger row (key payment:{id}, the outlet,
the cashier) and updates the order. The limits are
min(balance, floor(min(remaining, total × max_payment_percent / 100 − paid
with EnakPoint) / point_value)) in cents, so EnakPoint never pays more than
what is left and gives no change.

Unlike the generic CreatePayment, which always marks the order paid, an
EnakPoint payment leaves it partial with the right remaining amount until
it is settled, so the rest can be paid in cash. Settling it triggers
earning, whose basis leaves out the EnakPoint part. Splitting with the
EnakPoint method is refused. Refusals answer 400. The payment response
carries points_used and point_value for the receipt.

GET /orders/:id/point-payment/preview returns eligibility, balance, point
value and the maximum for the use-maximum button.

The payment and order repositories write outside transactions, so this
path uses its own repository that joins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:46:31 +07:00
2026-06-19 13:31:33 +07:00
2026-07-09 22:11:03 +07:00
2026-07-09 22:19:53 +07:00
2025-07-18 20:10:29 +07:00
2025-07-30 23:18:20 +07:00
2024-05-28 14:14:55 +07:00
2026-07-09 22:11:03 +07:00
2024-07-23 09:36:08 +07:00
2026-07-09 22:43:01 +07:00
2025-07-30 23:18:20 +07:00
2025-07-18 20:10:29 +07:00
2025-07-30 23:18:20 +07:00
2026-05-12 18:46:12 +07:00
2024-05-28 14:14:55 +07:00
2025-08-03 00:34:25 +07:00
2026-08-13 14:38:28 +07:00

Go
Backend Template

Clean architecture based backend template in Go.

Makefile

Makefile requires installed dependecies:

$ make

Usage: make [command] [ENV=staging|production]

Commands:
 run                           Run server (default: staging)
 run ENV=production            Run server with production config

 rename-project name={name}    Rename project

 build-http                    Build http server

 migration-create name={name}  Create migration
 migration-up                  Up migrations
 migration-up ENV=production   Up migrations (production DB)
 migration-down                Down last migration

 docker-up                     Up docker services
 docker-down                   Down docker services

 fmt                           Format source code
 test                          Run unit tests

HTTP Server

The server takes no CLI flags. It reads ENV_MODE and loads the matching YAML file from infra/ — see Running the Application for details.

# Build, then start with the staging config (default)
$ go build -o ./bin/http-server ./cmd/server/main.go
$ ENV_MODE=staging ./bin/http-server

# Start with the production config
$ ENV_MODE=production ./bin/http-server

API Docs

License

This project is licensed under the MIT License.

Apskel POS Backend

A SaaS Point of Sale (POS) Restaurant System backend built with clean architecture principles in Go.

Architecture Overview

This application follows a clean architecture pattern with clear separation of concerns:

Handler → Service → Processor → Repository

Layers

  1. Contract Package (internal/contract/)

    • Request/Response DTOs for API communication
    • Contains JSON tags for serialization
    • Input validation tags
  2. Handler Layer (internal/handler/)

    • HTTP request/response handling
    • Request validation using go-playground/validator
    • Route definitions and middleware
    • Transforms contracts to/from services
  3. Service Layer (internal/service/)

    • Business logic orchestration
    • Calls processors and transformers
    • Coordinates between different business operations
  4. Processor Layer (internal/processor/)

    • Complex business operations
    • Cross-repository transactions
    • Business rule enforcement
    • Handles operations like order creation with inventory updates
  5. Repository Layer (internal/repository/)

    • Data access layer
    • Individual repository per entity
    • Database-specific operations
    • Uses entities for database models
  6. Supporting Packages:

    • Models (internal/models/) - Pure business logic models (no database dependencies)
    • Entities (internal/entities/) - Database models with GORM tags
    • Constants (internal/constants/) - Type-safe enums and business constants
    • Transformer (internal/transformer/) - Contract ↔ Model conversions
    • Mappers (internal/mappers/) - Model ↔ Entity conversions

Key Features

  • Clean Architecture: Strict separation between business logic and infrastructure
  • Type Safety: Constants package with validation helpers
  • Validation: Comprehensive request validation using go-playground/validator
  • Error Handling: Structured error responses with proper HTTP status codes
  • Database Independence: Business logic never depends on database implementation
  • Testability: Each layer can be tested independently

API Endpoints

Health Check

  • GET /health - Health check endpoint (registered at the root, not under /api/v1)

Organizations

  • POST /api/v1/organizations - Create organization
  • GET /api/v1/organizations - List organizations
  • GET /api/v1/organizations/{id} - Get organization by ID
  • PUT /api/v1/organizations/{id} - Update organization
  • DELETE /api/v1/organizations/{id} - Delete organization

Users

  • POST /api/v1/users - Create user
  • GET /api/v1/users - List users
  • GET /api/v1/users/{id} - Get user by ID
  • PUT /api/v1/users/{id} - Update user
  • DELETE /api/v1/users/{id} - Delete user
  • PUT /api/v1/users/{id}/password - Change password
  • PUT /api/v1/users/{id}/activate - Activate user
  • PUT /api/v1/users/{id}/deactivate - Deactivate user

Orders

  • POST /api/v1/orders - Create order with items
  • GET /api/v1/orders - List orders
  • GET /api/v1/orders/{id} - Get order by ID
  • GET /api/v1/orders/{id}?include_items=true - Get order with items
  • PUT /api/v1/orders/{id} - Update order
  • PUT /api/v1/orders/{id}/cancel - Cancel order
  • PUT /api/v1/orders/{id}/complete - Complete order
  • POST /api/v1/orders/{id}/items - Add item to order

Order Items

  • PUT /api/v1/order-items/{id} - Update order item
  • DELETE /api/v1/order-items/{id} - Remove order item

Running the Application

Prerequisites

Tool Version Needed for
Go 1.24+ building & running the server
golang-migrate latest make migration-* targets
make any shortcut commands (Windows: use Git Bash / WSL, see note below)
docker & docker-compose optional running Postgres/Redis locally
air optional hot reload during development (.air.toml is already configured)

1. Clone & install dependencies

git clone <repository-url>
cd apskel-pos-backend
go mod download

2. Configuration

Configuration is not read from .env files — it is loaded from YAML files in infra/ by config/configs.go using viper.

The file is selected by the ENV_MODE environment variable:

ENV_MODE Config file loaded
local infra/local.yaml
development infra/development.yaml
staging (default) infra/staging.yaml
production infra/production.yaml

Any other/unset value falls back to staging. Only staging.yaml and production.yaml are committed — for local/development copy one of them first:

cp infra/staging.yaml infra/local.yaml

Two important notes:

  • The config path is relative to the working directory, so always run the server from the repository root, otherwise viper panics with failed to read config file.
  • Push notifications need infra/firebase-service-account.json (git-ignored). Without it, obtain the file from the team before enabling FCM features.

3. Run migrations

The migration targets build the DB URL from the credentials at the top of the Makefile:

make migration-up                  # staging DB (default)
make migration-up ENV=production   # production DB

make migration-create name=create_cash_advances_table
make migration-down                # roll back the last migration
make migration-force version=87    # clear a dirty migration state

4. Start the server

make run                    # ENV_MODE=staging
make run ENV=production     # ENV_MODE=production

make run is just a wrapper around:

ENV_MODE=staging go run cmd/server/main.go

The server listens on the server.port value from the loaded YAML (4000 for both staging and production). Verify it is up:

curl http://localhost:4000/health

All application routes live under /api/v1 (see internal/router/router.go).

Windows note

The run/start targets use POSIX inline env-var syntax, which cmd.exe and PowerShell do not understand. Either run make from Git Bash / WSL, or start the server directly:

# PowerShell
$env:ENV_MODE = "staging"; go run cmd/server/main.go
:: cmd.exe
set ENV_MODE=staging && go run cmd/server/main.go

Hot reload

ENV_MODE=local air     # rebuilds ./tmp/main on every .go change

5. Other commands

make            # show all available targets
make fmt        # go fmt ./...
make test       # go test ./... -v

# Build a binary (make build-http still points at the old ./cmd/http path)
go build -o ./bin/http-server ./cmd/server/main.go

Running with Docker

docker-compose.yaml provides Postgres (5432), Redis (6379), and the API image. See DOCKER.md for the full workflow.

make docker-up      # docker-compose up -d
make docker-down    # docker-compose down

If you use the containerised Postgres/Redis, point infra/local.yaml at localhost:5432 / localhost:6379 instead of the remote hosts baked into staging.yaml.

Example API Usage

Create Organization

curl -X POST http://localhost:4000/api/v1/organizations \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Restaurant",
    "plan_type": "premium"
  }'

Create User

curl -X POST http://localhost:4000/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "uuid-here",
    "username": "john_doe",
    "email": "john@example.com",
    "password": "password123",
    "full_name": "John Doe",
    "role": "manager"
  }'

Create Order with Items

curl -X POST http://localhost:4000/api/v1/orders \
  -H "Content-Type: application/json" \
  -d '{
    "outlet_id": "uuid-here",
    "user_id": "uuid-here",
    "table_number": "A1",
    "order_type": "dine_in",
    "notes": "No onions",
    "order_items": [
      {
        "product_id": "uuid-here",
        "quantity": 2,
        "unit_price": 15.99
      }
    ]
  }'

Project Structure

apskel-pos-backend/
├── cmd/
│   └── server/           # Application entry point
├── internal/
│   ├── app/             # Application wiring and dependency injection
│   ├── contract/        # API contracts (request/response DTOs)
│   ├── handler/         # HTTP handlers and routes
│   ├── service/         # Business logic orchestration
│   ├── processor/       # Complex business operations
│   ├── repository/      # Data access layer
│   ├── models/          # Pure business models
│   ├── entities/        # Database entities (GORM models)
│   ├── constants/       # Business constants and enums
│   ├── transformer/     # Contract ↔ Model transformations
│   └── mappers/         # Model ↔ Entity transformations
├── migrations/          # Database migrations
├── Makefile            # Build and development commands
├── go.mod              # Go module definition
└── README.md           # This file

Dependencies

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

S
Description
No description provided
Readme MIT
85 MiB
Languages
Go 98.5%
HTML 0.9%
Shell 0.4%
PLpgSQL 0.1%
Makefile 0.1%