Skip to main content

Welcome to Archetypy Oprogramowania

This quickstart will guide you through creating a simple e-commerce transaction system with proper accounting. You’ll learn how to:
  • Set up accounts for revenue tracking
  • Process a sale transaction with VAT
  • Handle customer refunds correctly
  • Query account balances
By the end, you’ll have a working example that demonstrates double-entry accounting principles in action.

Prerequisites

Before you begin, ensure you have:
  • Java 21 or later installed
  • Maven 3.6+ for dependency management
  • A Java IDE (IntelliJ IDEA, Eclipse, or VS Code)
This quickstart uses the accounting and quantity modules. You can add more modules as needed for your application.

Installation

Add Archetypy Oprogramowania to your Maven project:
1

Add the repository

First, configure the GitHub Packages repository in your pom.xml:
pom.xml
<repositories>
    <repository>
        <id>github</id>
        <url>https://maven.pkg.github.com/Archetypy-Oprogramowania/archetypes</url>
    </repository>
</repositories>
2

Add dependencies

Add the accounting module dependency:
pom.xml
<dependencies>
    <!-- Accounting module with double-entry bookkeeping -->
    <dependency>
        <groupId>com.softwarearchetypes</groupId>
        <artifactId>accounting</artifactId>
        <version>0.0.1</version>
    </dependency>
    
    <!-- Quantity module for Money type -->
    <dependency>
        <groupId>com.softwarearchetypes</groupId>
        <artifactId>quantity</artifactId>
        <version>0.0.1</version>
    </dependency>
</dependencies>
3

Verify installation

Build your project to download dependencies:
mvn clean install

Your First Transaction

Let’s create a simple e-commerce scenario: processing a sale with VAT and then handling a refund.

Step 1: Set Up the Accounting System

Create a new Java class and initialize the accounting facade:
EcommerceExample.java
package com.example;

import com.softwarearchetypes.accounting.*;
import com.softwarearchetypes.common.Result;
import com.softwarearchetypes.quantity.money.Money;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;

import static com.softwarearchetypes.quantity.money.Money.pln;
import static java.time.Clock.fixed;

public class EcommerceExample {
    
    public static void main(String[] args) {
        // Initialize the accounting system with in-memory storage
        Instant now = Instant.now();
        Clock clock = fixed(now, ZoneId.systemDefault());
        AccountingFacade accounting = AccountingConfiguration
            .inMemory(clock)
            .facade();
        
        System.out.println("✅ Accounting system initialized");
    }
}
AccountingConfiguration.inMemory() provides a simple in-memory implementation perfect for testing and learning. For production, you’d implement persistent repositories.

Step 2: Create Your Chart of Accounts

Every accounting system needs accounts. Let’s create three accounts for our e-commerce store:
EcommerceExample.java
// Create accounts for the e-commerce business
AccountId cashAccount = AccountId.generate();
AccountId revenueAccount = AccountId.generate();
AccountId vatPayableAccount = AccountId.generate();

// Cash is an ASSET - money we have
accounting.createAccount(
    CreateAccount.generateAssetAccount(cashAccount, "Cash")
);

// Sales Revenue is a REVENUE account - income from sales
accounting.createAccount(
    new CreateAccount(revenueAccount, "Sales Revenue", "REVENUE")
);

// VAT Payable is a LIABILITY - tax we owe to the government
accounting.createAccount(
    new CreateAccount(vatPayableAccount, "VAT Payable", "LIABILITY")
);

System.out.println("✅ Chart of accounts created");
Account Types:
  • ASSET - Resources owned (cash, inventory, receivables)
  • LIABILITY - Obligations owed (payables, loans)
  • REVENUE - Income from business activities
  • EXPENSE - Costs of doing business
  • OFF_BALANCE - Tracking accounts not on balance sheet

Step 3: Process a Sale Transaction

Now let’s record a sale of 123 PLN (100 PLN net + 23 PLN VAT):
EcommerceExample.java
// Record a sale: 123 PLN gross (100 PLN net + 23 PLN VAT)
Instant saleTime = now;

Transaction sale = accounting.transaction()
    .occurredAt(saleTime)           // When it happened
    .appliesAt(saleTime)            // When it takes effect
    .withTypeOf("sale")             // Transaction type
    .executing()                    // Start building entries
    .creditTo(cashAccount, pln(123))           // +123 to Cash (debit in accounting)
    .debitFrom(revenueAccount, pln(100))       // +100 to Revenue (credit in accounting)
    .debitFrom(vatPayableAccount, pln(23))     // +23 to VAT Payable (credit)
    .build();

