Traditional payment systems have middlemen. Banks, processors, clearinghouses. Each one takes a cut. Each one adds delays. In 2020, our Software Development course gave us a challenge: build an e-commerce platform that bypasses all of them.

Shopy was our answer. An e-commerce application accepting cryptocurrency payments directly. No intermediaries. No waiting for bank approvals. Just buyers and sellers connected through blockchain technology.

I was responsible for the service layer. The backend logic that made everything work.

What Shopy Does

Shopy is a full-featured e-commerce platform. Users can browse products, add them to carts, and complete purchases. Sellers can list products, manage inventory, and receive payments. Standard stuff for an online store.

The difference is in how payments work. Instead of credit cards or bank transfers, Shopy accepts cryptocurrency. Users pay with Bitcoin or other supported coins. The platform handles conversion, verification, and settlement.

The frontend is a Vue.js application. Clean interface. Product catalogs. Shopping cart. Checkout flow. The backend is NestJS running on Node.js. It exposes REST APIs for everything the frontend needs.

Architecture Decisions

We split the codebase into two parts. The client application lives in one folder. The server lives in another. Both use TypeScript for type safety.

NestJS gave us a modular structure. Each domain concept became its own module. Products. Users. Carts. Payments. Categories. Reviews. Each module contained its own controller, service, and entity definitions.

The controller handles HTTP requests. The service contains business logic. The entity defines the database structure. This separation kept the code organized as it grew.

For the database, we chose PostgreSQL with TypeORM. TypeORM let us define entities as TypeScript classes. It handled migrations automatically. When we changed a model, we ran a command and the database updated itself.

The Service Layer

My responsibility was the service layer. This is where business rules live. When a user adds something to their cart, the service checks stock levels. When they checkout, the service calculates totals. When payment arrives, the service updates order status.

The cart service was one of the more complex pieces. It needed to track products per user. Handle quantity changes. Calculate subtotals. Validate that items were still available. All while keeping the data consistent.

The product service managed the catalog. Creating products. Updating prices. Managing images. Organizing into categories. Each operation needed proper validation and error handling.

The user service handled authentication and profiles. We integrated with Google OAuth for social login. We also supported traditional email/password registration with bcrypt for password hashing.

Payment Integration

The interesting part was cryptocurrency payments. We integrated with Coingate, a payment gateway for crypto transactions.

Coingate provides a sandbox environment for testing. No real money moves. But the API behaves exactly like production. This let us build and test without financial risk.

The payment flow works like this. User completes checkout. We create an order in Coingate. They return a payment URL. User pays at that URL. Coingate notifies us when payment confirms.

Webhooks made this possible. Coingate sends HTTP requests to our server when payment status changes. We had to expose a public URL for this. During development, we used ngrok to tunnel local servers to the internet.

The payment service tracked order states. Pending. Paid. Expired. Failed. Each state triggered different behaviors. Successful payments updated inventory and notified sellers. Failed payments released held stock.

Database Design

TypeORM let us design the schema using decorators. Each entity defined its columns, relationships, and constraints. The ORM generated SQL automatically.

We had entities for users, products, categories, carts, orders, and payments. Relationships linked them together. A user has many orders. An order has many products. A product belongs to categories.

Migrations kept everyone synchronized. When someone changed an entity, they generated a migration file. That file contained the SQL to transform the database. Running migrations on a fresh database recreated the entire schema.

This approach saved hours of debugging. No more “it works on my machine” problems. Everyone ran the same migrations and got the same database structure.

Authentication and Security

Security mattered even in a course project. We used JWT tokens for authentication. Users login once, receive a token, and include it in subsequent requests.

Passport.js handled the authentication strategies. One strategy for JWT tokens. Another for Google OAuth. The service layer remained unaware of how users authenticated. It just received verified user objects.

We also implemented role-based access. Regular users can browse and buy. Sellers can list products. Admins can manage the platform. Each endpoint checked permissions before allowing actions.

Sensitive operations required extra validation. Changing passwords. Deleting accounts. Processing refunds. The service layer enforced these rules consistently.

Email Notifications

Transactional emails kept users informed. Order confirmations. Payment receipts. Shipping updates. We integrated SendGrid for reliable delivery.

The mail service used templates. HTML files with placeholders for dynamic content. When sending, we loaded the template and filled in the blanks. Order number here. Total amount there.

This separation kept email content manageable. Designers could update templates without touching code. Developers could add new email types without designing HTML.

What I Learned

Building Shopy taught me how to structure a real backend application. Not just make it work, but make it maintainable. The modular architecture paid off as the codebase grew.

TypeScript caught errors before they reached production. The ORM simplified database work. The framework enforced good patterns. These tools exist because they solve real problems.

Cryptocurrency integration showed me a different payment model. No chargebacks. No processing delays. Different tradeoffs than traditional systems. Worth understanding even if not always practical.

Most importantly, I learned to work on a team project with shared responsibility. My service layer depended on others’ work. Their features depended on mine. We had to communicate, coordinate, and compromise.


If you’re building e-commerce systems or working with cryptocurrency integrations, I’d be happy to share more details.

Back to Projects