Skip to content

Repository files navigation

BuyEngine

CI

BuyEngine is a .NET library designed to provide a simple, but fully-featured, e-commerce capability to new or existing .NET projects using patterns and technologies that are already familiar to .NET developers

Plenty of turnkey e-commerce solutions exist across languages and stacks. But rather than reinventing the wheel building e-commerce into an existing app, or fighting an e-commerce product's plugin/customization model to bend it to your needs, BuyEngine takes a different approach: use your existing toolset and workflow. No custom templating language, no custom execution or hosting model — just a library you add to a normal .NET project.

Roadmap

  • Version 0.1 - Basic Product Management functionality (Products, Suppliers, Brands)
  • Version 0.2 - Cart Management
  • Version 0.3 - SQL Data Provider, Testing Improvements
  • Version 0.4 - Product Browse By Brand, Supplier
  • Version 0.5 - Payment Processing - Stripe, Paypal, and Square
  • Version 0.6 - Shipping Providers - Multi-carrier seam (Shippo), Store Pickup
  • Version 0.7 - Additional Relational Data Providers - SQLite, PostgreSQL (self-hosted, Amazon Aurora, Azure Database for PostgreSQL)
  • Version 0.8 - Additional Data Providers (MongoDb, CosomoDb)
  • Version 0.9 - API Security and documentation

Further Out

  • L10N
  • Tax Calculators

Configuration

AddBuyEngine is the front door for wiring BuyEngine into a host's IServiceCollection. It registers Catalog + Checkout with their sensible defaults (EF InMemory, store-pickup shipping, PayAtStorePaymentProvider), then hands you a BuyEngineBuilder to opt into providers from add-on packages:

services.AddBuyEngine(cfg =>
{
    cfg.UseSqlServer(sql => sql.ConnectionStringName = "BuyEngine");        // BuyEngine.Data.Sql
    cfg.UseStripeForPayment(pay => pay.ApiKeyName = "Stripe:ApiKey");       // BuyEngine.Payment.Stripe
    cfg.UseShippoForShipping(s => s.ApiKeyName = "Shippo:ApiKey");         // BuyEngine.Shipping.Shippo
    cfg.UseStorePickupShipping(p => p.DefaultMethodName = "Curbside");     // stacks alongside any carrier
    cfg.UseWebApi(web => web.HostPrefix = "be-api");                       // BuyEngine.WebApi
});

// ...
app.MapBuyEngineWebApi(); // reads the HostPrefix configured above

Each Use* method lives in the package it configures (core has no reference to BuyEngine.Data.Sql, .WebApi, any payment package, or any shipping package) and is an extension on BuyEngineBuilder, so only the packages you reference show up as options. Use*ForPayment and Use*ForShipping methods are each mutually exclusive within their own kind - configuring a second payment provider throws, and so does configuring a second shipping carrier/aggregator (e.g. Shippo and FedEx together), matching the "one provider wins" behavior of the underlying registrations. UseStorePickupShipping is the one exception: it never claims the shipping slot, so it always composes alongside whichever carrier is configured (or stands alone as the zero-config default) - a shopper can be offered both "UPS Ground via Shippo" and "In-Store Pickup" on the same order.

*Name-suffixed options (ConnectionStringName, ApiKeyName, ...) are resolved from IConfiguration at first use, so the host can add configuration sources after calling AddBuyEngine. The plain-value options (ConnectionString, ApiKey, ...) are used verbatim - set one or the other, never both.

The original à-la-carte methods (AddCatalogServices(), AddCheckoutServices(), AddSqlDataServices(...), AddStripePaymentServices(...), AddBuyEngineWebApiServices(), ...) remain available for hosts that don't want the builder.

Upsells and Cross-Sells

A store manager curates which products are offered alongside another product - an Upsell (a better version of what the shopper is looking at) or a CrossSell (something that goes with it). These are never returned inside the product payload: a shopper's product page reads them with a second call, so product reads stay cheap and a storefront decides for itself whether to ask.

GET    products/product/{productId}/upsells               the enabled upsell Products, in sort order
GET    products/product/{productId}/cross-sells           the enabled cross-sell Products, in sort order
GET    products/product/{productId}/recommendations       every link, enabled or not (store manager view)
POST   products/product/{productId}/recommendations       { recommendedProductId, type, sortOrder, enabled }
DELETE products/product/{productId}/recommendations/{recommendationId}

type crosses the wire as "Upsell" / "CrossSell". The same pair of products can be linked once per type, a product cannot recommend itself, and a recommendation whose target product is disabled is dropped from the shopper-facing calls. IProductRecommendationService is the equivalent in-process surface.

Data Providers

Every provider below registers the same ICatalogDbContext/ICheckoutDbContext surface, splitting a single connection string into a .Catalog and a .Checkout database - see each package's Use* extension for the exact options.

  • BuyEngine.Data.Sql - SQL Server, via cfg.UseSqlServer(...).
  • BuyEngine.Data.Sqlite - SQLite, via cfg.UseSqlite(sqlite => sqlite.DataSource = "buyengine.db"). No server to stand up - good for evaluating BuyEngine, single-node deployments, or tests that need a real relational engine without Docker.
  • BuyEngine.Data.PostgreSql - PostgreSQL, via cfg.UsePostgreSql(pg => pg.ConnectionStringName = "BuyEngine"). Works unchanged against self-hosted PostgreSQL, Amazon Aurora PostgreSQL, and Azure Database for PostgreSQL (Flexible Server) - those are all wire-compatible Postgres, so nothing beyond the connection string differs:
    • Aurora's writer/reader failover: Host=writer.cluster-xxx.rds.amazonaws.com,reader.cluster-xxx.rds.amazonaws.com;Target Session Attributes=primary;...
    • Azure's required TLS: Host=<server>.postgres.database.azure.com;Ssl Mode=VerifyFull;...
    • PostgreSqlOptions.EnableRetryOnFailure (on by default) and MaxRetryCount absorb the transient network blips both managed services occasionally produce.
    • Token-based auth (Azure Entra ID, AWS IAM) isn't a static connection-string option - use the two-delegate AddPostgreSqlDataServices(configureCatalog, configureCheckout) overload and build the NpgsqlDataSource/connection yourself inside those callbacks.

Each provider owns a full, independent copy of the EF model (DbContexts + IEntityTypeConfigurations) rather than sharing one - see each project's CLAUDE.md notes for why. Product.RowVersion-based optimistic concurrency is implemented per provider's native mechanism: SQL Server's rowversion column, a SaveChanges-time stamp for SQLite, and PostgreSQL's xmin system column.

About

BuyEngine is a .NET library designed to add e-commerce functionality to new or existing .NET projects.

Topics

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages