Releases: SmartAppsCo/Paycheck
Releases · SmartAppsCo/Paycheck
v0.9.0
Added
- OS capture on device activation: Record the operating system where licensed software is activated. The
osfield is stored on the device record and returned in license info responses (GET /license,GET /orgs/.../licenses/{id}). - SDK auto-detection: All SDKs (Rust, TypeScript, React) auto-detect the OS at activation time and send it with the
/redeemrequest. Developers can override withDeviceInfo { os: "custom" }. - New
osfield onDeviceInfo(SDK input) andLicenseDeviceInfo(SDK response) across all SDKs. - Database migration 002: Adds
os TEXTcolumn to existingdevicestables. Fresh databases include it in the schema automatically.
Changed
/redeemrequest body: New optionalosfield (max 64 chars). Fully backward compatible — omitting it results innull.- Device reactivation preserves OS: When an existing device reactivates, a new
osvalue updates the record, but omitting it preserves the previous value (COALESCEsemantics). - Bruno
Local.bruenvironment file untracked: Moved operator API key to Bruno secret vars; file is now gitignored.
Full Changelog: v0.8.2...v0.9.0
v0.8.2
Added
- Configurable grace period for online validation: New
grace_period(Rust) /gracePeriod(TS) client option controls how long a cached token is trusted when the server is unreachable duringvalidate_online()/validate({ online: true }). Default: 24 hours. Based on JWTiatclaim.
Changed
validate_online()returnsOfflineValidateResultwith claims: Previously returned a separateValidateResulttype without claims, forcing callers to decode the token separately. Now returns the same type asvalidate()andsync(), withclaims,reason, and full local validation (signature, issuer, device, expiration) before the server call.validate_online()grace period on server unreachable: Instead of immediately returningvalid: falsewhen the server can't be reached, the SDK now trusts the cached token if it was issued within the grace period window.sync()uses single/refreshround trip: Previously called/validatethen conditionally/refresh(two requests). Now calls/refreshdirectly, which validates and returns fresh claims in one call. Also distinguishes network errors (offline fallback) from server rejections (revoked/invalid).- Removed
ValidateResulttype from both SDKs: Was redundant withOfflineValidateResultand was dead code in the TypeScript SDK (defined but never returned by any method).
Full Changelog: v0.8.1...v0.8.2
v0.8.1
Added
- Test coverage: Email validation, SSRF prevention, auth/impersonation, feedback/crash endpoints, SDK token verification, service config CRUD, cross-org isolation, webhook error handling
- Bruno API collection files for operator tag endpoints and metering service
Deserializederive on metering event types for downstream consumption
Changed
- Replaced
jwt-simplewithjsonwebtoken: Migrated from BoringSSL backend to ring backend, eliminating heavy build dependencies (cmake, golang, libclang) from Docker builds - Removed dead JWKS/first-party JWT code: Deleted speculative
JwksCache,TrustedIssuer, and RSA handling that was never used (console uses API keys via proxy pattern) - Graceful Docker shutdown: Server now handles SIGTERM in addition to SIGINT, preventing force-kill (exit code 137) on
docker stop - Removed
internal/from.dockerignore
Fixed
- SSRF prevention:
validate_webhook_url()now blocks IPv4-mapped IPv6 addresses,0.0.0.0,::, and 172.16-31.x DNS rebinding hostnames - Email validation:
validate_email_format()rejects CRLF injection, null bytes, and enforces RFC 5321 length limits - Activation code request: Now validates email format before processing
Full Changelog: v0.8.0...v0.8.1
v0.8.0
Added
- Tagging system for users and organizations: Flexible tagging with add/remove semantics where remove takes precedence over add
- Tags stored as JSON arrays in the database
- New
PATCH /operators/users/{id}/tagsendpoint for user tags (admin+ required) - New
PATCH /operators/organizations/{id}/tagsendpoint for org tags (admin+ required) - Request body:
{"add": ["tag1", "tag2"], "remove": ["tag3"]}— remove takes precedence
- Org tag enforcement for public API endpoints: Configurable tag-based blocking for organizations
PAYCHECK_DISABLE_CHECKOUT_TAG: BlocksPOST /buywhen org has matching tagPAYCHECK_DISABLE_PUBLIC_API_TAG: Blocks/buy,/validate,/activation/request-code, and/refresh- Returns 503 Service Unavailable when org has the matching tag
- No blocking when env vars are not set (self-hosted friendly)
Fixed
- Unnecessary backup on fresh databases: Migration system now skips backup for version 0 databases (nothing to backup)
Full Changelog: v0.7.2...v0.8.0
v0.7.2
Fixed
- JTI prefix consistency: New JWT IDs now use
pc_jti_prefix viaEntityType::Jti.gen_id()(existing tokens unaffected - stored JTI is reused on refresh)
Full Changelog: v0.7.1...v0.7.2
v0.7.1
Fixed
- API key prefix consistency: New API keys now use
pc_key_prefix viaEntityType::ApiKey.gen_id()(existing keys unaffected - lookup is by hash) - Docs site logo now navigates in same window instead of opening new tab
Full Changelog: v0.7.0...v0.7.1
v0.7.0
Added
- Prefixed entity IDs: All IDs now use
pc_{entity}_{32_hex_chars}format (e.g.,pc_usr_,pc_org_,pc_lic_)- Instant entity type identification in logs and support tickets
- Collision avoidance with payment provider IDs (Stripe's
prod_,cus_,sub_) - New
src/id.rsmodule withEntityTypeenum for 15 entity types
- Transaction tracking: Revenue analytics decoupled from licenses
GET /orgs/{org_id}/transactions[/stats]— org-level revenueGET /orgs/{org_id}/projects/{project_id}/transactions[/stats]— project-level revenueGET /orgs/{org_id}/projects/{project_id}/licenses/{license_id}/transactions— license history- Filters:
product_id,transaction_type,date_range,test_mode - Stats:
total_revenue,net_revenue,transactions_by_type
- Feedback and crash reporting: Passthrough to dev-configured endpoints (no PII storage)
POST /feedbackandPOST /crashendpoints (JWT auth required)- Webhook (primary) + email (fallback) delivery with exponential backoff
- Project config:
feedback_webhook_url,feedback_email,crash_webhook_url,crash_email - SDK methods:
submitFeedback(),reportCrash(),reportError()with stack trace sanitization
- Usage metering webhook: Optional billing events for hosted deployments
- Email events:
activation_sent,feedback_sent,crash_sentwithdelivery_method - Sales events:
purchase,renewal,refundwith transaction details - Configure via
PAYCHECK_METERING_WEBHOOK_URL
- Email events:
- Refund handling: Stripe and LemonSqueezy refund webhooks create
refundtransactions - Subscription renewal transactions: Renewal webhooks now create transaction records
- 3-level email config lookup: Product → Project → Org inheritance for
email_config_id - Configurable request body size:
PAYCHECK_MAX_BODY_SIZEenv var (default: 1MB) - Docusaurus documentation site: Comprehensive docs at docs.paycheck.dev
- SDK issuer validation: Rust and TypeScript SDKs reject JWTs not issued by Paycheck
Changed
- Breaking:
/validateendpoint now accepts full JWT token instead of justjti - README simplified to point to hosted documentation
- SDK package metadata updated with correct license (Elastic-2.0) and repository URL
- Use
OsRnginstead ofthread_rng()for all cryptographic operations - Memory limits added to activation rate limiter (DoS prevention)
Fixed
- Project restore endpoint unreachable: Moved to
org_routeslayer (middleware was rejecting soft-deleted projects) - Service config deletion: Now uses TOCTOU-safe transaction
- Soft delete restore cascades:
project_membersnow properly restored with parent - Queries excluding soft-deleted entities: Joined entity lookups now filter
deleted_at - CORS headers: Added
x-on-behalf-ofto allowed headers - Feedback billing fairness: Metering correctly reports
org_keyvssystem_keyfor all email types
Full Changelog: v0.6.2...v0.7.0
v0.6.2
Fixed
- Payment callback webhook race condition: Callback endpoint now polls for up to 500ms before returning
status=pending, catching cases where the browser redirect beats the webhook delivery (typically 100-300ms)
Full Changelog: v0.6.1...v0.6.2
v0.6.1
Changed
- SDKs normalize activation codes: Non-alphanumeric characters (backticks, dots, underscores, etc.) are stripped before validation
- Handles messy copy-paste from emails gracefully (e.g.,
`C9MA-JUFF`→C9MA-JUFF) - Multiple separators collapsed into single dashes
- Handles messy copy-paste from emails gracefully (e.g.,
- Project prefix validation:
license_key_prefixmust contain only alphanumeric characters- Ensures SDK normalization works correctly (non-alphanumeric chars are treated as separators)
Fixed
- Email template uses
<span>instead of<code>to prevent some email clients from adding backticks when copying activation codes
Full Changelog: v0.6.0...v0.6.1
v0.6.0
Added
- Expanded license update API:
PUT /orgs/.../licenses/{id}now supports updatingcustomer_id,expires_at, andupdates_expires_at- Nullable fields can be explicitly set to null via
Option<Option<T>>pattern
- Nullable fields can be explicitly set to null via
- Optional activation code prefix: Server accepts both
PREFIX-XXXX-XXXXand bareXXXX-XXXXformats- Bare codes get project prefix prepended before hashing (no collision risk)
- SDKs validate both formats before making network requests
- Allows apps to display prefix as a visual hint without requiring user input
Changed
- Breaking (Rust SDK): Refactored to sync-first for desktop app compatibility
- Replaced async
reqwest+tokiowith syncureq(no async runtime required) - Removed
MemoryStorage— desktop apps should use persistent file storage - Constructor now takes explicit
storage_dirpath instead of app name - Auto-creates storage directory if it doesn't exist
- Split API:
new()for defaults,with_options()for custom configuration - Simplified TLS feature flags:
rustls-tls(default) ornative-tls
- Replaced async
- Breaking: License update endpoint changed from
PATCHtoPUTfor consistency - Audit action renamed:
UpdateLicenseEmail→UpdateLicense
Full Changelog: v0.5.0...v0.6.0