- 📖 Overview
- 🎯 Features
- 🚀 Technologies Used
- 🏗️ Architecture
- 📦 Installation
- 🤝 Authors
- 💎 Assets and Attributions
PayUpPal is a modern full-stack app designed to simplify group expense sharing 👥💰. Whether you're traveling with friends
Users can easily create or join groups ➕👫, add expenses 🧾, and let the system calculate how much each person owes. Thanks to its smart debt minimization algorithm 🤖📉, the app reduces the number of transactions needed to settle up, saving you time and awkward money talk 😅.
No spreadsheets, no confusion 🤯 – just simple, fair expense management ✨.
Here’s what you can do with PayUpPal 💸✨:
👇 Click on any feature below to see it in action
🤝 Friends System - Add and manage friends directly in the app
- Browse the Friends tab 👥
- Search for other users 🔍
- Send and accept friend requests 📩✅
👥 Group Creation - Create custom groups for trips, shared apartments, or events
- Create your own group 👥
- Set a name, description, and group photo 📝🖼️
🔗 Join Groups Easily - Join groups using an invitation link 📬 or with a unique group code 🧩 – quick and simple!
🧾 Add & Track Expenses - Add shared expenses in a group, split them fairly, and keep everyone on the same page 💳📊
🤖 Smart Payment Optimization - Our transaction optimization algorithm reduces the number of money transfers needed to settle up. Less hassle, same results 📉🤯.
- For details check: 🏗️ Architecture > ⚙️ Backend > 🤖 Payment optimization algorithm
- Flutter, Dart, Firebase Authentication, BLoC.
- ASP.NET Core, C#, Entity Framework Core, SQL Server, Azure Blob Storage.
-
🗂️ Project Structure
The frontend of PayUpPal was designed with modularity and scalability in mind. The project is divided into the main app code and two standalone Dart packages:
lib/: Contains app logic, pages, views, state management, and routes.packages/api_client/: A reusable Dart package that defines all REST API communication.packages/authentication_repository/: Encapsulates FirebaseAuth methods and exposes auth-related interfaces to the app layer.
The
api_clientpackage is a self-contained Dart library that provides a modular, scalable, and strongly-typed interface for communicating with the PayUpPal backend API.It is responsible for:
- ⚙️ Configuring secure HTTP communication,
- 🔄 Transforming raw JSON responses into clean domain models,
- 📦 Exposing high-level, app-friendly methods via repositories.
├───packages │ ├───api_client │ │ └───lib │ │ └───src │ │ ├───configurations │ │ │ └───converters │ │ ├───dtos │ │ ├───mappers │ │ ├───models │ │ ├───providers │ │ └───repositories
Directory Description configurations/Contains dioinstance setup and request interceptors (e.g. JWT token injection).converters/Houses custom serializers for types like Decimalor enums used across DTOs.dtos/Data Transfer Objects generated using json_serializableandbuild_runner, which mirror the backend API schema.mappers/Responsible for mapping between DTOs and Domain Models. models/Clean, immutable domain models used by the app. Designed for presentation and business logic. providers/Annotated API service definitions using retrofit. Maps HTTP verbs to methods.repositories/High-level public API used by the Flutter app. Calls providers, applies mappers, and returns models.
The
authentication_repositoryis a focused Dart package that encapsulates all logic related to user authentication within the PayUpPal ecosystem.It provides a unified API for signing in, signing up, signing out, and securely storing session data using
firebase_auth,google_sign_inand native device mechanisms.├───packages │ ├───authentication_repository │ │ ├───lib │ │ │ │ authentication_repository.dart │ │ │ └───src │ │ │ │ authentication_repository.dart │ │ │ └───exceptions │ │ └───test │ │ authentication_repository_test.dart
Directory Description authentication_repository.dartPublic API for auth logic (sign in/out, get user, register, etc.). Wraps lower-level Firebase calls. exceptions/Defines domain-specific exceptions with readable messages. test/Unit tests verifying repository behavior and exception handling.
The
lib/directory serves as the entry point for all business logic, UI rendering, and presentation logic in the PayUpPal frontend. It adheres to feature-first organization, ensuring scalability, modularity, and testability.├───lib │ ├───configurations │ ├───core │ │ ├───services │ │ └───widgets │ │ ├───animations │ │ ├───display_messages │ │ └───forms │ └───features │ ├───authentication │ │ ├───login │ │ │ ├───bloc │ │ │ └───view │ │ ├───register │ │ │ ├───bloc │ │ │ └───view │ │ └───widgets │ ├───expenses │ │ └───create_expense │ ├───friendships │ │ ├───explore │ │ ├───friends │ │ ├───invitations │ │ ├───invitation_response │ │ └───request │ ├───groups │ │ ├───create_group │ │ ├───create_join_request │ │ ├───explore │ │ ├───invitations │ │ ├───invitation_response │ │ ├───memberships │ │ └───requests │ ├───group_details │ │ ├───balances │ │ │ ├───settle │ │ ├───expenses │ ├───group_settings │ │ ├───invites │ │ ├───members │ │ └───request │ ├───home │ ├───money_transfers │ │ ├───history │ │ ├───incoming │ │ ├───incoming_response │ │ └───outgoing │ └───profile
Directory Description configurations/Contains global app configurations such as asset paths ( assets.dart), environment definitions (environment.dart), route settings (routes.dart), and dependency injection setup (service_registration.dart).core/services/App-wide service classes like secure storage, image picker logic or snack bar service. core/widgets/Shared UI components and layouts like custom app bars, drawers, and themed widgets. Subfolders include animations, reusable display messages, and form components. features/Contains the domain-specific logic grouped by app features (e.g., authentication,groups,expenses). Each feature typically includes BLoC state management, pages, views and widgets. -
🧠 State Management
PayUpPal uses the BLoC (Business Logic Component) pattern to manage application state in a predictable and scalable way. State logic lives in each feature under
features/<feature>/bloc/. -
🌐 Networking & Data Layer
All HTTP and data-mapping logic is encapsulated in the
api_clientpackage (underpackages/api_client/), decoupling your UI from raw network calls. -
💉 Dependency Injection
PayUpPal uses
get_itas its service locator. All app-wide services and repositories are registered once at startup. -
🧭 Routing & Navigation
Routing is handled by the declarative
go_routerpackage. All route definitions and guards live inconfigurations/routes.dart.
PayUpPal's backend is built on ASP.NET Core and is logically divided into 5 projects, promoting a clean and maintainable architecture:
The heart of the PayUpPal backend is a clean, scalable, and secure RESTful API. It serves as the single entry point for the Flutter frontend, responsible for handling requests, validating input, and orchestrating business logic through a modern, decoupled architecture.
Key principles and technologies include:
-
CQRS with
MediatR: We leverage the Command Query Responsibility Segregation (CQRS) pattern using the popularMediatRlibrary. This decouples endpoint handlers from controllers; each API endpoint simply dispatches a specificCommand(for write operations) orQuery(for read operations) to its dedicated handler. This results in focused, single-responsibility handlers that are easy to test and maintain. -
Robust Validation with
FluentValidation: Data integrity is paramount. Every incoming request DTO is rigorously validated using theFluentValidationlibrary. This allows us to define clear, strongly-typed validation rules that are applied automatically in the request pipeline, ensuring that only valid data reaches our business logic and providing clear error feedback to the client. -
Seamless Mapping with
AutoMapper: To prevent leaking our internal domain models and to shape data specifically for the client, we useAutoMapper. It handles the transformation between our coreEntitiesand theData Transfer Objects (DTOs)returned by the API, eliminating boilerplate mapping code and keeping endpoints clean.
├───PayUpPal.API
│ ├───Authentication
│ ├───Configurations
│ ├───Controllers
│ ├───DTOs
│ │ ├───Currencies
│ │ ├───Debts
│ │ ├───Expenses
│ │ ├───ExpensesSplits
│ │ ├───Friendships
│ │ ├───Groups
│ │ ├───Memberships
│ │ ├───MoneyTransfers
│ │ └───Users
│ ├───Enums
│ ├───Pagination
│ ├───Profiles
│ ├───Properties
│ ├───Requests
│ │ ├───Currencies
│ │ │ ├───Handlers
│ │ │ └───Queries
│ │ ├───Debts
│ │ │ ├───Commands
│ │ │ ├───Handlers
│ │ │ └───Queries
│ │ ├───Expenses
│ │ ├───Commands
│ │ ├───Handlers
│ │ └───Queries
│ │ ├───Groups
│ │ │ ├───Commands
│ │ │ ├───Handlers
│ │ │ └───Queries
│ │ ├───Memberships
│ │ │ ├───Handlers
│ │ │ └───Queries
│ │ ├───MoneyTransfers
│ │ │ ├───Commands
│ │ │ ├───Handlers
│ │ │ └───Query
│ │ └───Users
│ │ ├───Commands
│ │ ├───Handlers
│ │ └───Queries
│ ├───Types
│ └───Validators
│ ├───Debts
│ ├───Expenses
│ ├───Extensions
│ ├───Groups
│ ├───Memberships
│ ├───MoneyTransfers
│ ├───PipelineBehaviours
│ └───Users| Directory | Description |
|---|---|
Authentication/ |
Includes middleware and services related to JWT validation. |
Configuration/ |
Service registration for cors and dependency injection. |
Controllers/ |
Defines the RESTful API endpoints. Controllers are lightweight, responsible only for receiving HTTP requests and dispatching MediatR commands/queries. |
DTOs/ |
Data Transfer Objects used for API responses. Shaped by AutoMapper to provide clean, client-friendly data structures. |
Enums/ |
API-specific enumerations used in requests and responses. |
Pagination/ |
Contains classes and helpers for handling paginated API responses. |
Profiles/ |
Contains AutoMapper mapping profiles that define the conversion logic between entities and DTOs. |
Requests/ |
Contains all MediatR Command and Query objects, along with their corresponding Handlers. |
Types/ |
Custom API-level types. |
Validators/ |
Houses all FluentValidation rule sets for incoming requests, ensuring data consistency before processing. |
This project defines the core domain entities and enumerations.
├───PayUpPal.Core
│ ├───Entities
│ └───EnumsThe persistence layer of PayUpPal is built on a modern .NET stack, leveraging several key technologies and design patterns to ensure a clean, maintainable, and scalable data access layer.
├───PayUpPal.Persistence
│ ├───Configuration
│ ├───Data
│ ├───Migrations
│ ├───Options
│ ├───Pagination
│ ├───Repositories
│ ├───Scripts
│ └───Specifications
│ ├───Currencies
│ ├───Debts
│ ├───Expenses
│ ├───Friendships
│ ├───Groups
│ ├───Memberships
│ ├───MoneyTransfers
│ └───Users-
ORM: Entity Framework Core (EF Core) is used as the Object-Relational Mapper, providing a powerful and flexible way to interact with the database using .NET objects.
-
Configuration over Convention: Instead of relying solely on EF Core conventions or data annotations, the project uses the Fluent API with
IEntityTypeConfiguration<T>classes for each entity. This approach decouples the database schema configuration from the core domain models (POCOs), leading to cleaner entity classes and a more organized persistence layer. -
Repository and Specification Patterns: Data retrieval logic is encapsulated using the Repository Pattern combined with the Specification Pattern, implemented by means of the
Ardalis.Specificationlibrary. This allows for the creation of strongly-typed and reusable queries. -
A Note on Coupling: It's important to recognize the pragmatic trade-offs made in this design. While the Fluent API decouples entities from the database schema details, the entity classes themselves still contain navigation properties (e.g.,
public User Admin { get; set; }). This intentional coupling is what allows EF Core to map relationships and enables theArdalis.Specificationpattern to build powerful and efficient eager-loaded queries using itsIncludemethods. This avoids the overhead of maintaining separate persistence models and complex mapping logic.
Entity Relationship Diagram
The following diagram illustrates the relationships between the core entities in the PayUpPal database.
The annotations on foreign keys (e.g., Requester, Addressee) directly correspond to the navigation property names
in the C# entities.
erDiagram
User {
int Id PK
string DisplayName
string Email
}
Friendship {
int Id PK
int RequesterId FK "Requester"
int AddresseeId FK "Addressee"
FriendshipStatus Status
}
Group {
int Id PK
string Name
int AdminId FK "Admin"
int CurrencyId FK "Currency"
GroupStatus Status
}
Currency {
int Id PK
string Code
string Name
}
Membership {
int Id PK
int UserId FK "User"
int GroupId FK "Group"
GroupMembershipStatus Status
decimal Balance
}
Expense {
int Id PK
string Name
int GroupId FK "Group"
int CreatedById FK "CreatedBy"
decimal Amount
}
ExpenseSplit {
int Id PK
int ExpenseId FK "Expense"
int DebtorId FK "Debtor"
decimal Amount
}
Debt {
int Id PK
int FirstId FK "First"
int SecondId FK "Second"
decimal Amount
}
MoneyTransfer {
int Id PK
int RequesterMembershipId FK "RequesterMembership"
int AddresseeMembershipId FK "AddresseeMembership"
decimal Amount
MoneyTransferStatus Status
}
User ||--o{ Friendship : "requests"
User ||--o{ Friendship : "receives_request_from"
User ||--o{ Group : "administers"
User ||--o{ Membership : "has"
Group ||--o{ Membership : "contains"
Group ||--o{ Expense : "has"
Currency ||--o{ Group : "is_used_by"
Membership ||--o{ Expense : "pays_for"
Membership ||--o{ ExpenseSplit : "owes"
Membership ||--o{ Debt : "is_party_one_of"
Membership ||--o{ Debt : "is_party_two_of"
Membership ||--o{ MoneyTransfer : "sends"
Membership ||--o{ MoneyTransfer : "receives"
Expense ||--o{ ExpenseSplit : "is_split_into"
This project contains various service implementations and providers.
├───PayUpPal.Infrastructure
│ ├───Configurations
│ ├───Providers
│ │ └───DateTimeProviders
│ ├───Services
│ │ ├───DebtServices
│ │ ├───PictureUploadServices
│ │ └───UniqueCodeServices
│ └───Storages
│ └───BlobStoragesTo optimize the number of transactions between group members, PayUpPal uses a debt simplification algorithm based on individual balances.
Here's how it works under the hood:
-
Balance Calculation
For each group member, we calculate their total balance by subtracting what they owe from what they paid.- Positive balance → the person is owed money 💰
- Negative balance → the person owes money 🧾
-
Heap Construction
We build two priority queues (heaps):- One for users with negative balances (debtors) 🔻
- One for users with positive balances (creditors) 🔺
The top of each heap holds the person with the largest absolute balance.
-
Transaction Matching
We repeatedly take the top debtor and creditor from the heaps and create a transaction between them:- One of them will be fully settled in each step.
- If the other still has a remaining balance, we push them back into the heap with the updated amount.
- This continues until all balances are settled (both heaps are empty). ✅
This approach guarantees an optimized number of transactions while ensuring that everyone pays or receives exactly what they should – no more, no less 💸🔄
Follow these steps to run PayUpPal locally:
-
Clone the repository:
git clone https://github.com/adamgracikowski/PayUpPal.git cd PayUpPal -
Configure Backend:
📂 Enter
/backenddirectory:cd backend📦 Restore NuGet packages:
dotnet restore
🔒 Configure user secrets:
cd PayUpPal.API dotnet user-secrets init dotnet user-secrets set "Firebase:Authentication:ProjectId" "<your-firebase-project-id>" dotnet user-secrets set "Firebase:Authentication:Issuer" "<your-firebase-issuer>" dotnet user-secrets set "ConnectionStrings:DefaultConnection" "<your-database-connection-string>" dotnet user-secrets set "AzureBlobStorage:ConnectionString" "<your-blob-storage-connection-string>" dotnet user-secrets set "AzureBlobStorage:BlobContainers:UsersContainer" "<your-users-container-name>" dotnet user-secrets set "AzureBlobStorage:BlobContainers:GroupsContainer" "<your-groups-container-name>"
Secret Key Description Firebase:Authentication:ProjectIdYour Firebase project ID (appears in the Firebase Console URL). Firebase:Authentication:IssuerJWT issuer URI for your Firebase project (usually https://securetoken.google.com/<ProjectId>).ConnectionStrings:DefaultConnectionA valid ADO.NET connection string pointing to your SQL database (e.g., local SQL Server or Azure SQL). AzureBlobStorage:ConnectionStringThe full Azure Storage connection string, including protocol, account name/key, and endpoint suffix. AzureBlobStorage:BlobContainers:UsersContainerName of the blob container where user-related photos will be stored. AzureBlobStorage:BlobContainers:GroupsContainerName of the blob container where group-related photos will be stored. 🔄 Apply EF Core migrations:
dotnet ef database update --project PayUpPal.Persistence
✅ (Optional) Run backend tests
dotnet test▶️ Start the APIOn Linux or macOS:
dotnet run --project PayUpPal.API &On Windows:
start dotnet run --project PayUpPal.API
🔙 Return to root
cd ../.. -
Configure Frontend:
📂 Enter
/frontendfolder:cd frontend📦 Install Dart & Flutter packages:
flutter pub get
📱 Select a device and run the app:
flutter devices flutter run --device-id <DEVICE_ID>
You're all set! 🎉 PayUpPal should now be running on your machine. If you encounter any issues, double‑check your user‑secrets configuration and that your database migrations applied successfully.
This project was created by:
The current look could not be achieved without high quality assets. They are listed below with source and license type. We encourage you to check their creators via provided links.
asset Source License images/circle_icons_profile.svg Elegant Themes GNU General Public License, version 2 images/circle_icons_money_return.svg svgrepo CC Attribution License images/circle_icons_group.svg svgrepo MIT License images/circle_icons_expense.svg flaticon Flaticon License animations/money_animation.json lottiefiles Lottie Simple License animations/money_animation.json lottiefiles Lottie Simple License









