Villa Backend SDK — Design

This document explains how the SDK is structured, why it is organized this way, and how data flows from login through order construction, validation, and payment.

Goals

The SDK exists to give developers a **small, predictable surface area** for Villa Market backend operations:

1. Authenticate a shopper via **AWS Cognito**

2. Build an **order** that matches the official villaMasterSchema contract

3. **Validate** that order locally and against backend pricing APIs

4. **Pay** using the Villa payment endpoints

Each step is isolated in its own module so it can be tested, replaced, or extended independently.


High-level architecture

The SDK uses a **layered architecture** with a thin facade on top:

┌─────────────────────────────────────────────────────────────┐
│                        VillaClient                          │
│  (facade: wires services, login(), health(), context mgr)   │
└───────────────┬───────────────┬───────────────┬─────────────┘
                │               │               │
        ┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
        │     auth     │ │   orders    │ │  payments   │
        │ CognitoAuth  │ │ OrderService│ │PaymentService│
        └───────┬──────┘ └──────┬──────┘ └──────┬──────┘
                │               │               │
                └───────────────┼───────────────┘
                                │
                    ┌───────────▼───────────┐
                    │      validation       │
                    │ schema + API checks   │
                    └───────────┬───────────┘
                                │
                    ┌───────────▼───────────┐
                    │         http          │
                    │ requests + auth hdrs  │
                    └───────────┬───────────┘
                                │
                    ┌───────────▼───────────┐
                    │        config         │
                    │ URLs, env, API paths  │
                    └───────────────────────┘

Design principles

PrincipleWhat it means in this SDK
**Single responsibility**auth/ only handles Cognito. payments/ only handles payment endpoints.
**Dependency injection**Services receive HttpClient and VillaConfig — tests can substitute mocks without patching globals.
**Schema as contract**Order shape comes from bundled villaMasterSchema YAML, not ad-hoc dicts.
**Fail fast before payment**PrePaymentValidator runs local + remote checks before money moves.
**Thin facade**VillaClient composes services; most logic lives in modules you can import directly.

Module reference

config.py — configuration

