mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-05 19:17:32 +00:00
The catalog now tells subscription products from one-time ones and maps a store plan to its tier and months. A subscription receipt is checked with the store once: with a grantable charge, the service plans the charge's rows from the store's period start, signs the first month, and writes the payment, purchase, charge, ledger rows, issuance, state and Play token in one transaction, answering with the subscription state. Without one, it answers payment_pending (300 s while the period has not started or the store is pending, a day otherwise), receipt_invalid for an ended Play subscription, or test_purchase, and writes nothing. Replays carry the state. The store verifier interface gains the subscription check, which no real store implements yet; the fake store does. Run: 001-badge-subscriptions, slice 3b
402 lines
11 KiB
JSON
402 lines
11 KiB
JSON
{
|
|
"metadata": {
|
|
"description": "JTD schema for badge service RPC requests and responses, see badges-rpc.md."
|
|
},
|
|
"definitions": {
|
|
"base64url": {
|
|
"type": "string",
|
|
"metadata": {"format": "base64url encoded string"}
|
|
},
|
|
"purchaseKey": {
|
|
"ref": "base64url",
|
|
"metadata": {"comment": "must match the RPC transport key"}
|
|
},
|
|
"currencyAmount": {
|
|
"type": "uint32",
|
|
"metadata": {"comment": "USD etc. are in minor units, following Stripe etc. convention"}
|
|
},
|
|
"badgeInfo": {
|
|
"properties": {
|
|
"badgeType": {"type": "string"},
|
|
"badgeExtra": {"type": "string"},
|
|
"badgeExpiry": {"type": "timestamp"}
|
|
}
|
|
},
|
|
"badgeRequest": {
|
|
"properties": {
|
|
"masterKey": {"ref": "base64url"},
|
|
"badgeInfo": {"ref": "badgeInfo"}
|
|
}
|
|
},
|
|
"credential": {
|
|
"properties": {
|
|
"badgeKeyIdx": {"type": "uint16"},
|
|
"masterKey": {"ref": "base64url"},
|
|
"signature": {"ref": "base64url"},
|
|
"badgeInfo": {"ref": "badgeInfo"}
|
|
}
|
|
},
|
|
"paymentVia": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"card": {
|
|
"properties": {"provider": {"enum": ["stripe"]}}
|
|
},
|
|
"crypto": {
|
|
"properties": {"currency": {"enum": ["btc", "xmr"]}}
|
|
}
|
|
}
|
|
},
|
|
"paymentTo": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"card": {
|
|
"properties": {
|
|
"provider": {"enum": ["stripe"]},
|
|
"url": {"type": "string"}
|
|
}
|
|
},
|
|
"crypto": {
|
|
"properties": {
|
|
"currency": {"enum": ["btc", "xmr"]},
|
|
"address": {"type": "string"},
|
|
"cryptoAmount": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"invoice": {
|
|
"properties": {
|
|
"invoiceId": {"type": "string"},
|
|
"price": {"ref": "currencyAmount"},
|
|
"amount": {
|
|
"ref": "currencyAmount",
|
|
"metadata": {"comment": "price - discount - credit"}
|
|
},
|
|
"currency": {"type": "string"},
|
|
"expiresAt": {"type": "timestamp"},
|
|
"paymentTo": {"ref": "paymentTo"}
|
|
},
|
|
"optionalProperties": {
|
|
"discount": {
|
|
"ref": "currencyAmount",
|
|
"metadata": {"comment": "discount amount from the price"}
|
|
},
|
|
"credit": {
|
|
"ref": "currencyAmount",
|
|
"metadata": {"comment": "credit for upgrade"}
|
|
}
|
|
}
|
|
},
|
|
"payment": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"apple": {
|
|
"properties": {"jws": {"type": "string"}}
|
|
},
|
|
"google": {
|
|
"properties": {"productId": {"type": "string"}, "token": {"type": "string"}}
|
|
},
|
|
"invoice": {
|
|
"properties": {"invoiceId": {"type": "string"}}
|
|
},
|
|
"receipt": {
|
|
"properties": {
|
|
"receipt": {
|
|
"type": "string",
|
|
"metadata": {"comment": "transfer of unissued months"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"upgrade": {
|
|
"properties": {
|
|
"fromPurchaseKey": {"ref": "base64url"},
|
|
"receipt": {"type": "string"},
|
|
"receiptSignature": {"ref": "base64url"},
|
|
"balance": {"ref": "balance"}
|
|
}
|
|
},
|
|
"catalog": {
|
|
"properties": {
|
|
"prices": {"elements": {"ref": "badgePrice"}},
|
|
"offers": {"elements": {"ref": "badgeOffer"}}
|
|
}
|
|
},
|
|
"badgePrice": {
|
|
"properties": {
|
|
"priceId": {"type": "string"},
|
|
"badgeType": {"type": "string"},
|
|
"monthPrice": {"ref": "currencyAmount"},
|
|
"currency": {"type": "string"},
|
|
"status": {"ref": "badgeItemStatus"},
|
|
"createdAt": {"type": "timestamp"}
|
|
}
|
|
},
|
|
"badgeOffer": {
|
|
"properties": {
|
|
"offerId": {"type": "string"},
|
|
"months": {"type": "uint8"},
|
|
"discount": {"ref": "offerDiscount"},
|
|
"status": {"ref": "badgeItemStatus"},
|
|
"createdAt": {"type": "timestamp"}
|
|
},
|
|
"optionalProperties": {
|
|
"priceId": {
|
|
"type": "string",
|
|
"metadata": {"comment": "absent applies to any price"}
|
|
}
|
|
}
|
|
},
|
|
"offerDiscount": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"freeMonths": {
|
|
"properties": {"freeMonths": {"type": "uint8"}}
|
|
},
|
|
"discount": {
|
|
"properties": {
|
|
"discount": {
|
|
"type": "uint8",
|
|
"metadata": {"comment": "percent"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"badgeItemStatus": {
|
|
"enum": ["active", "deprecated", "disabled"],
|
|
"metadata": {"comment": "disabled is not sent"}
|
|
},
|
|
"statement": {
|
|
"properties": {
|
|
"entries": {"elements": {"ref": "ledgerEntry"}}
|
|
},
|
|
"optionalProperties": {
|
|
"previousEntryId": {
|
|
"type": "string",
|
|
"metadata": {"comment": "matches the client's asserted entryId, absent for the full ledger"}
|
|
}
|
|
}
|
|
},
|
|
"balance": {
|
|
"properties": {
|
|
"lastEntry": {"ref": "ledgerEntry"}
|
|
}
|
|
},
|
|
"ledgerEntry": {
|
|
"properties": {
|
|
"entryId": {"type": "string"},
|
|
"changeMonths": {"type": "int16"},
|
|
"balanceMonths": {"type": "uint16"},
|
|
"balanceStartTs": {"type": "timestamp"},
|
|
"balanceAnchorTs": {
|
|
"type": "timestamp",
|
|
"metadata": {"comment": "start of the current run of months; boundaries are counted from it"}
|
|
},
|
|
"balanceBadgeType": {"type": "string"},
|
|
"createdAt": {"type": "timestamp"},
|
|
"entryType": {"ref": "ledgerEntryType"}
|
|
},
|
|
"optionalProperties": {
|
|
"wasPausedSince": {"type": "timestamp"}
|
|
}
|
|
},
|
|
"ledgerEntryType": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"credit": {
|
|
"properties": {"credit": {"ref": "ledgerCredit"}}
|
|
},
|
|
"debit": {
|
|
"properties": {"debit": {"ref": "ledgerDebit"}}
|
|
}
|
|
}
|
|
},
|
|
"ledgerCredit": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"payment": {
|
|
"optionalProperties": {
|
|
"invoiceId": {
|
|
"type": "string",
|
|
"metadata": {"comment": "absent for store payments"}
|
|
}
|
|
}
|
|
},
|
|
"code": {
|
|
"optionalProperties": {},
|
|
"metadata": {"comment": "a redeemed code; its invoice belongs to the buyer, not the redeemer"}
|
|
},
|
|
"charge": {
|
|
"properties": {"chargeId": {"type": "string"}},
|
|
"metadata": {"comment": "a store subscription's charge, dated (createdAt) to the store's billing period start"}
|
|
},
|
|
"support": {
|
|
"optionalProperties": {}
|
|
},
|
|
"transferIn": {
|
|
"properties": {"fromPurchaseKey": {"ref": "base64url"}}
|
|
},
|
|
"opening": {
|
|
"optionalProperties": {}
|
|
}
|
|
}
|
|
},
|
|
"ledgerDebit": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"refund": {
|
|
"optionalProperties": {}
|
|
},
|
|
"upgrade": {
|
|
"properties": {"toPurchaseKey": {"ref": "base64url"}}
|
|
},
|
|
"transferOut": {
|
|
"properties": {"toPurchaseKey": {"ref": "base64url"}}
|
|
},
|
|
"support": {
|
|
"optionalProperties": {}
|
|
},
|
|
"badge": {
|
|
"optionalProperties": {}
|
|
},
|
|
"lapse": {
|
|
"optionalProperties": {}
|
|
}
|
|
}
|
|
},
|
|
"request": {
|
|
"properties": {
|
|
"version": {"type": "uint16"},
|
|
"request": {"ref": "command"}
|
|
},
|
|
"optionalProperties": {
|
|
"purchaseKey": {
|
|
"ref": "purchaseKey",
|
|
"metadata": {"comment": "optional for getBadgeCatalog, required for other commands"}
|
|
}
|
|
}
|
|
},
|
|
"command": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"getBadgeCatalog": {
|
|
"optionalProperties": {}
|
|
},
|
|
"getBadgeInvoice": {
|
|
"properties": {
|
|
"priceId": {"type": "string"},
|
|
"badgeInfo": {"ref": "badgeInfo"},
|
|
"paymentVia": {"ref": "paymentVia"}
|
|
},
|
|
"optionalProperties": {
|
|
"offerId": {
|
|
"type": "string",
|
|
"metadata": {"comment": "absent for 1 month at the badge price"}
|
|
},
|
|
"upgrade": {
|
|
"ref": "upgrade",
|
|
"metadata": {"comment": "upgrade non-store badge"}
|
|
}
|
|
}
|
|
},
|
|
"redeemBadgeCode": {
|
|
"properties": {
|
|
"masterKey": {"ref": "base64url"},
|
|
"code": {"type": "string"}
|
|
}
|
|
},
|
|
"purchaseBadge": {
|
|
"properties": {
|
|
"masterKey": {"ref": "base64url"},
|
|
"payment": {"ref": "payment"}
|
|
},
|
|
"optionalProperties": {
|
|
"upgrade": {
|
|
"ref": "upgrade",
|
|
"metadata": {"comment": "upgrade store 1-time badge"}
|
|
}
|
|
}
|
|
},
|
|
"upgradeBadgeSubscription": {
|
|
"properties": {
|
|
"badgeRequest": {
|
|
"ref": "badgeRequest",
|
|
"metadata": {"comment": "known wrong: to be dropped when implemented, see badges-rpc.md"}
|
|
},
|
|
"payment": {
|
|
"ref": "payment",
|
|
"metadata": {"comment": "store payments only"}
|
|
},
|
|
"balance": {"ref": "balance"}
|
|
}
|
|
},
|
|
"issueBadge": {
|
|
"properties": {
|
|
"balance": {"ref": "balance"}
|
|
}
|
|
},
|
|
"pauseBadge": {
|
|
"optionalProperties": {}
|
|
}
|
|
}
|
|
},
|
|
"response": {
|
|
"discriminator": "type",
|
|
"mapping": {
|
|
"badgeCatalog": {
|
|
"properties": {
|
|
"catalog": {"ref": "catalog"}
|
|
},
|
|
"optionalProperties": {
|
|
"badgeStatement": {
|
|
"ref": "statement",
|
|
"metadata": {"comment": "for signed getBadgeCatalog"}
|
|
}
|
|
}
|
|
},
|
|
"badgeInvoice": {
|
|
"properties": {
|
|
"invoice": {"ref": "invoice"},
|
|
"badgeType": {"type": "string"},
|
|
"months": {"type": "uint8"}
|
|
}
|
|
},
|
|
"badgeCredential": {
|
|
"properties": {
|
|
"statement": {"ref": "statement"}
|
|
},
|
|
"optionalProperties": {
|
|
"credential": {
|
|
"ref": "credential",
|
|
"metadata": {"comment": "absent when no balance to issueBadge or no current credential for pause"}
|
|
},
|
|
"receipt": {
|
|
"type": "string",
|
|
"metadata": {"comment": "not provided for lifetime badges"}
|
|
},
|
|
"subscription": {
|
|
"type": "string",
|
|
"metadata": {"comment": "SubscriptionState: renewing, cancelled, billingRetry, ended; any other value is read as not ended. Present exactly when a store subscription funds the purchase"}
|
|
}
|
|
}
|
|
},
|
|
"error": {
|
|
"properties": {
|
|
"code": {
|
|
"type": "string",
|
|
"metadata": {"comment": "BadgeServiceErrorCode"}
|
|
}
|
|
},
|
|
"optionalProperties": {
|
|
"message": {"type": "string"},
|
|
"retryAfter": {"type": "uint32"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|