openvibers/openvibe.contracts Commits
Add pastdue renewal grace and renewal reasons to billing events (#31)
Changes
@@@ -4,6 +4,15 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,the notes were in the tag and commit messages (`git tag -n1`).++## 0.94.2 — 2026-10-04++++**`past_due` renewal grace on the billing events** (plan T5 step 11, s1). Additive: no field is required or removed.++`billing.entitlement.changed@1` `subscription.status` gains `past_due` (a failed renewal, retried until `grace_until`);++new optional `grace_until` (date-time or null) and `renewal_period_end`; `reason` adds `renewal_failed`, `grace_ended`++and `credit_refund`. `billing.subscription@1` status lists `past_due`. `billing.transaction.reversed@1` already carried a++refund of a credit-paid subscription period (type `refund`, from/to swapped); its description now says so, and a fixture++pins it. New test `test/billing-grace.test.js`. Billing and VIP pin this tag.++## 0.94.1 — 2026-10-04`community.moderation.action@1` catalog version is `1.1.0` (the additive space-moderator actions of 0.94.0).@@@ -13,7 +13,7 @@"provider": { "type": "string", "description": "credit, or the provider that takes the payments." },"provider_ref": { "type": ["string", "null"] },"route": { "type": ["string", "null"], "description": "site or direct (paid to the streamer's own provider account)." },-- "status": { "type": "string", "description": "active, canceled, expired, …" },++ "status": { "type": "string", "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …" },"auto_renew": { "type": "boolean" },"cancel_at_period_end": { "type": "boolean" },"price_cents": { "type": ["integer", "null"] },@@@ -18,7 +18,7 @@"additionalProperties": false,"properties": {"id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },-- "status": { "enum": ["active", "canceled", "expired"] },++ "status": { "enum": ["active", "past_due", "canceled", "expired"], "description": "past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due." },"auto_renew": { "type": "boolean" },"cancel_at_period_end": { "type": "boolean" },"provider": { "type": "string" }@@@ -26,15 +26,17 @@},"reason": {"type": "string",-- "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."++ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded)."},"transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "granted/renewed: the transaction that paid the period." },++ "grace_until": { "type": ["string", "null"], "format": "date-time", "description": "renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise." },++ "renewal_period_end": { "type": "string", "format": "date-time", "description": "renewal_failed, grace_ended: the end of the period the failed renewal charge was for." },"revoked_period": {"type": "object","required": ["starts_at", "ends_at"],"additionalProperties": false,"properties": { "starts_at": { "type": "string", "format": "date-time" }, "ends_at": { "type": "string", "format": "date-time" } },-- "description": "refund/chargeback: the period the reversed payment had granted."++ "description": "refund/chargeback/credit_refund: the period the reversed payment had granted."}}}@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json","title": "BillingTransactionReversedPayload",-- "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",++ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.","type": "object","required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata", "reverses_txn"],"additionalProperties": false,nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": false,++ "expires_at": null,++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "grace",++ "auto_renew": true,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "renewal_failed"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": false,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "expired",++ "auto_renew": false,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "grace_ended",++ "grace_until": null,++ "renewal_period_end": "2026-11-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "subject": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"++ },++ "streamer": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "kind": "channel_subscription",++ "active": false,++ "expires_at": "2026-10-23T10:00:00.000Z",++ "subscription": {++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "status": "past_due",++ "auto_renew": true,++ "cancel_at_period_end": false,++ "provider": "credit"++ },++ "reason": "renewal_failed",++ "grace_until": "2026-10-26T10:00:00.000Z",++ "renewal_period_end": "2026-11-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "subscriber": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" },++ "streamer": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },++ "provider": "credit",++ "status": "past_due",++ "auto_renew": true,++ "cancel_at_period_end": false,++ "current_period_end": "2026-10-23T10:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4",++ "type": "refund",++ "test": false,++ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "provider": null,++ "metadata": {++ "original_type": "subscription",++ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",++ "amount_bits": 500++ },++ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ5"++}@@@ -21095,7 +21095,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json","title": "BillingTransactionReversedPayload",-- "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",++ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.","type": "object","required": ["transaction_id",@@@ -21239,9 +21239,11 @@"status": {"enum": ["active",++ "past_due","canceled","expired"-- ]++ ],++ "description": "past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due."},"auto_renew": {"type": "boolean"@@@ -21256,13 +21258,26 @@},"reason": {"type": "string",-- "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."++ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded)."},"transaction_id": {"type": "string","pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$","description": "granted/renewed: the transaction that paid the period."},++ "grace_until": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise."++ },++ "renewal_period_end": {++ "type": "string",++ "format": "date-time",++ "description": "renewal_failed, grace_ended: the end of the period the failed renewal charge was for."++ },"revoked_period": {"type": "object","required": [@@@ -21280,7 +21295,7 @@"format": "date-time"}},-- "description": "refund/chargeback: the period the reversed payment had granted."++ "description": "refund/chargeback/credit_refund: the period the reversed payment had granted."}}}@@@ -23421,7 +23436,7 @@},"status": {"type": "string",-- "description": "active, canceled, expired, …"++ "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …"},"auto_renew": {"type": "boolean"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3049,7 +3049,7 @@},"status": {"type": "string",-- "description": "active, canceled, expired, …"++ "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …"},"auto_renew": {"type": "boolean"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRe.Stream API",-- "version": "0.94.1",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.94.1",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.94.2",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -28461,7 +28461,7 @@ export interface BillingTransactionSettledPayload {/** [email protected] (owner: billing) *//**-- * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.++ * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.*/export interface BillingTransactionReversedPayload {transaction_id: string;@@@ -28524,13 +28524,16 @@ export interface BillingEntitlementChangedPayload {expires_at: string | null;subscription: {id: string;-- status: "active" | "canceled" | "expired";++ /**++ * past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due.++ */++ status: "active" | "past_due" | "canceled" | "expired";auto_renew: boolean;cancel_at_period_end: boolean;provider: string;} | null;/**-- * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback.++ * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded).*/reason: string;/**@@@ -28538,7 +28541,15 @@ export interface BillingEntitlementChangedPayload {*/transaction_id?: string;/**-- * refund/chargeback: the period the reversed payment had granted.++ * renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise.++ */++ grace_until?: string | null;++ /**++ * renewal_failed, grace_ended: the end of the period the failed renewal charge was for.++ */++ renewal_period_end?: string;++ /**++ * refund/chargeback/credit_refund: the period the reversed payment had granted.*/revoked_period?: {starts_at: string;@@@ -29211,7 +29222,7 @@ export interface BillingSubscription {*/route?: string | null;/**-- * active, canceled, expired, …++ * active, past_due (a failed renewal, retried until grace_until), canceled, expired, …*/status: string;auto_renew: boolean;@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.94.1",++ "version": "0.94.2","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.94.1",++ "version": "0.94.2","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.94.1",++ "version": "0.94.2","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",nnew file mode 100644@@@ -0,0 +1,35 @@++'use strict';++// T5 step 11: a failed renewal moves a subscription to past_due grace (additive on the billing events).++const assert = require('assert');++const contracts = require('..');++++const base = {++ subject: { type: 'user', id: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPR' },++ streamer: { type: 'user', id: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ' },++ kind: 'channel_subscription',++ active: false,++ expires_at: '2026-10-23T10:00:00.000Z',++ subscription: { id: 'sub_01JAB3C4D5E6F7G8H9J0K1MNQ0', status: 'active', auto_renew: true, cancel_at_period_end: false, provider: 'credit' },++ reason: 'renewed',++};++const V = (p) => contracts.validate('billing.entitlement.changed@1', p);++const withSub = (status) => ({ ...base, subscription: { ...base.subscription, status } });++++assert.ok(V(base).valid, 'a payload without the grace fields still validates');++const pastDue = { ...withSub('past_due'), reason: 'renewal_failed', grace_until: '2026-10-26T10:00:00.000Z', renewal_period_end: '2026-11-23T10:00:00.000Z' };++assert.ok(V(pastDue).valid, 'past_due with grace_until and renewal_failed validates');++assert.ok(V({ ...pastDue, grace_until: null }).valid, 'grace_until may be null');++assert.ok(V({ ...withSub('expired'), reason: 'grace_ended' }).valid, 'grace_ended validates without the optional fields');++assert.ok(V({ ...withSub('expired'), reason: 'credit_refund', revoked_period: { starts_at: '2026-09-23T10:00:00.000Z', ends_at: '2026-10-23T10:00:00.000Z' } }).valid, 'credit_refund validates');++assert.ok(['active', 'canceled', 'expired'].every((s) => V(withSub(s)).valid), 'the earlier statuses still validate');++assert.ok(!V(withSub('grace')).valid, 'an unknown status is refused');++assert.ok(!V({ ...pastDue, grace_until: 'soon' }).valid, 'grace_until is a date-time');++assert.ok(!V({ ...pastDue, renewal_period_end: null }).valid, 'renewal_period_end is a date-time when present');++++const rev = {++ transaction_id: 'txn_01JAB3C4D5E6F7G8H9J0K1MNQ4', type: 'refund', test: false,++ from_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ', to_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPR', provider: null,++ metadata: { original_type: 'subscription' }, reverses_txn: 'txn_01JAB3C4D5E6F7G8H9J0K1MNQ5',++};++assert.ok(contracts.validate('billing.transaction.reversed@1', rev).valid, 'a refund of a credit-paid subscription period validates');++console.log('billing-grace ok');@@@ -907,6 +907,7 @@ sub(path.join(__dirname, 'product-catalog.test.js'));sub(path.join(__dirname, 'zones.test.js'));// *.usage.recorded rollups are counts: only network, codes and zone may consume one, never billing (T5 step 8).sub(path.join(__dirname, 'usage-topics.test.js'));++sub(path.join(__dirname, 'billing-grace.test.js'));// ── Generated output and compatibility gate ──────────────────────────────sub(path.join(__dirname, 'platform-fabric.test.js'));
@@ -4,6 +4,15 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,15 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.94.2 — 2026-10-04
+
+**`past_due` renewal grace on the billing events** (plan T5 step 11, s1). Additive: no field is required or removed.
+`billing.entitlement.changed@1` `subscription.status` gains `past_due` (a failed renewal, retried until `grace_until`);
+new optional `grace_until` (date-time or null) and `renewal_period_end`; `reason` adds `renewal_failed`, `grace_ended`
+and `credit_refund`. `billing.subscription@1` status lists `past_due`. `billing.transaction.reversed@1` already carried a
+refund of a credit-paid subscription period (type `refund`, from/to swapped); its description now says so, and a fixture
+pins it. New test `test/billing-grace.test.js`. Billing and VIP pin this tag.
+
## 0.94.1 — 2026-10-04
## 0.94.1 — 2026-10-04
`community.moderation.action@1` catalog version is `1.1.0` (the additive space-moderator actions of 0.94.0).
`community.moderation.action@1` catalog version is `1.1.0` (the additive space-moderator actions of 0.94.0).
@@ -13,7 +13,7 @@
@@ -13,7 +13,7 @@
"provider": { "type": "string", "description": "credit, or the provider that takes the payments." },
"provider": { "type": "string", "description": "credit, or the provider that takes the payments." },
"provider_ref": { "type": ["string", "null"] },
"provider_ref": { "type": ["string", "null"] },
"route": { "type": ["string", "null"], "description": "site or direct (paid to the streamer's own provider account)." },
"route": { "type": ["string", "null"], "description": "site or direct (paid to the streamer's own provider account)." },
- "status": { "type": "string", "description": "active, canceled, expired, …" },
+ "status": { "type": "string", "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …" },
"auto_renew": { "type": "boolean" },
"auto_renew": { "type": "boolean" },
"cancel_at_period_end": { "type": "boolean" },
"cancel_at_period_end": { "type": "boolean" },
"price_cents": { "type": ["integer", "null"] },
"price_cents": { "type": ["integer", "null"] },
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
"id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },
"id": { "type": "string", "pattern": "^sub_[0-9A-HJKMNP-TV-Z]{26}$" },
- "status": { "enum": ["active", "canceled", "expired"] },
+ "status": { "enum": ["active", "past_due", "canceled", "expired"], "description": "past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due." },
"auto_renew": { "type": "boolean" },
"auto_renew": { "type": "boolean" },
"cancel_at_period_end": { "type": "boolean" },
"cancel_at_period_end": { "type": "boolean" },
"provider": { "type": "string" }
"provider": { "type": "string" }
@@ -26,15 +26,17 @@
@@ -26,15 +26,17 @@
},
},
"reason": {
"reason": {
"type": "string",
"type": "string",
- "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."
+ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded)."
},
},
"transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "granted/renewed: the transaction that paid the period." },
"transaction_id": { "type": "string", "pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$", "description": "granted/renewed: the transaction that paid the period." },
+ "grace_until": { "type": ["string", "null"], "format": "date-time", "description": "renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise." },
+ "renewal_period_end": { "type": "string", "format": "date-time", "description": "renewal_failed, grace_ended: the end of the period the failed renewal charge was for." },
"revoked_period": {
"revoked_period": {
"type": "object",
"type": "object",
"required": ["starts_at", "ends_at"],
"required": ["starts_at", "ends_at"],
"additionalProperties": false,
"additionalProperties": false,
"properties": { "starts_at": { "type": "string", "format": "date-time" }, "ends_at": { "type": "string", "format": "date-time" } },
"properties": { "starts_at": { "type": "string", "format": "date-time" }, "ends_at": { "type": "string", "format": "date-time" } },
- "description": "refund/chargeback: the period the reversed payment had granted."
+ "description": "refund/chargeback/credit_refund: the period the reversed payment had granted."
}
}
}
}
}
}
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
"$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
"title": "BillingTransactionReversedPayload",
"title": "BillingTransactionReversedPayload",
- "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
+ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
"type": "object",
"type": "object",
"required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata", "reverses_txn"],
"required": ["transaction_id", "type", "test", "from_subject", "to_subject", "provider", "metadata", "reverses_txn"],
"additionalProperties": false,
"additionalProperties": false,
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": false,
+ "expires_at": null,
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "grace",
+ "auto_renew": true,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "renewal_failed"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": false,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "expired",
+ "auto_renew": false,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "grace_ended",
+ "grace_until": null,
+ "renewal_period_end": "2026-11-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "subject": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
+ },
+ "streamer": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "kind": "channel_subscription",
+ "active": false,
+ "expires_at": "2026-10-23T10:00:00.000Z",
+ "subscription": {
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "status": "past_due",
+ "auto_renew": true,
+ "cancel_at_period_end": false,
+ "provider": "credit"
+ },
+ "reason": "renewal_failed",
+ "grace_until": "2026-10-26T10:00:00.000Z",
+ "renewal_period_end": "2026-11-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "subscriber": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR" },
+ "streamer": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
+ "provider": "credit",
+ "status": "past_due",
+ "auto_renew": true,
+ "cancel_at_period_end": false,
+ "current_period_end": "2026-10-23T10:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "transaction_id": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ4",
+ "type": "refund",
+ "test": false,
+ "from_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "to_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "provider": null,
+ "metadata": {
+ "original_type": "subscription",
+ "subscription_id": "sub_01JAB3C4D5E6F7G8H9J0K1MNQ0",
+ "amount_bits": 500
+ },
+ "reverses_txn": "txn_01JAB3C4D5E6F7G8H9J0K1MNQ5"
+}
@@ -21095,7 +21095,7 @@
@@ -21095,7 +21095,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
"$id": "https://openvibe.network/contracts/events/payloads/billing.transaction.reversed.v1.json",
"title": "BillingTransactionReversedPayload",
"title": "BillingTransactionReversedPayload",
- "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
+ "description": "billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.",
"type": "object",
"type": "object",
"required": [
"required": [
"transaction_id",
"transaction_id",
@@ -21239,9 +21239,11 @@
@@ -21239,9 +21239,11 @@
"status": {
"status": {
"enum": [
"enum": [
"active",
"active",
+ "past_due",
"canceled",
"canceled",
"expired"
"expired"
- ]
+ ],
+ "description": "past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due."
},
},
"auto_renew": {
"auto_renew": {
"type": "boolean"
"type": "boolean"
@@ -21256,13 +21258,26 @@
@@ -21256,13 +21258,26 @@
},
},
"reason": {
"reason": {
"type": "string",
"type": "string",
- "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback."
+ "description": "granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded)."
},
},
"transaction_id": {
"transaction_id": {
"type": "string",
"type": "string",
"pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
"pattern": "^txn_[0-9A-HJKMNP-TV-Z]{26}$",
"description": "granted/renewed: the transaction that paid the period."
"description": "granted/renewed: the transaction that paid the period."
},
},
+ "grace_until": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise."
+ },
+ "renewal_period_end": {
+ "type": "string",
+ "format": "date-time",
+ "description": "renewal_failed, grace_ended: the end of the period the failed renewal charge was for."
+ },
"revoked_period": {
"revoked_period": {
"type": "object",
"type": "object",
"required": [
"required": [
@@ -21280,7 +21295,7 @@
@@ -21280,7 +21295,7 @@
"format": "date-time"
"format": "date-time"
}
}
},
},
- "description": "refund/chargeback: the period the reversed payment had granted."
+ "description": "refund/chargeback/credit_refund: the period the reversed payment had granted."
}
}
}
}
}
}
@@ -23421,7 +23436,7 @@
@@ -23421,7 +23436,7 @@
},
},
"status": {
"status": {
"type": "string",
"type": "string",
- "description": "active, canceled, expired, …"
+ "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …"
},
},
"auto_renew": {
"auto_renew": {
"type": "boolean"
"type": "boolean"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3049,7 +3049,7 @@
@@ -3049,7 +3049,7 @@
},
},
"status": {
"status": {
"type": "string",
"type": "string",
- "description": "active, canceled, expired, …"
+ "description": "active, past_due (a failed renewal, retried until grace_until), canceled, expired, …"
},
},
"auto_renew": {
"auto_renew": {
"type": "boolean"
"type": "boolean"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.94.1",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.94.1",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.94.1): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.94.2",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.94.2): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -28461,7 +28461,7 @@ export interface BillingTransactionSettledPayload {
@@ -28461,7 +28461,7 @@ export interface BillingTransactionSettledPayload {
/** [email protected] (owner: billing) */
/** [email protected] (owner: billing) */
/**
/**
- * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out. Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.
+ * billing.transaction.reversed v1 (OpenVibe.Billing server/ops/reversals.js reverseReceipt, transfers.js refund, cashouts.js deny). A transaction reversing an earlier one was posted: a provider refund or chargeback, a donation refund, a denied cash-out, a refund of a credit-paid subscription period (type refund, reverses_txn names the subscription transaction, from_subject and to_subject swapped, provider null; the entitlement then changes with reason credit_refund). Envelope: subject { type: transaction, id: txn_… (the reversing transaction) }, visibility internal, priority important, actor service:billing.
*/
*/
export interface BillingTransactionReversedPayload {
export interface BillingTransactionReversedPayload {
transaction_id: string;
transaction_id: string;
@@ -28524,13 +28524,16 @@ export interface BillingEntitlementChangedPayload {
@@ -28524,13 +28524,16 @@ export interface BillingEntitlementChangedPayload {
expires_at: string | null;
expires_at: string | null;
subscription: {
subscription: {
id: string;
id: string;
- status: "active" | "canceled" | "expired";
+ /**
+ * past_due: a renewal charge failed and Billing retries it until grace_until; the entitlement is not active while past_due.
+ */
+ status: "active" | "past_due" | "canceled" | "expired";
auto_renew: boolean;
auto_renew: boolean;
cancel_at_period_end: boolean;
cancel_at_period_end: boolean;
provider: string;
provider: string;
} | null;
} | null;
/**
/**
- * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback.
+ * granted, renewed, canceled, expired, stripe_not_renewed, stripe_deleted, renewal_insufficient_credit, refund, chargeback, renewal_failed (a renewal charge failed: status past_due, grace_until set), grace_ended (the grace window passed without a paid renewal), credit_refund (a credit-paid period was refunded).
*/
*/
reason: string;
reason: string;
/**
/**
@@ -28538,7 +28541,15 @@ export interface BillingEntitlementChangedPayload {
@@ -28538,7 +28541,15 @@ export interface BillingEntitlementChangedPayload {
*/
*/
transaction_id?: string;
transaction_id?: string;
/**
/**
- * refund/chargeback: the period the reversed payment had granted.
+ * renewal_failed: when Billing stops retrying the renewal and ends the subscription; null or absent otherwise.
+ */
+ grace_until?: string | null;
+ /**
+ * renewal_failed, grace_ended: the end of the period the failed renewal charge was for.
+ */
+ renewal_period_end?: string;
+ /**
+ * refund/chargeback/credit_refund: the period the reversed payment had granted.
*/
*/
revoked_period?: {
revoked_period?: {
starts_at: string;
starts_at: string;
@@ -29211,7 +29222,7 @@ export interface BillingSubscription {
@@ -29211,7 +29222,7 @@ export interface BillingSubscription {
*/
*/
route?: string | null;
route?: string | null;
/**
/**
- * active, canceled, expired, …
+ * active, past_due (a failed renewal, retried until grace_until), canceled, expired, …
*/
*/
status: string;
status: string;
auto_renew: boolean;
auto_renew: boolean;
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.94.1",
+ "version": "0.94.2",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.94.1",
+ "version": "0.94.2",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.94.1",
+ "version": "0.94.2",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+'use strict';
+// T5 step 11: a failed renewal moves a subscription to past_due grace (additive on the billing events).
+const assert = require('assert');
+const contracts = require('..');
+
+const base = {
+ subject: { type: 'user', id: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPR' },
+ streamer: { type: 'user', id: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ' },
+ kind: 'channel_subscription',
+ active: false,
+ expires_at: '2026-10-23T10:00:00.000Z',
+ subscription: { id: 'sub_01JAB3C4D5E6F7G8H9J0K1MNQ0', status: 'active', auto_renew: true, cancel_at_period_end: false, provider: 'credit' },
+ reason: 'renewed',
+};
+const V = (p) => contracts.validate('billing.entitlement.changed@1', p);
+const withSub = (status) => ({ ...base, subscription: { ...base.subscription, status } });
+
+assert.ok(V(base).valid, 'a payload without the grace fields still validates');
+const pastDue = { ...withSub('past_due'), reason: 'renewal_failed', grace_until: '2026-10-26T10:00:00.000Z', renewal_period_end: '2026-11-23T10:00:00.000Z' };
+assert.ok(V(pastDue).valid, 'past_due with grace_until and renewal_failed validates');
+assert.ok(V({ ...pastDue, grace_until: null }).valid, 'grace_until may be null');
+assert.ok(V({ ...withSub('expired'), reason: 'grace_ended' }).valid, 'grace_ended validates without the optional fields');
+assert.ok(V({ ...withSub('expired'), reason: 'credit_refund', revoked_period: { starts_at: '2026-09-23T10:00:00.000Z', ends_at: '2026-10-23T10:00:00.000Z' } }).valid, 'credit_refund validates');
+assert.ok(['active', 'canceled', 'expired'].every((s) => V(withSub(s)).valid), 'the earlier statuses still validate');
+assert.ok(!V(withSub('grace')).valid, 'an unknown status is refused');
+assert.ok(!V({ ...pastDue, grace_until: 'soon' }).valid, 'grace_until is a date-time');
+assert.ok(!V({ ...pastDue, renewal_period_end: null }).valid, 'renewal_period_end is a date-time when present');
+
+const rev = {
+ transaction_id: 'txn_01JAB3C4D5E6F7G8H9J0K1MNQ4', type: 'refund', test: false,
+ from_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ', to_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPR', provider: null,
+ metadata: { original_type: 'subscription' }, reverses_txn: 'txn_01JAB3C4D5E6F7G8H9J0K1MNQ5',
+};
+assert.ok(contracts.validate('billing.transaction.reversed@1', rev).valid, 'a refund of a credit-paid subscription period validates');
+console.log('billing-grace ok');
@@ -907,6 +907,7 @@ sub(path.join(__dirname, 'product-catalog.test.js'));
@@ -907,6 +907,7 @@ sub(path.join(__dirname, 'product-catalog.test.js'));
sub(path.join(__dirname, 'zones.test.js'));
sub(path.join(__dirname, 'zones.test.js'));
// *.usage.recorded rollups are counts: only network, codes and zone may consume one, never billing (T5 step 8).
// *.usage.recorded rollups are counts: only network, codes and zone may consume one, never billing (T5 step 8).
sub(path.join(__dirname, 'usage-topics.test.js'));
sub(path.join(__dirname, 'usage-topics.test.js'));
+sub(path.join(__dirname, 'billing-grace.test.js'));
// ── Generated output and compatibility gate ──────────────────────────────
// ── Generated output and compatibility gate ──────────────────────────────
sub(path.join(__dirname, 'platform-fabric.test.js'));
sub(path.join(__dirname, 'platform-fabric.test.js'));