// Execute the transaction
Result<String, TransactionId> result = accounting.execute(sale);

if (result.success()) {
    System.out.println("✅ Sale recorded successfully!");
    System.out.println("   Cash balance: " + accounting.balance(cashAccount).orElse(pln(0)));
    System.out.println("   Revenue: " + accounting.balance(revenueAccount).orElse(pln(0)));
    System.out.println("   VAT Payable: " + accounting.balance(vatPayableAccount).orElse(pln(0)));
} else {
    System.out.println("❌ Transaction failed: " + result.getFailure());
}
Expected Output:
✅ Sale recorded successfully!
   Cash balance: 123.00 PLN
   Revenue: -100.00 PLN
   VAT Payable: -23.00 PLN
Why negative balances? In double-entry accounting:
  • Assets have positive balances (debits)
  • Revenues and Liabilities have negative balances (credits)
This ensures the fundamental equation: Assets = Liabilities + Equity (which includes Revenue)

Step 4: Process a Full Refund

Customer changed their mind? Let’s process a refund that reverses the sale:
EcommerceExample.java
// Process a full refund
Instant refundTime = saleTime.plusSeconds(3600); // 1 hour later

Transaction refund = accounting.transaction()
    .occurredAt(refundTime)
    .appliesAt(refundTime)
    .withTypeOf("refund")
    .executing()
    .debitFrom(cashAccount, pln(123))          // -123 from Cash
    .creditTo(revenueAccount, pln(100))        // -100 from Revenue
    .creditTo(vatPayableAccount, pln(23))      // -23 from VAT Payable
    .build();

Result<String, TransactionId> refundResult = accounting.execute(refund);

if (refundResult.success()) {
    System.out.println("✅ Refund processed successfully!");
    System.out.println("   Cash balance: " + accounting.balance(cashAccount).orElse(pln(0)));
    System.out.println("   Revenue: " + accounting.balance(revenueAccount).orElse(pln(0)));
    System.out.println("   VAT Payable: " + accounting.balance(vatPayableAccount).orElse(pln(0)));
}
Expected Output:
✅ Refund processed successfully!
   Cash balance: 0.00 PLN
   Revenue: 0.00 PLN
   VAT Payable: 0.00 PLN
Perfect! The refund completely reversed the original sale.

Complete Working Example

Here’s the complete code you can run:
package com.example;

import com.softwarearchetypes.accounting.*;
import com.softwarearchetypes.common.Result;
import com.softwarearchetypes.quantity.money.Money;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;

import static com.softwarearchetypes.quantity.money.Money.pln;
import static java.time.Clock.fixed;

public class EcommerceExample {
    
    public static void main(String[] args) {
        // Step 1: Initialize accounting system
        Instant now = Instant.now();
        Clock clock = fixed(now, ZoneId.systemDefault());
        AccountingFacade accounting = AccountingConfiguration
            .inMemory(clock)
            .facade();
        
        System.out.println("✅ Accounting system initialized\n");
        
        // Step 2: Create chart of accounts
        AccountId cashAccount = AccountId.generate();
        AccountId revenueAccount = AccountId.generate();
        AccountId vatPayableAccount = AccountId.generate();
        
        accounting.createAccount(
            CreateAccount.generateAssetAccount(cashAccount, "Cash")
        );
        accounting.createAccount(
            new CreateAccount(revenueAccount, "Sales Revenue", "REVENUE")
        );
        accounting.createAccount(
            new CreateAccount(vatPayableAccount, "VAT Payable", "LIABILITY")
        );
        
        System.out.println("✅ Chart of accounts created\n");
        
        // Step 3: Process a sale
        Instant saleTime = now;
        Transaction sale = accounting.transaction()
            .occurredAt(saleTime)
            .appliesAt(saleTime)
            .withTypeOf("sale")
            .executing()
            .creditTo(cashAccount, pln(123))
            .debitFrom(revenueAccount, pln(100))
            .debitFrom(vatPayableAccount, pln(23))
            .build();
        
        Result<String, TransactionId> saleResult = accounting.execute(sale);
        
        if (saleResult.success()) {
            System.out.println("✅ Sale recorded: 123 PLN (100 PLN + 23 PLN VAT)");
            printBalances(accounting, cashAccount, revenueAccount, vatPayableAccount);
        }
        
        // Step 4: Process a refund
        Instant refundTime = saleTime.plusSeconds(3600);
        Transaction refund = accounting.transaction()
            .occurredAt(refundTime)
            .appliesAt(refundTime)
            .withTypeOf("refund")
            .executing()
            .debitFrom(cashAccount, pln(123))
            .creditTo(revenueAccount, pln(100))
            .creditTo(vatPayableAccount, pln(23))
            .build();
        
        Result<String, TransactionId> refundResult = accounting.execute(refund);
        
        if (refundResult.success()) {
            System.out.println("\n✅ Refund processed: 123 PLN");
            printBalances(accounting, cashAccount, revenueAccount, vatPayableAccount);
        }
    }
    
