@colixsystems/widget-sdk 0.278.0 → 0.281.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/contract.cjs +44 -5
- package/dist/contract.js +44 -5
- package/dist/hooks.js +138 -2
- package/dist/index.d.ts +67 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/package.json +1 -1
- package/src/contract.js +44 -5
package/README.md
CHANGED
|
@@ -73,7 +73,8 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
73
73
|
| **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, q?, app_user_id?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| not_sent \| open \| all` (endpoint default `all`; `open` = `pending`, `not_sent` or `expired`); `q` matches the invite's name or email, `app_user_id` returns the invites reserving that user id. `resend(inviteId)` sends the invitation — the first send of a `not_sent` one included. |
|
|
74
74
|
| **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
|
|
75
75
|
| **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId })` (optional: spaceType, folderId) | `{ openPdf, opening, canOpenPdf, exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.renderPdf` + `ctx.device.pdfViewer`, `ctx.filestore.files.exportPdf` — `files.write:*`. `openPdf(html, { fileName? })` renders the HTML to a PDF server-side and OPENS it (web: PDF tab; native: print / share sheet) without saving. `exportToPdf(html, { fileName?, folderId? })` renders it and saves it as a file (`application/pdf`). Same server-side renderer on web + native. |
|
|
76
|
-
| **PAYMENTS** (`ctx.payments`) | `
|
|
76
|
+
| **PAYMENTS** (`ctx.payments`) | `useAccessPlans()` | `{ plans, loading, error, purchaseAvailable, purchase, cancel, refresh }` | `ctx.payments.{listAccessPlans,purchaseAccessPlan,cancelAccessPlan}` — `payments.charge:appUser`. Plans selling access to restricted pages; `purchase(planId)` names only the plan, the server sets price and group. `purchaseAvailable` is `false` in an iOS / Android build (digital goods need store billing): show a visible not-available state. |
|
|
77
|
+
| **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser`. `requestPayment` requires `kind` (`"physical"` \| `"service"` \| `"digital"`): what is sold. A `"digital"` charge is refused in an iOS or Android build (`DIGITAL_SALE_NEEDS_STORE_BILLING`). Rejects with `PaymentError { code, message, retryable }`; when `retryable` is `false` show `message` and drop the retry. Charges are accepted ONLY in the currency the workspace sells in — omit `currency` and the platform applies it (a disagreeing literal is a publish-blocking `payment-currency` finding). |
|
|
77
78
|
| **NOTIFICATIONS** (`ctx.notifications`) | `useSendNotification()` | `{ send, sending, error }` | `ctx.notifications.send` — `notifications.send:appUser`. `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace; call from an event handler (never render); rejects with `NotificationError`. |
|
|
78
79
|
| **NOTIFICATIONS** (`ctx.notifications`) | `useRecordMutes(tableId)` | `{ isMuted, loading, error, refetch }` | `ctx.notifications.mutes` — no scope. Which records of one table the signed-in user has muted, in one request for a whole list; `isMuted(recordId)` turns false when a timed mute lapses, and a `useRecordMute` write shows at once. |
|
|
79
80
|
| **NOTIFICATIONS** (`ctx.notifications`) | `useRecordMute(tableId, recordId)` | `{ muted, mutedUntil, loading, error, mute, unmute, refetch }` | `ctx.notifications.mutes` — no scope. Mutes one record's notifications for the signed-in user, `mute(until)` with an ISO time, a `Date`, or `null` for until unmuted. Needs read access to the record (`RECORD_NOT_FOUND` otherwise). |
|
|
@@ -87,7 +88,19 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
87
88
|
|
|
88
89
|
## Status
|
|
89
90
|
|
|
90
|
-
`v0.
|
|
91
|
+
`v0.281.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
92
|
+
|
|
93
|
+
### What's new in 0.281.0 (contract 1.230.0)
|
|
94
|
+
|
|
95
|
+
**Paid page access in the store builds.** A workspace can sell a `useAccessPlans()` plan in its iOS and Android apps by naming a RevenueCat entitlement for it. In those builds `purchase(planId)` then opens the store's own in-app purchase sheet, and access arrives when RevenueCat confirms the purchase — call `refresh()` a moment later. Each plan now carries `purchase_available`; `purchaseAvailable` is `false` only when no plan can be bought here, so check the plan's own flag before showing its button. A plan or paid-app access bought on the web also grants its RevenueCat entitlement, so `usePurchases().entitlements` in the app includes it. `usePurchases()` itself stays native-only. The payments client's `purchaseAccessPlan()` may answer with `store_product_id` (`@colixsystems/payments-client` 0.4.0).
|
|
96
|
+
|
|
97
|
+
### What's new in 0.280.0 (contract 1.229.0)
|
|
98
|
+
|
|
99
|
+
**`useAccessPlans()` — paid access to restricted pages.** A workspace can sell plans (one-time or monthly) that add the buyer to a group the maker restricted pages to. `useAccessPlans()` returns `{ plans, loading, error, purchaseAvailable, purchase, cancel, refresh }`; each plan is `{ id, name, price_cents, currency, billing, owned, renews, access_ends_at }`. `cancel(planId)` stops a held monthly plan renewing; access continues until `access_ends_at`. `purchase(planId)` sends only the plan id: the server sets the price and the group, so a widget can never name an amount. The host opens hosted Checkout as it does for `usePayments()`, and access arrives when the payment is confirmed — call `refresh()` after the user returns. Page access is a digital good, so `purchaseAvailable` is `false` in an iOS or Android build; render a visible "not available in this app" state there. Needs the `payments.charge:appUser` scope. The injected payments client gains `listAccessPlans()`, `purchaseAccessPlan()` and `cancelAccessPlan()` (`@colixsystems/payments-client` 0.3.0).
|
|
100
|
+
|
|
101
|
+
### What's new in 0.279.0 (contract 1.228.0)
|
|
102
|
+
|
|
103
|
+
**`requestPayment` says what it sells.** `usePayments().requestPayment` now takes a required `kind`: `"physical"` for goods shipped or handed over, `"service"` for a real-world service such as a booking or a ticket, and `"digital"` for content or features used inside the app. The server refuses a charge without one (`CHARGE_KIND_REQUIRED`) and a kind the workspace has not been opened for (`CHARGE_KIND_NOT_ENABLED`). App stores require their own billing for digital goods, so a `"digital"` charge from an iOS or Android build is refused with `DIGITAL_SALE_NEEDS_STORE_BILLING`. All three are non-retryable `PaymentError` codes that carry a message to show. This is a breaking change for a widget that charges without `kind`, so it ships as a minor version.
|
|
91
104
|
|
|
92
105
|
### What's new in 0.278.0 (contract 1.227.0)
|
|
93
106
|
|
package/dist/contract.cjs
CHANGED
|
@@ -2330,13 +2330,37 @@ const HOOKS = [
|
|
|
2330
2330
|
signature: "usePayments()",
|
|
2331
2331
|
returnShape: {
|
|
2332
2332
|
requestPayment:
|
|
2333
|
-
"({ amount_cents, currency?, description, return_path? }) => Promise<{ id, status }> // host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2333
|
+
"({ amount_cents, currency?, description, kind, return_path? }) => Promise<{ id, status }> // kind is REQUIRED: \"physical\" | \"service\" | \"digital\" — what is sold; a digital charge is refused in an iOS/Android build (DIGITAL_SALE_NEEDS_STORE_BILLING); host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2334
2334
|
getPayment:
|
|
2335
2335
|
"(paymentId) => Promise<{ id, status, amount_cents, currency, description }>",
|
|
2336
2336
|
},
|
|
2337
2337
|
requiredContextSlice: ["payments.requestPayment"],
|
|
2338
2338
|
scopes: ["payments.charge:appUser"],
|
|
2339
2339
|
},
|
|
2340
|
+
// sc-15167 — paid access to restricted pages.
|
|
2341
|
+
{
|
|
2342
|
+
name: "useAccessPlans",
|
|
2343
|
+
signature: "useAccessPlans()",
|
|
2344
|
+
returnShape: {
|
|
2345
|
+
plans:
|
|
2346
|
+
"Array<{ id, name, price_cents, currency, billing: \"one_time\" | \"monthly\", owned, renews, access_ends_at, purchase_available }> // owned: the user holds it; renews: a held monthly plan will charge again; access_ends_at: end of held access without a renewal (null for one-time); purchase_available: this plan can be bought here — false in an iOS/Android build for a plan the workspace does not sell in-app",
|
|
2347
|
+
loading: "boolean",
|
|
2348
|
+
error: "PaymentError | null",
|
|
2349
|
+
purchaseAvailable:
|
|
2350
|
+
"boolean // false when no plan can be bought here (an iOS/Android build where the workspace sells no plan in-app) — render a visible not-available state, never a dead button or a blank; check each plan's purchase_available too",
|
|
2351
|
+
purchase:
|
|
2352
|
+
"(planId, { return_path? }?) => Promise<{ plan_id, status: \"ACTIVE\" | \"PENDING\" }> // pass ONLY the plan id; the server sets price and group; the host opens hosted Checkout on the web and the store's in-app purchase sheet in an iOS/Android build; access arrives with the payment confirmation, so refresh() a moment later",
|
|
2353
|
+
cancel:
|
|
2354
|
+
"(planId) => Promise<{ plan_id, renews: false, access_ends_at }> // stop a renewing monthly plan; access runs to access_ends_at — offer it beside every plan with renews: true",
|
|
2355
|
+
refresh: "() => Promise<void> // re-read after returning from Checkout",
|
|
2356
|
+
},
|
|
2357
|
+
requiredContextSlice: [
|
|
2358
|
+
"payments.listAccessPlans",
|
|
2359
|
+
"payments.purchaseAccessPlan",
|
|
2360
|
+
"payments.cancelAccessPlan",
|
|
2361
|
+
],
|
|
2362
|
+
scopes: ["payments.charge:appUser"],
|
|
2363
|
+
},
|
|
2340
2364
|
// sc-4686 — the workspace's charge currency + the only sanctioned way to
|
|
2341
2365
|
// render money. A widget must NOT name a currency: the owner can change it
|
|
2342
2366
|
// after the widget was authored, so a baked symbol goes stale over a charge
|
|
@@ -3147,7 +3171,9 @@ const HOOKS = [
|
|
|
3147
3171
|
"the buyer closed the sheet, so do not show it as a failure. A purchase needs a signed-in app user. NATIVE-ONLY: a " +
|
|
3148
3172
|
"browser has no App Store or Google Play billing, so the web Player (and the Studio canvas) report supported:false " +
|
|
3149
3173
|
"— ALWAYS render a visible 'available in the app' state there, never a blank or a dead button. The Expo export also " +
|
|
3150
|
-
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app."
|
|
3174
|
+
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app. On the web, sell " +
|
|
3175
|
+
"page access with useAccessPlans() instead: a web purchase of a plan (or of paid-app access) also grants its " +
|
|
3176
|
+
"RevenueCat entitlement, so `entitlements` in the app includes what the user bought on the web.",
|
|
3151
3177
|
returnShape: {
|
|
3152
3178
|
supported: "boolean // false on the web Player and in an app built without RevenueCat",
|
|
3153
3179
|
loading: "boolean",
|
|
@@ -4380,9 +4406,15 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4380
4406
|
},
|
|
4381
4407
|
payments: {
|
|
4382
4408
|
description:
|
|
4383
|
-
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment> }; wire is snake_case (amount_cents, return_path). Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4409
|
+
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment>, listAccessPlans() -> Promise<{ data, meta }>, purchaseAccessPlan({ plan_id, return_path? }) -> Promise<{ plan_id, status }>, cancelAccessPlan(planId) -> Promise<{ plan_id, renews, access_ends_at }> }; wire is snake_case (amount_cents, return_path). Backs usePayments() and useAccessPlans(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4384
4410
|
required: true,
|
|
4385
|
-
fields: {
|
|
4411
|
+
fields: {
|
|
4412
|
+
requestPayment: "function",
|
|
4413
|
+
getPayment: "function",
|
|
4414
|
+
listAccessPlans: "function",
|
|
4415
|
+
purchaseAccessPlan: "function",
|
|
4416
|
+
cancelAccessPlan: "function",
|
|
4417
|
+
},
|
|
4386
4418
|
},
|
|
4387
4419
|
// sc-890 — backs useSendNotification().
|
|
4388
4420
|
notifications: {
|
|
@@ -6628,7 +6660,14 @@ const CONTRACT = deepFreeze({
|
|
|
6628
6660
|
// 1.227.0: additive (sc-15163) — new native-only `usePurchases()` hook and
|
|
6629
6661
|
// `PurchasesError`, a `device.purchases` slice, and the vetted
|
|
6630
6662
|
// `react-native-purchases` import.
|
|
6631
|
-
|
|
6663
|
+
// 1.228.0: stricter (sc-15161) — `usePayments().requestPayment` requires
|
|
6664
|
+
// `kind` (physical | service | digital); three new PaymentError codes.
|
|
6665
|
+
// 1.229.0: additive (sc-15167) — `useAccessPlans()`, and the payments slice
|
|
6666
|
+
// gains `listAccessPlans` / `purchaseAccessPlan` / `cancelAccessPlan`.
|
|
6667
|
+
// 1.230.0: additive (sc-15164) — each access plan carries
|
|
6668
|
+
// `purchase_available`; a store build buys a plan the workspace sells
|
|
6669
|
+
// in-app through the store, and web purchases grant RevenueCat entitlements.
|
|
6670
|
+
version: "1.230.0",
|
|
6632
6671
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6633
6672
|
hooks: HOOKS,
|
|
6634
6673
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -2326,13 +2326,37 @@ const HOOKS = [
|
|
|
2326
2326
|
signature: "usePayments()",
|
|
2327
2327
|
returnShape: {
|
|
2328
2328
|
requestPayment:
|
|
2329
|
-
"({ amount_cents, currency?, description, return_path? }) => Promise<{ id, status }> // host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2329
|
+
"({ amount_cents, currency?, description, kind, return_path? }) => Promise<{ id, status }> // kind is REQUIRED: \"physical\" | \"service\" | \"digital\" — what is sold; a digital charge is refused in an iOS/Android build (DIGITAL_SALE_NEEDS_STORE_BILLING); host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2330
2330
|
getPayment:
|
|
2331
2331
|
"(paymentId) => Promise<{ id, status, amount_cents, currency, description }>",
|
|
2332
2332
|
},
|
|
2333
2333
|
requiredContextSlice: ["payments.requestPayment"],
|
|
2334
2334
|
scopes: ["payments.charge:appUser"],
|
|
2335
2335
|
},
|
|
2336
|
+
// sc-15167 — paid access to restricted pages.
|
|
2337
|
+
{
|
|
2338
|
+
name: "useAccessPlans",
|
|
2339
|
+
signature: "useAccessPlans()",
|
|
2340
|
+
returnShape: {
|
|
2341
|
+
plans:
|
|
2342
|
+
"Array<{ id, name, price_cents, currency, billing: \"one_time\" | \"monthly\", owned, renews, access_ends_at, purchase_available }> // owned: the user holds it; renews: a held monthly plan will charge again; access_ends_at: end of held access without a renewal (null for one-time); purchase_available: this plan can be bought here — false in an iOS/Android build for a plan the workspace does not sell in-app",
|
|
2343
|
+
loading: "boolean",
|
|
2344
|
+
error: "PaymentError | null",
|
|
2345
|
+
purchaseAvailable:
|
|
2346
|
+
"boolean // false when no plan can be bought here (an iOS/Android build where the workspace sells no plan in-app) — render a visible not-available state, never a dead button or a blank; check each plan's purchase_available too",
|
|
2347
|
+
purchase:
|
|
2348
|
+
"(planId, { return_path? }?) => Promise<{ plan_id, status: \"ACTIVE\" | \"PENDING\" }> // pass ONLY the plan id; the server sets price and group; the host opens hosted Checkout on the web and the store's in-app purchase sheet in an iOS/Android build; access arrives with the payment confirmation, so refresh() a moment later",
|
|
2349
|
+
cancel:
|
|
2350
|
+
"(planId) => Promise<{ plan_id, renews: false, access_ends_at }> // stop a renewing monthly plan; access runs to access_ends_at — offer it beside every plan with renews: true",
|
|
2351
|
+
refresh: "() => Promise<void> // re-read after returning from Checkout",
|
|
2352
|
+
},
|
|
2353
|
+
requiredContextSlice: [
|
|
2354
|
+
"payments.listAccessPlans",
|
|
2355
|
+
"payments.purchaseAccessPlan",
|
|
2356
|
+
"payments.cancelAccessPlan",
|
|
2357
|
+
],
|
|
2358
|
+
scopes: ["payments.charge:appUser"],
|
|
2359
|
+
},
|
|
2336
2360
|
// sc-4686 — the workspace's charge currency + the only sanctioned way to
|
|
2337
2361
|
// render money. A widget must NOT name a currency: the owner can change it
|
|
2338
2362
|
// after the widget was authored, so a baked symbol goes stale over a charge
|
|
@@ -3143,7 +3167,9 @@ const HOOKS = [
|
|
|
3143
3167
|
"the buyer closed the sheet, so do not show it as a failure. A purchase needs a signed-in app user. NATIVE-ONLY: a " +
|
|
3144
3168
|
"browser has no App Store or Google Play billing, so the web Player (and the Studio canvas) report supported:false " +
|
|
3145
3169
|
"— ALWAYS render a visible 'available in the app' state there, never a blank or a dead button. The Expo export also " +
|
|
3146
|
-
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app."
|
|
3170
|
+
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app. On the web, sell " +
|
|
3171
|
+
"page access with useAccessPlans() instead: a web purchase of a plan (or of paid-app access) also grants its " +
|
|
3172
|
+
"RevenueCat entitlement, so `entitlements` in the app includes what the user bought on the web.",
|
|
3147
3173
|
returnShape: {
|
|
3148
3174
|
supported: "boolean // false on the web Player and in an app built without RevenueCat",
|
|
3149
3175
|
loading: "boolean",
|
|
@@ -4376,9 +4402,15 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4376
4402
|
},
|
|
4377
4403
|
payments: {
|
|
4378
4404
|
description:
|
|
4379
|
-
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment> }; wire is snake_case (amount_cents, return_path). Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4405
|
+
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment>, listAccessPlans() -> Promise<{ data, meta }>, purchaseAccessPlan({ plan_id, return_path? }) -> Promise<{ plan_id, status }>, cancelAccessPlan(planId) -> Promise<{ plan_id, renews, access_ends_at }> }; wire is snake_case (amount_cents, return_path). Backs usePayments() and useAccessPlans(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4380
4406
|
required: true,
|
|
4381
|
-
fields: {
|
|
4407
|
+
fields: {
|
|
4408
|
+
requestPayment: "function",
|
|
4409
|
+
getPayment: "function",
|
|
4410
|
+
listAccessPlans: "function",
|
|
4411
|
+
purchaseAccessPlan: "function",
|
|
4412
|
+
cancelAccessPlan: "function",
|
|
4413
|
+
},
|
|
4382
4414
|
},
|
|
4383
4415
|
// sc-890 — backs useSendNotification().
|
|
4384
4416
|
notifications: {
|
|
@@ -6624,7 +6656,14 @@ const CONTRACT = deepFreeze({
|
|
|
6624
6656
|
// 1.227.0: additive (sc-15163) — new native-only `usePurchases()` hook and
|
|
6625
6657
|
// `PurchasesError`, a `device.purchases` slice, and the vetted
|
|
6626
6658
|
// `react-native-purchases` import.
|
|
6627
|
-
|
|
6659
|
+
// 1.228.0: stricter (sc-15161) — `usePayments().requestPayment` requires
|
|
6660
|
+
// `kind` (physical | service | digital); three new PaymentError codes.
|
|
6661
|
+
// 1.229.0: additive (sc-15167) — `useAccessPlans()`, and the payments slice
|
|
6662
|
+
// gains `listAccessPlans` / `purchaseAccessPlan` / `cancelAccessPlan`.
|
|
6663
|
+
// 1.230.0: additive (sc-15164) — each access plan carries
|
|
6664
|
+
// `purchase_available`; a store build buys a plan the workspace sells
|
|
6665
|
+
// in-app through the store, and web purchases grant RevenueCat entitlements.
|
|
6666
|
+
version: "1.230.0",
|
|
6628
6667
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6629
6668
|
hooks: HOOKS,
|
|
6630
6669
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -6886,7 +6886,10 @@ const NON_RETRYABLE_PAYMENT_CODES = new Set([
|
|
|
6886
6886
|
"AUTH_REQUIRED",
|
|
6887
6887
|
"AMOUNT_TOO_LARGE",
|
|
6888
6888
|
"BUSINESS_IDENTITY_REQUIRED",
|
|
6889
|
+
"CHARGE_KIND_NOT_ENABLED",
|
|
6890
|
+
"CHARGE_KIND_REQUIRED",
|
|
6889
6891
|
"CONNECT_NOT_READY",
|
|
6892
|
+
"DIGITAL_SALE_NEEDS_STORE_BILLING",
|
|
6890
6893
|
"FORBIDDEN",
|
|
6891
6894
|
"INVALID_AMOUNT",
|
|
6892
6895
|
"NOT_FOUND",
|
|
@@ -6912,6 +6915,11 @@ const NON_RETRYABLE_PAYMENT_CODES = new Set([
|
|
|
6912
6915
|
* - "PAYMENTS_UNAVAILABLE" — no installed widget / no payments host
|
|
6913
6916
|
* - "UNSUPPORTED_CURRENCY" — not the platform's charge currency
|
|
6914
6917
|
* - "INVALID_AMOUNT" / "AMOUNT_TOO_LARGE" / "VALIDATION" — bad request
|
|
6918
|
+
* - "CHARGE_KIND_REQUIRED" — `kind` missing or not physical / service
|
|
6919
|
+
* / digital
|
|
6920
|
+
* - "CHARGE_KIND_NOT_ENABLED" — this workspace cannot sell that kind yet
|
|
6921
|
+
* - "DIGITAL_SALE_NEEDS_STORE_BILLING" — a digital sale from an iOS / Android
|
|
6922
|
+
* build: store policy requires store billing
|
|
6915
6923
|
* - "CONNECT_NOT_READY" / "PAYMENTS_DISABLED" — provider not ready
|
|
6916
6924
|
* - "DECLINED" — the charge was declined
|
|
6917
6925
|
* - "INTERNAL" — anything else
|
|
@@ -6972,8 +6980,13 @@ function toPaymentError(err) {
|
|
|
6972
6980
|
* `{ requestPayment, getPayment }`. The wire is snake_case (REQ-GEN-09) —
|
|
6973
6981
|
* pass and read snake_case keys VERBATIM.
|
|
6974
6982
|
*
|
|
6975
|
-
* requestPayment({ amount_cents, currency?, description, return_path? })
|
|
6976
|
-
* → Promise<{ id, status, ... }>.
|
|
6983
|
+
* requestPayment({ amount_cents, currency?, description, kind, return_path? })
|
|
6984
|
+
* → Promise<{ id, status, ... }>. `kind` is REQUIRED and says what is
|
|
6985
|
+
* sold: "physical" (goods shipped or handed over), "service" (a
|
|
6986
|
+
* real-world service: a booking, a ticket, a delivery) or "digital"
|
|
6987
|
+
* (content or features used inside the app). A digital charge is refused
|
|
6988
|
+
* in an iOS / Android build with DIGITAL_SALE_NEEDS_STORE_BILLING — show
|
|
6989
|
+
* the error's message as a visible state, never a blank render. When a hosted-checkout provider
|
|
6977
6990
|
* (Mollie) is active the HOST opens Checkout for you — a same-tab
|
|
6978
6991
|
* redirect on web, the in-app browser on native — so you do NOT open
|
|
6979
6992
|
* anything yourself (no `Linking.openURL`, no `checkout_url` to
|
|
@@ -7055,6 +7068,129 @@ export function usePayments() {
|
|
|
7055
7068
|
return { requestPayment, getPayment };
|
|
7056
7069
|
}
|
|
7057
7070
|
|
|
7071
|
+
/**
|
|
7072
|
+
* Paid access to restricted pages (sc-15167). A workspace sells plans; buying
|
|
7073
|
+
* one adds the signed-in app user to a group the maker restricted pages to, so
|
|
7074
|
+
* those pages open for them. Returns
|
|
7075
|
+
* `{ plans, loading, error, purchaseAvailable, purchase, cancel, refresh }`:
|
|
7076
|
+
*
|
|
7077
|
+
* plans → [{ id, name, price_cents, currency, billing, owned, renews,
|
|
7078
|
+
* access_ends_at, purchase_available }] — `billing` is "one_time" or
|
|
7079
|
+
* "monthly"; `owned` is true once the user holds the plan; `renews` while
|
|
7080
|
+
* a held monthly plan will charge again; `access_ends_at` is when held
|
|
7081
|
+
* access ends without a renewal (null for one-time access);
|
|
7082
|
+
* `purchase_available` is whether this plan can be bought here.
|
|
7083
|
+
* Render the price with `useWorkspaceCurrency().formatMoney(price_cents)`.
|
|
7084
|
+
* purchaseAvailable → false where no plan can be bought here: page access is
|
|
7085
|
+
* a digital good, and an iOS / Android build sells only the plans the
|
|
7086
|
+
* workspace sells in-app (through store billing). Render a visible "not
|
|
7087
|
+
* available in this app" state for a plan whose `purchase_available` is
|
|
7088
|
+
* false — never a dead button and never a blank render.
|
|
7089
|
+
* purchase(planId, { return_path? }?) → Promise<{ plan_id, status }>. Pass
|
|
7090
|
+
* ONLY the plan id: the server sets the price and the group, so a widget can
|
|
7091
|
+
* never name an amount. The HOST takes the payment: hosted Checkout on the
|
|
7092
|
+
* web, the store's in-app purchase sheet in an iOS / Android build. `status`
|
|
7093
|
+
* is "ACTIVE" when access was granted at once (the mock provider) and
|
|
7094
|
+
* "PENDING" while payment is confirmed; access arrives with the
|
|
7095
|
+
* confirmation, never from the client.
|
|
7096
|
+
* cancel(planId) → Promise<{ plan_id, renews: false, access_ends_at }> —
|
|
7097
|
+
* stop a held monthly plan renewing (`renews` true). Access continues
|
|
7098
|
+
* until `access_ends_at`; offer it beside every renewing plan.
|
|
7099
|
+
* refresh() → re-read the plans. Call it when the user returns from Checkout
|
|
7100
|
+
* (`useRouteParams().payment === "success"`) and poll a few times, since the
|
|
7101
|
+
* confirmation may land a moment after the redirect.
|
|
7102
|
+
*
|
|
7103
|
+
* `error` and `purchase` rejections are a `PaymentError` — show its `message`
|
|
7104
|
+
* when `retryable` is false. Requires the `payments.charge:appUser` scope.
|
|
7105
|
+
*/
|
|
7106
|
+
export function useAccessPlans() {
|
|
7107
|
+
const ctx = useWidgetContextOrThrow("useAccessPlans");
|
|
7108
|
+
const paymentsRef = useRef(ctx.payments);
|
|
7109
|
+
paymentsRef.current = ctx.payments;
|
|
7110
|
+
const supported = Boolean(
|
|
7111
|
+
ctx.payments && typeof ctx.payments.listAccessPlans === "function",
|
|
7112
|
+
);
|
|
7113
|
+
const [state, setState] = useState(() => ({
|
|
7114
|
+
plans: [],
|
|
7115
|
+
loading: supported,
|
|
7116
|
+
error: supported
|
|
7117
|
+
? null
|
|
7118
|
+
: new PaymentError(
|
|
7119
|
+
"PAYMENTS_UNAVAILABLE",
|
|
7120
|
+
"Plans are only available for an installed widget running in the app.",
|
|
7121
|
+
),
|
|
7122
|
+
purchaseAvailable: false,
|
|
7123
|
+
}));
|
|
7124
|
+
|
|
7125
|
+
const refresh = useCallback(async () => {
|
|
7126
|
+
const payments = paymentsRef.current;
|
|
7127
|
+
if (!payments || typeof payments.listAccessPlans !== "function") return;
|
|
7128
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
7129
|
+
try {
|
|
7130
|
+
const res = await payments.listAccessPlans();
|
|
7131
|
+
setState({
|
|
7132
|
+
plans: Array.isArray(res && res.data) ? res.data : [],
|
|
7133
|
+
loading: false,
|
|
7134
|
+
error: null,
|
|
7135
|
+
purchaseAvailable: Boolean(res && res.meta && res.meta.purchase_available),
|
|
7136
|
+
});
|
|
7137
|
+
} catch (err) {
|
|
7138
|
+
setState((prev) => ({ ...prev, loading: false, error: toPaymentError(err) }));
|
|
7139
|
+
}
|
|
7140
|
+
}, []);
|
|
7141
|
+
|
|
7142
|
+
useEffect(() => {
|
|
7143
|
+
if (supported) refresh();
|
|
7144
|
+
}, [supported, refresh]);
|
|
7145
|
+
|
|
7146
|
+
const purchase = useCallback(
|
|
7147
|
+
async (planId, opts) => {
|
|
7148
|
+
const payments = paymentsRef.current;
|
|
7149
|
+
if (!payments || typeof payments.purchaseAccessPlan !== "function") {
|
|
7150
|
+
throw new PaymentError(
|
|
7151
|
+
"PAYMENTS_UNAVAILABLE",
|
|
7152
|
+
"Plans are only available for an installed widget running in the app.",
|
|
7153
|
+
);
|
|
7154
|
+
}
|
|
7155
|
+
let res;
|
|
7156
|
+
try {
|
|
7157
|
+
res = await payments.purchaseAccessPlan({
|
|
7158
|
+
plan_id: planId,
|
|
7159
|
+
return_path: opts && opts.return_path,
|
|
7160
|
+
});
|
|
7161
|
+
} catch (err) {
|
|
7162
|
+
throw toPaymentError(err);
|
|
7163
|
+
}
|
|
7164
|
+
if (res && res.status === "ACTIVE") await refresh();
|
|
7165
|
+
return res;
|
|
7166
|
+
},
|
|
7167
|
+
[refresh],
|
|
7168
|
+
);
|
|
7169
|
+
|
|
7170
|
+
const cancel = useCallback(
|
|
7171
|
+
async (planId) => {
|
|
7172
|
+
const payments = paymentsRef.current;
|
|
7173
|
+
if (!payments || typeof payments.cancelAccessPlan !== "function") {
|
|
7174
|
+
throw new PaymentError(
|
|
7175
|
+
"PAYMENTS_UNAVAILABLE",
|
|
7176
|
+
"Plans are only available for an installed widget running in the app.",
|
|
7177
|
+
);
|
|
7178
|
+
}
|
|
7179
|
+
let res;
|
|
7180
|
+
try {
|
|
7181
|
+
res = await payments.cancelAccessPlan(planId);
|
|
7182
|
+
} catch (err) {
|
|
7183
|
+
throw toPaymentError(err);
|
|
7184
|
+
}
|
|
7185
|
+
await refresh();
|
|
7186
|
+
return res;
|
|
7187
|
+
},
|
|
7188
|
+
[refresh],
|
|
7189
|
+
);
|
|
7190
|
+
|
|
7191
|
+
return { ...state, purchase, cancel, refresh };
|
|
7192
|
+
}
|
|
7193
|
+
|
|
7058
7194
|
/* ============================================================================
|
|
7059
7195
|
* NOTIFICATIONS CLIENT — ctx.notifications (@colixsystems/notifications-client)
|
|
7060
7196
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -697,6 +697,14 @@ export interface AssetsClient {
|
|
|
697
697
|
export interface PaymentsClient {
|
|
698
698
|
requestPayment(body: PaymentRequest): Promise<PaymentResult>;
|
|
699
699
|
getPayment(paymentId: string): Promise<PaymentResult>;
|
|
700
|
+
listAccessPlans(): Promise<{
|
|
701
|
+
data: AccessPlan[];
|
|
702
|
+
meta: { total: number; limit: number; offset: number; purchase_available: boolean };
|
|
703
|
+
}>;
|
|
704
|
+
purchaseAccessPlan(body: { plan_id: string; return_path?: string }): Promise<AccessPlanPurchase>;
|
|
705
|
+
cancelAccessPlan(
|
|
706
|
+
planId: string,
|
|
707
|
+
): Promise<{ plan_id: string; renews: false; access_ends_at: string | null }>;
|
|
700
708
|
}
|
|
701
709
|
|
|
702
710
|
/**
|
|
@@ -1465,6 +1473,14 @@ export interface PaymentRequest {
|
|
|
1465
1473
|
amount_cents: number;
|
|
1466
1474
|
currency?: string;
|
|
1467
1475
|
description: string;
|
|
1476
|
+
/**
|
|
1477
|
+
* What the charge sells. Required. "physical": goods shipped or handed over;
|
|
1478
|
+
* "service": a real-world service such as a booking or a ticket; "digital":
|
|
1479
|
+
* content or features used inside the app. App stores require their own
|
|
1480
|
+
* billing for digital goods, so a "digital" charge from an iOS or Android
|
|
1481
|
+
* build is refused with DIGITAL_SALE_NEEDS_STORE_BILLING.
|
|
1482
|
+
*/
|
|
1483
|
+
kind: "physical" | "service" | "digital";
|
|
1468
1484
|
/**
|
|
1469
1485
|
* Site-relative path to return to after Checkout (e.g. "/cart"). Defaults
|
|
1470
1486
|
* to the current page, so the user lands back where they started.
|
|
@@ -1478,6 +1494,7 @@ export interface PaymentResult {
|
|
|
1478
1494
|
amount_cents?: number;
|
|
1479
1495
|
currency?: string;
|
|
1480
1496
|
description?: string;
|
|
1497
|
+
kind?: "physical" | "service" | "digital" | null;
|
|
1481
1498
|
}
|
|
1482
1499
|
|
|
1483
1500
|
export interface PaymentsApi {
|
|
@@ -1496,6 +1513,53 @@ export interface PaymentsApi {
|
|
|
1496
1513
|
*/
|
|
1497
1514
|
export function usePayments(): PaymentsApi;
|
|
1498
1515
|
|
|
1516
|
+
/** A plan selling access to restricted pages, as the app user sees it. */
|
|
1517
|
+
export interface AccessPlan {
|
|
1518
|
+
id: string;
|
|
1519
|
+
name: string;
|
|
1520
|
+
/** In the currency's minor unit. */
|
|
1521
|
+
price_cents: number;
|
|
1522
|
+
currency: string;
|
|
1523
|
+
billing: "one_time" | "monthly";
|
|
1524
|
+
/** True once the signed-in user holds the plan. */
|
|
1525
|
+
owned: boolean;
|
|
1526
|
+
/** A held monthly plan that will charge again; false once cancelled. */
|
|
1527
|
+
renews: boolean;
|
|
1528
|
+
/** ISO time held access ends without a renewal; null for one-time access. */
|
|
1529
|
+
access_ends_at: string | null;
|
|
1530
|
+
/** Whether this plan can be bought here (false in a store build that does not sell it in-app). */
|
|
1531
|
+
purchase_available: boolean;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
export interface AccessPlanPurchase {
|
|
1535
|
+
plan_id: string;
|
|
1536
|
+
/** ACTIVE: granted at once. PENDING: waiting for the payment to confirm. */
|
|
1537
|
+
status: "ACTIVE" | "PENDING";
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
export interface AccessPlansApi {
|
|
1541
|
+
plans: AccessPlan[];
|
|
1542
|
+
loading: boolean;
|
|
1543
|
+
error: PaymentError | null;
|
|
1544
|
+
/**
|
|
1545
|
+
* False where no plan can be bought here (an iOS / Android build sells only
|
|
1546
|
+
* the plans the workspace sells in-app): render a visible not-available state.
|
|
1547
|
+
*/
|
|
1548
|
+
purchaseAvailable: boolean;
|
|
1549
|
+
/** Pass only the plan id; the server sets price and group. */
|
|
1550
|
+
purchase(planId: string, opts?: { return_path?: string }): Promise<AccessPlanPurchase>;
|
|
1551
|
+
/** Stop a held monthly plan renewing; access runs to `access_ends_at`. */
|
|
1552
|
+
cancel(planId: string): Promise<{ plan_id: string; renews: false; access_ends_at: string | null }>;
|
|
1553
|
+
refresh(): Promise<void>;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Paid access to restricted pages: list the workspace's plans and buy one.
|
|
1558
|
+
* Buying adds the user to the group the maker restricted pages to, once the
|
|
1559
|
+
* payment is confirmed. Requires the `payments.charge:appUser` scope.
|
|
1560
|
+
*/
|
|
1561
|
+
export function useAccessPlans(): AccessPlansApi;
|
|
1562
|
+
|
|
1499
1563
|
/**
|
|
1500
1564
|
* Rejection thrown by both `usePayments()` callbacks. `code` is the server's
|
|
1501
1565
|
* own reason when it sent one; `message` is its user-safe sentence. Branch on
|
|
@@ -1514,6 +1578,9 @@ export class PaymentError extends Error {
|
|
|
1514
1578
|
| "AMOUNT_TOO_LARGE"
|
|
1515
1579
|
| "VALIDATION"
|
|
1516
1580
|
| "CONNECT_NOT_READY"
|
|
1581
|
+
| "CHARGE_KIND_REQUIRED"
|
|
1582
|
+
| "CHARGE_KIND_NOT_ENABLED"
|
|
1583
|
+
| "DIGITAL_SALE_NEEDS_STORE_BILLING"
|
|
1517
1584
|
| "PAYMENTS_DISABLED"
|
|
1518
1585
|
| "DECLINED"
|
|
1519
1586
|
| "FORBIDDEN"
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.281.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -2326,13 +2326,37 @@ const HOOKS = [
|
|
|
2326
2326
|
signature: "usePayments()",
|
|
2327
2327
|
returnShape: {
|
|
2328
2328
|
requestPayment:
|
|
2329
|
-
"({ amount_cents, currency?, description, return_path? }) => Promise<{ id, status }> // host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2329
|
+
"({ amount_cents, currency?, description, kind, return_path? }) => Promise<{ id, status }> // kind is REQUIRED: \"physical\" | \"service\" | \"digital\" — what is sold; a digital charge is refused in an iOS/Android build (DIGITAL_SALE_NEEDS_STORE_BILLING); host opens hosted Checkout; rejects with PaymentError { code, message, retryable } — render the message when retryable is false, never \"try again\"",
|
|
2330
2330
|
getPayment:
|
|
2331
2331
|
"(paymentId) => Promise<{ id, status, amount_cents, currency, description }>",
|
|
2332
2332
|
},
|
|
2333
2333
|
requiredContextSlice: ["payments.requestPayment"],
|
|
2334
2334
|
scopes: ["payments.charge:appUser"],
|
|
2335
2335
|
},
|
|
2336
|
+
// sc-15167 — paid access to restricted pages.
|
|
2337
|
+
{
|
|
2338
|
+
name: "useAccessPlans",
|
|
2339
|
+
signature: "useAccessPlans()",
|
|
2340
|
+
returnShape: {
|
|
2341
|
+
plans:
|
|
2342
|
+
"Array<{ id, name, price_cents, currency, billing: \"one_time\" | \"monthly\", owned, renews, access_ends_at, purchase_available }> // owned: the user holds it; renews: a held monthly plan will charge again; access_ends_at: end of held access without a renewal (null for one-time); purchase_available: this plan can be bought here — false in an iOS/Android build for a plan the workspace does not sell in-app",
|
|
2343
|
+
loading: "boolean",
|
|
2344
|
+
error: "PaymentError | null",
|
|
2345
|
+
purchaseAvailable:
|
|
2346
|
+
"boolean // false when no plan can be bought here (an iOS/Android build where the workspace sells no plan in-app) — render a visible not-available state, never a dead button or a blank; check each plan's purchase_available too",
|
|
2347
|
+
purchase:
|
|
2348
|
+
"(planId, { return_path? }?) => Promise<{ plan_id, status: \"ACTIVE\" | \"PENDING\" }> // pass ONLY the plan id; the server sets price and group; the host opens hosted Checkout on the web and the store's in-app purchase sheet in an iOS/Android build; access arrives with the payment confirmation, so refresh() a moment later",
|
|
2349
|
+
cancel:
|
|
2350
|
+
"(planId) => Promise<{ plan_id, renews: false, access_ends_at }> // stop a renewing monthly plan; access runs to access_ends_at — offer it beside every plan with renews: true",
|
|
2351
|
+
refresh: "() => Promise<void> // re-read after returning from Checkout",
|
|
2352
|
+
},
|
|
2353
|
+
requiredContextSlice: [
|
|
2354
|
+
"payments.listAccessPlans",
|
|
2355
|
+
"payments.purchaseAccessPlan",
|
|
2356
|
+
"payments.cancelAccessPlan",
|
|
2357
|
+
],
|
|
2358
|
+
scopes: ["payments.charge:appUser"],
|
|
2359
|
+
},
|
|
2336
2360
|
// sc-4686 — the workspace's charge currency + the only sanctioned way to
|
|
2337
2361
|
// render money. A widget must NOT name a currency: the owner can change it
|
|
2338
2362
|
// after the widget was authored, so a baked symbol goes stale over a charge
|
|
@@ -3143,7 +3167,9 @@ const HOOKS = [
|
|
|
3143
3167
|
"the buyer closed the sheet, so do not show it as a failure. A purchase needs a signed-in app user. NATIVE-ONLY: a " +
|
|
3144
3168
|
"browser has no App Store or Google Play billing, so the web Player (and the Studio canvas) report supported:false " +
|
|
3145
3169
|
"— ALWAYS render a visible 'available in the app' state there, never a blank or a dead button. The Expo export also " +
|
|
3146
|
-
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app."
|
|
3170
|
+
"reports supported:false until the workspace connects RevenueCat in Payments and rebuilds the app. On the web, sell " +
|
|
3171
|
+
"page access with useAccessPlans() instead: a web purchase of a plan (or of paid-app access) also grants its " +
|
|
3172
|
+
"RevenueCat entitlement, so `entitlements` in the app includes what the user bought on the web.",
|
|
3147
3173
|
returnShape: {
|
|
3148
3174
|
supported: "boolean // false on the web Player and in an app built without RevenueCat",
|
|
3149
3175
|
loading: "boolean",
|
|
@@ -4376,9 +4402,15 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4376
4402
|
},
|
|
4377
4403
|
payments: {
|
|
4378
4404
|
description:
|
|
4379
|
-
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment> }; wire is snake_case (amount_cents, return_path). Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4405
|
+
"Injected @colixsystems/payments-client instance (REQ-BILL-07-WIDGETPAY). { requestPayment(body) -> Promise<{ id, status }>, getPayment(id) -> Promise<payment>, listAccessPlans() -> Promise<{ data, meta }>, purchaseAccessPlan({ plan_id, return_path? }) -> Promise<{ plan_id, status }>, cancelAccessPlan(planId) -> Promise<{ plan_id, renews, access_ends_at }> }; wire is snake_case (amount_cents, return_path). Backs usePayments() and useAccessPlans(); requires the payments.charge:appUser scope. The host opens hosted Checkout itself — same-tab on web, in-app browser on native (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
4380
4406
|
required: true,
|
|
4381
|
-
fields: {
|
|
4407
|
+
fields: {
|
|
4408
|
+
requestPayment: "function",
|
|
4409
|
+
getPayment: "function",
|
|
4410
|
+
listAccessPlans: "function",
|
|
4411
|
+
purchaseAccessPlan: "function",
|
|
4412
|
+
cancelAccessPlan: "function",
|
|
4413
|
+
},
|
|
4382
4414
|
},
|
|
4383
4415
|
// sc-890 — backs useSendNotification().
|
|
4384
4416
|
notifications: {
|
|
@@ -6624,7 +6656,14 @@ const CONTRACT = deepFreeze({
|
|
|
6624
6656
|
// 1.227.0: additive (sc-15163) — new native-only `usePurchases()` hook and
|
|
6625
6657
|
// `PurchasesError`, a `device.purchases` slice, and the vetted
|
|
6626
6658
|
// `react-native-purchases` import.
|
|
6627
|
-
|
|
6659
|
+
// 1.228.0: stricter (sc-15161) — `usePayments().requestPayment` requires
|
|
6660
|
+
// `kind` (physical | service | digital); three new PaymentError codes.
|
|
6661
|
+
// 1.229.0: additive (sc-15167) — `useAccessPlans()`, and the payments slice
|
|
6662
|
+
// gains `listAccessPlans` / `purchaseAccessPlan` / `cancelAccessPlan`.
|
|
6663
|
+
// 1.230.0: additive (sc-15164) — each access plan carries
|
|
6664
|
+
// `purchase_available`; a store build buys a plan the workspace sells
|
|
6665
|
+
// in-app through the store, and web purchases grant RevenueCat entitlements.
|
|
6666
|
+
version: "1.230.0",
|
|
6628
6667
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6629
6668
|
hooks: HOOKS,
|
|
6630
6669
|
primitives: PRIMITIVES,
|