Centralizes all runtime settings:

  • **Base URL** (VILLA_BASE_URL) — default https://shop.villamarket.com
  • **Environment** (VILLA_ENV) — dev or prod, switches payment paths
  • **Cognito** (VILLA_COGNITO_*) — user pool, client ID, optional secret
  • **API paths** (ApiPaths) — every backend route is configurable
  • Payment paths switch by environment:

    EnvironmentCard tokenCard payment
    dev/api/payment3devapi/cardtoken/api/payment3devapi/cardpayment
    prod/api/payment3/cardtoken/api/payment3/cardpayment

    http.py — transport layer

    Wraps requests and adds:

  • Bearer token injection after Cognito login
  • JSON and form-encoded POST helpers
  • Consistent VillaApiError on HTTP/network failures
  • Path templating (/api/orders/{order_id})
  • Nothing in http.py knows about orders or payments — it only sends HTTP.

    auth/ — AWS Cognito login

    User credentials
          │
          ▼
    CognitoAuthService.login()
          │
          ▼
    boto3 cognito-idp InitiateAuth (USER_PASSWORD_AUTH)
          │
          ▼
    AuthTokens (AccessToken, IdToken, RefreshToken)
          │
          ▼
    HttpClient.set_bearer_token(IdToken)
  • Uses standard Cognito USER_PASSWORD_AUTH flow
  • Computes SECRET_HASH when a client secret is configured
  • Maps Cognito errors to VillaAuthError
  • Injects the **Id token** as Authorization: Bearer … on subsequent API calls
  • orders/ — order construction and CRUD

    Three pieces work together:

    FileRole
    models.pyPydantic types mirroring order.yaml (camelCase aliases)
    builder.pyFluent OrderBuilder for constructing valid orders in code
    service.pyHTTP calls: generate ID, create, get, list

    **OrderBuilder** example flow:

    order = (
        OrderBuilder()
        .with_ids(order_id="...", owner_id="...", basket_id="...")
        .with_branch("1000")
        .add_product(cprcode=25281, quantity=1)
        .with_delivery_shipping(shipping_postcode="10330", shipping_phone="...")
        .with_payment_totals(grand_total=174.0)
        .build()
    )

    order.to_api_payload() emits camelCase JSON matching the backend contract.

    validation/ — local schema + remote API checks

    This is the **pre-payment gate**. Two validators compose into PrePaymentValidator:

    #### 1. Local: OrderSchemaValidator

  • Loads bundled schemas/order/order.yaml
  • Validates payloads with jsonschema (Draft 4)
  • Runs **offline** — no network, fast, deterministic
  • #### 2. Remote: OrderApiValidator

    Calls backend pricing/verification APIs:

    MethodAPI pathPurpose
    calculate_grand_total()/api/webPayment/calculateGrandtotalServer-side total
    calculate_cost()/api/calculateCost/getCostLine-item pricing
    verify_payment()/api/payment/verifyPost-payment check
    validate_order_exists()/api/orders/{order_id}Order exists on server

    #### 3. Orchestrator: PrePaymentValidator

    Runs a **pipeline** and returns PrePaymentValidationResult:

    Step 1: local_schema          → jsonschema against order.yaml
    Step 2: drafting check        → reject if isDrafting=true
    Step 3: remote_order_exists   → optional GET order
    Step 4: calculate_grand_total → compare with expected amount
    Step 5: calculate_cost        → confirm cart pricing

    Each step appends to result.steps on success or result.errors on failure. The pipeline **stops at the first failure**.

    payments/ — card token and card payment

    Handles Villa + KBank payment endpoints:

  • create_card_token()POST form to cardtoken endpoint
  • initiate_card_payment()POST form to cardpayment endpoint
  • parse_callback() — extract query params from redirect URL
  • verify() — post-payment verification
  • Payment requests use **form-urlencoded** bodies, not JSON, matching the live Villa payment API.

    cli/ — command-line interface

    Maps 1:1 to SDK modules:

    Command groupSDK module
    villa auth loginauth/
    villa order …orders/
    villa validate …validation/
    villa payment …payments/

    Schema contract

    Order shape is defined by villaMasterSchema/order/order.yaml.

    Bundled copies live in:

    villa_backend_sdk/schemas/
    ├── order/order.yaml
    ├── order/genOrderId.yaml
    ├── webPayment/calculateGrandtotal.yaml
    └── calculateCost/getCost.yaml

    **Required order fields:** orderId, ownerId, basketId

    The SDK keeps schemas in the package so:

  • Validation works offline in tests and CI
  • Version pinning is explicit (schema version = package version)
  • No runtime dependency on GitHub

  • Typical checkout sequence

    sequenceDiagram
        participant Dev as Application
        participant SDK as VillaClient
        participant Cognito as AWS Cognito
        participant API as shop.villamarket.com
    
        Dev->>SDK: login(email, password)
        SDK->>Cognito: InitiateAuth
        Cognito-->>SDK: IdToken
        SDK->>SDK: set Bearer token on HttpClient
    
        Dev->>SDK: OrderBuilder.build()
        Dev->>SDK: pre_payment.validate(order)
    
        SDK->>SDK: jsonschema (local)
        SDK->>API: POST calculateGrandtotal
        API-->>SDK: grandTotal
        SDK->>API: POST calculateCost
        API-->>SDK: pricing breakdown
    
        alt validation passed
            Dev->>SDK: payments.create_card_token(...)
            SDK->>API: POST cardtoken
            Dev->>SDK: payments.initiate_card_payment(...)
            SDK->>API: POST cardpayment
            API-->>Dev: redirect URL with status
        else validation failed
            SDK-->>Dev: PrePaymentValidationResult(errors=...)
        end

    Error model

    All SDK errors inherit from VillaError:

    ExceptionWhen raised
    VillaConfigErrorMissing/invalid env config
    VillaAuthErrorCognito login failure
    VillaValidationErrorSchema or pre-payment validation failure
    VillaApiErrorHTTP 4xx/5xx or network error
    VillaPaymentErrorPayment endpoint returned failure

    VillaValidationError carries an errors: list[str] with human-readable messages from jsonschema or the validation pipeline.


    Extension points

    NeedHow to extend
    Custom API base URLVillaClient(base_url=...) or VILLA_BASE_URL
    Custom API pathsVillaConfig(api_paths=ApiPaths(...))
    Mock Cognito in testsPass client=FakeCognitoClient() to CognitoAuthService
    Skip remote validationpre_payment.validate(..., verify_grand_total=False)
    Direct module useImport OrderService, PaymentService, etc. without VillaClient

    Related documents

  • TESTING.md — test methodology, fixtures, and CI
  • ../README.md — installation and quick start