Metadata-Version: 2.4
Name: oubliette-commerce
Version: 0.3.0
Summary: Shared revenue SDK for the Oubliette product suite — config-driven licensing, metering, key issuance, and sale webhooks
Author: Oubliette Security
License-Expression: Apache-2.0
Keywords: licensing,metering,billing,saas,open-core
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: flask
Requires-Dist: flask>=2.3.0; extra == "flask"
Provides-Extra: licensing
Requires-Dist: cryptography>=48.0.1; extra == "licensing"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: cryptography>=48.0.1; extra == "dev"
Provides-Extra: test
Requires-Dist: oubliette-commerce[dev]; extra == "test"

# Oubliette Commerce

**Shared revenue SDK for the Oubliette product suite** — config-driven licensing, usage
metering, signed key issuance, and merchant-of-record (Gumroad/Paddle) sale webhooks.

This is internal infrastructure: the single source of truth so Shield, Dungeon, Trap, and
Warden don't each carry a copy of the commercial layer. The engine is product-neutral — each
product supplies its own `pro_features` and pricing/quota tables.

## Usage

```python
from oubliette_commerce import LicenseManager, issue_license, license_for_sale

PRO_FEATURES = frozenset({"scheduler", "reports", "api"})

# Validate a customer's license for ONE product (reads OUBLIETTE_LICENSE_KEY).
# Keys whose signed `products` list does not include "dungeon" give the free tier.
mgr = LicenseManager(product="dungeon", pro_features=PRO_FEATURES)
if mgr.check_feature("scheduler"):
    ...

# Server-side: mint a product-scoped Ed25519 key. The private key comes from the
# argument or OUBLIETTE_LICENSE_PRIVATE_KEY and never leaves the issuer.
key = issue_license(org="Acme", tier="pro", products=["dungeon"],
                    features=["scheduler", "reports"], expires="2027-09-30")

# Gumroad/Paddle webhook -> license (each SKU names its products and duration)
result = license_for_sale(
    payload,
    {"dungeon-pro": {"tier": "pro", "products": ["dungeon"], "duration_days": 365}},
    PRIVATE_KEY, seller_id=SELLER_ID,
    webhook_secret=WEBHOOK_SECRET, raw_body=raw_body, signature=signature,
)
```

## What's inside
- `LicenseManager` / `FeatureGate` / `verify_license_token`: product-scoped
  feature gating with Ed25519-signed schema-v2 keys. Anything that does not
  verify gives the free tier.
- `issue_license` / `license_record`, plus the CLI
  `python -m oubliette_commerce.license_issuer {keygen,pubkey,issue,verify}`:
  server-side key minting.
- `license_for_sale` / `create_license_webhook_blueprint`: Gumroad/Paddle sale → key.
- `UsageTracker`: usage metering with optional SQLite persistence and per-product quota/pricing.
- `auth_middleware`, `rbac`, `tenant`: API-key auth, RBAC, multi-tenancy.

`src/oubliette_commerce/_license_core.py` is the canonical verification core.
Shield, Trap and Dungeon vendor a byte-identical copy, with a sha256 pin test
in each repo. See [docs/licensing.md](docs/licensing.md) for the token format,
signing-key generation and storage, rotation, and issuing. The private signing
key stays on the issuer and is never committed. Gating is soft-enforce.

## License
Apache-2.0.