    private static void printBalances(AccountingFacade accounting, 
                                     AccountId cash, 
                                     AccountId revenue, 
                                     AccountId vat) {
        System.out.println("   Cash: " + accounting.balance(cash).orElse(pln(0)));
        System.out.println("   Revenue: " + accounting.balance(revenue).orElse(pln(0)));
        System.out.println("   VAT Payable: " + accounting.balance(vat).orElse(pln(0)));
    }
}
Run the example:
mvn clean compile exec:java -Dexec.mainClass="com.example.EcommerceExample"

Key Concepts Learned

Every transaction has at least two entries that balance each other:
  • Debit entries (debitFrom) increase assets, decrease liabilities/revenue
  • Credit entries (creditTo) decrease assets, increase liabilities/revenue
The sum of all debits must equal the sum of all credits in a transaction.
Transactions are built using a fluent API:
accounting.transaction()
    .occurredAt(instant)     // When it happened
    .appliesAt(instant)      // When it takes effect (bi-temporal)
    .withTypeOf("type")      // Transaction classification
    .executing()             // Build entries
    .creditTo(account, amount)
    .debitFrom(account, amount)
    .build()
Operations return Result<Error, Success> instead of throwing exceptions:
Result<String, TransactionId> result = accounting.execute(transaction);

if (result.success()) {
    TransactionId id = result.get();
    // Process success
} else {
    String error = result.getFailure();
    // Handle error
}
Use Money instead of primitives for financial calculations:
import static com.softwarearchetypes.quantity.money.Money.*;

Money price = pln(100);           // 100.00 PLN
Money vat = price.multiply(0.23); // 23.00 PLN
Money total = price.add(vat);     // 123.00 PLN
Benefits: No rounding errors, currency-aware, proper precision.

Next Steps

Now that you’ve built your first transaction system, explore more features:

Accounting Module

Learn about account projections, transaction reversal, and bi-temporal accounting

Money & Quantity

Master type-safe financial calculations and unit conversions

Ordering Module

Build a complete order processing system with inventory integration

Examples

Explore real-world examples including banking, telco, and fleet management

Common Patterns

Partial Refunds: To refund only part of an order, create a transaction with proportional amounts:
// Refund 50% of a 123 PLN sale
accounting.transaction()
    .withTypeOf("partial_refund")
    .executing()
    .debitFrom(cashAccount, pln(61.50))
    .creditTo(revenueAccount, pln(50))
    .creditTo(vatPayableAccount, pln(11.50))
    .build();
Money Transfers: Use the convenience method for simple transfers:
accounting.transfer(
    fromAccount, 
    toAccount, 
    pln(1000), 
    occurredAt, 
    appliesAt
);

Troubleshooting

Error: Transaction entries don’t sum to zeroSolution: Ensure debits equal credits:
// ✅ Correct - balanced
.creditTo(cash, pln(123))    // +123
.debitFrom(revenue, pln(123)) // -123

// ❌ Wrong - unbalanced
.creditTo(cash, pln(123))
.debitFrom(revenue, pln(100)) // Only -100!
Error: Account {id} does not existSolution: Create the account before using it in transactions:
AccountId id = AccountId.generate();
accounting.createAccount(
    CreateAccount.generateAssetAccount(id, "My Account")
);
// Now you can use 'id' in transactions
Error: Could not resolve dependencySolution: Configure GitHub authentication in ~/.m2/settings.xml:
<servers>
  <server>
    <id>github</id>
    <username>YOUR_GITHUB_USERNAME</username>
    <password>YOUR_GITHUB_TOKEN</password>
  </server>
</servers>

Need Help?

View Source Code

Browse the complete source code and test scenarios

Report Issues

Found a bug or have a question? Open an issue

Build docs developers (and LLMs) love