Skip to content

Releases: SmartAppsCo/Paycheck

v0.9.0

07 Mar 05:11
f013a58

Choose a tag to compare

Added

  • OS capture on device activation: Record the operating system where licensed software is activated. The os field 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 /redeem request. Developers can override with DeviceInfo { os: "custom" }.
  • New os field on DeviceInfo (SDK input) and LicenseDeviceInfo (SDK response) across all SDKs.
  • Database migration 002: Adds os TEXT column to existing devices tables. Fresh databases include it in the schema automatically.

Changed

  • /redeem request body: New optional os field (max 64 chars). Fully backward compatible — omitting it results in null.
  • Device reactivation preserves OS: When an existing device reactivates, a new os value updates the record, but omitting it preserves the previous value (COALESCE semantics).
  • Bruno Local.bru environment 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

15 Feb 11:31
8384097

Choose a tag to compare

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 during validate_online() / validate({ online: true }). Default: 24 hours. Based on JWT iat claim.

Changed

  • validate_online() returns OfflineValidateResult with claims: Previously returned a separate ValidateResult type without claims, forcing callers to decode the token separately. Now returns the same type as validate() and sync(), with claims, reason, and full local validation (signature, issuer, device, expiration) before the server call.
  • validate_online() grace period on server unreachable: Instead of immediately returning valid: false when 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 /refresh round trip: Previously called /validate then conditionally /refresh (two requests). Now calls /refresh directly, which validates and returns fresh claims in one call. Also distinguishes network errors (offline fallback) from server rejections (revoked/invalid).
  • Removed ValidateResult type from both SDKs: Was redundant with OfflineValidateResult and 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

07 Feb 09:30
4076afd

Choose a tag to compare

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
  • Deserialize derive on metering event types for downstream consumption

Changed

  • Replaced jwt-simple with jsonwebtoken: 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

05 Feb 04:44
d01c53d

Choose a tag to compare

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}/tags endpoint for user tags (admin+ required)
    • New PATCH /operators/organizations/{id}/tags endpoint 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: Blocks POST /buy when org has matching tag
    • PAYCHECK_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

04 Feb 15:04
e6d5714

Choose a tag to compare

Fixed

  • JTI prefix consistency: New JWT IDs now use pc_jti_ prefix via EntityType::Jti.gen_id() (existing tokens unaffected - stored JTI is reused on refresh)

Full Changelog: v0.7.1...v0.7.2

v0.7.1

04 Feb 14:57
e6f725e

Choose a tag to compare

Fixed

  • API key prefix consistency: New API keys now use pc_key_ prefix via EntityType::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

04 Feb 12:26
ce1ae34

Choose a tag to compare

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.rs module with EntityType enum for 15 entity types
  • Transaction tracking: Revenue analytics decoupled from licenses
    • GET /orgs/{org_id}/transactions[/stats] — org-level revenue
    • GET /orgs/{org_id}/projects/{project_id}/transactions[/stats] — project-level revenue
    • GET /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 /feedback and POST /crash endpoints (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_sent with delivery_method
    • Sales events: purchase, renewal, refund with transaction details
    • Configure via PAYCHECK_METERING_WEBHOOK_URL
  • Refund handling: Stripe and LemonSqueezy refund webhooks create refund transactions
  • 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_SIZE env 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: /validate endpoint now accepts full JWT token instead of just jti
  • README simplified to point to hosted documentation
  • SDK package metadata updated with correct license (Elastic-2.0) and repository URL
  • Use OsRng instead of thread_rng() for all cryptographic operations
  • Memory limits added to activation rate limiter (DoS prevention)

Fixed

  • Project restore endpoint unreachable: Moved to org_routes layer (middleware was rejecting soft-deleted projects)
  • Service config deletion: Now uses TOCTOU-safe transaction
  • Soft delete restore cascades: project_members now properly restored with parent
  • Queries excluding soft-deleted entities: Joined entity lookups now filter deleted_at
  • CORS headers: Added x-on-behalf-of to allowed headers
  • Feedback billing fairness: Metering correctly reports org_key vs system_key for all email types

Full Changelog: v0.6.2...v0.7.0

v0.6.2

01 Feb 15:16
e1374d0

Choose a tag to compare

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

01 Feb 14:42
2d24414

Choose a tag to compare

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
  • Project prefix validation: license_key_prefix must 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

01 Feb 14:01
70d20a1

Choose a tag to compare

Added

  • Expanded license update API: PUT /orgs/.../licenses/{id} now supports updating customer_id, expires_at, and updates_expires_at
    • Nullable fields can be explicitly set to null via Option<Option<T>> pattern
  • Optional activation code prefix: Server accepts both PREFIX-XXXX-XXXX and bare XXXX-XXXX formats
    • 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 + tokio with sync ureq (no async runtime required)
    • Removed MemoryStorage — desktop apps should use persistent file storage
    • Constructor now takes explicit storage_dir path 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) or native-tls
  • Breaking: License update endpoint changed from PATCH to PUT for consistency
  • Audit action renamed: UpdateLicenseEmailUpdateLicense

Full Changelog: v0.5.0...v0.6.0