@stacksjs/defaults 0.74.69 → 0.75.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/ai/skills/stacks-payments/SKILL.md +28 -3
- package/app/Actions/Auth/CreateTokenAction.ts +1 -1
- package/app/Actions/Auth/ListTokensAction.ts +1 -1
- package/app/Actions/Auth/ResendVerificationAction.ts +1 -1
- package/app/Actions/Auth/RevokeTokenAction.ts +1 -1
- package/app/Actions/Auth/VerifyRegistrationAction.ts +1 -1
- package/app/Actions/Billable/CheckoutAction.ts +8 -11
- package/app/Actions/Cms/PostStoreAction.ts +4 -1
- package/app/Actions/Dashboard/Auth/MeAction.ts +1 -1
- package/app/Actions/Dashboard/Settings/BillingShowAction.ts +1 -1
- package/app/Actions/Payment/CancelSubscriptionAction.ts +21 -3
- package/app/Actions/Payment/CreateCheckoutAction.ts +6 -1
- package/app/Actions/Payment/CreateInvoiceSubscription.ts +12 -1
- package/app/Actions/Payment/CreatePaymentIntentAction.ts +9 -4
- package/app/Actions/Payment/CreateSetupIntentAction.ts +6 -1
- package/app/Actions/Payment/CreateSubscriptionAction.ts +9 -2
- package/app/Actions/Payment/DeleteDefaultPaymentAction.ts +6 -1
- package/app/Actions/Payment/FetchActiveSubscriptionAction.ts +6 -1
- package/app/Actions/Payment/FetchDefaultPaymentMethodAction.ts +6 -1
- package/app/Actions/Payment/FetchPaymentCustomerAction.ts +8 -1
- package/app/Actions/Payment/FetchPaymentMethodsAction.ts +6 -1
- package/app/Actions/Payment/FetchTransactionHistoryAction.ts +1 -1
- package/app/Actions/Payment/SetDefaultPaymentAction.ts +10 -2
- package/app/Actions/Payment/SetUserDefaultPaymentAction.ts +8 -1
- package/app/Actions/Payment/StorePaymentMethodAction.ts +6 -1
- package/app/Actions/Payment/StoreTransactionAction.ts +19 -1
- package/app/Actions/Payment/UpdateCustomerAction.ts +23 -7
- package/app/Actions/Payment/UpdateDefaultPaymentMethodAction.ts +6 -1
- package/app/Actions/Payment/UpdateSubscriptionAction.ts +7 -2
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/resources/components/Dashboard/Auth/Login.stx +2 -2
- package/resources/components/Dashboard/Auth/Register.stx +4 -4
- package/resources/components/Dashboard/Infrastructure/LogsDashboard.stx +1 -0
- package/resources/components/Dashboard/Kanban/KanbanCardDialog.stx +1 -0
- package/resources/components/Dashboard/MobileSidebar.stx +1 -0
- package/resources/components/Dashboard/Teams/TeamPeopleDashboard.stx +2 -2
- package/resources/components/Dashboard/UI/Drawer.stx +1 -0
- package/resources/components/Dashboard/UI/Modal.stx +1 -0
- package/resources/plugins/preloader.ts +5 -5
|
@@ -429,9 +429,11 @@ Plans use `productName`, `description`, `metadata`, and a `pricing` array where
|
|
|
429
429
|
## User Model Requirements
|
|
430
430
|
The `UserModel` must have:
|
|
431
431
|
- `id`, `name`, `email`, `stripe_id` fields
|
|
432
|
-
- `hasStripeId()` method -- returns boolean
|
|
433
432
|
- `update(data)` method -- for persisting `stripe_id`
|
|
434
|
-
- `activeSubscription()` method -- for subscription updates
|
|
433
|
+
- `activeSubscription()` method -- for subscription updates (bound by the `billable` trait)
|
|
434
|
+
|
|
435
|
+
There is no `user.hasStripeId()` instance method. Use
|
|
436
|
+
`manageCustomer.hasStripeId(user)`, which reads `stripe_id`.
|
|
435
437
|
|
|
436
438
|
The framework default `storage/framework/defaults/app/Models/User.ts` sets
|
|
437
439
|
`billable: false` intentionally because not every application uses payments.
|
|
@@ -439,7 +441,30 @@ Run `buddy publish:model User`, keep the override at `app/Models/User.ts`, and
|
|
|
439
441
|
enable its `billable` trait before calling instance helpers such as
|
|
440
442
|
`activeSubscription()`, `paymentMethods()`, or `createSetupIntent()`. A payment
|
|
441
443
|
Action must report the missing trait clearly instead of calling an undefined
|
|
442
|
-
method.
|
|
444
|
+
method. `isBillable(user)` from `@stacksjs/orm` is that check, and narrows the
|
|
445
|
+
user to `BillableMethods` (the instance surface, derived from
|
|
446
|
+
`createBillableMethods`) so no cast is needed:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
import { isBillable } from '@stacksjs/orm'
|
|
450
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
451
|
+
|
|
452
|
+
const user = await request.user()
|
|
453
|
+
if (!user)
|
|
454
|
+
return response.unauthorized('Authentication required')
|
|
455
|
+
if (!isBillable(user))
|
|
456
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
457
|
+
|
|
458
|
+
const customer = await user.retrieveStripeUser()
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The instance methods are exactly the keys of `createBillableMethods` in
|
|
462
|
+
`core/orm/src/traits/billable.ts`. Among them: `retrieveStripeUser()`,
|
|
463
|
+
`createPayment(amount, options)`, `setDefaultPaymentMethod(id)` (a number is the
|
|
464
|
+
local row, a string is Stripe's `pm_...` id), `deletePaymentMethod(id)`,
|
|
465
|
+
`syncStripeCustomerDetails(options)`, `storeTransaction(productId, options)`,
|
|
466
|
+
`newSubscription(type, lookupKey, options)`. Anything else is not a function at
|
|
467
|
+
runtime.
|
|
443
468
|
|
|
444
469
|
## Gotchas
|
|
445
470
|
- Stripe API keys MUST be in `.env` as `STRIPE_SECRET_KEY` and `STRIPE_PUBLISHABLE_KEY` -- never hardcode them in config files
|
|
@@ -39,7 +39,7 @@ export default new Action({
|
|
|
39
39
|
return response.badRequest('At least one valid token scope is required')
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
|
-
const result = await createToken(user.id, name, scopeArray, {
|
|
42
|
+
const result = await createToken(Number(user.id), name, scopeArray, {
|
|
43
43
|
expiresInMinutes,
|
|
44
44
|
withRefreshToken,
|
|
45
45
|
refreshExpiresInDays: 30,
|
|
@@ -13,7 +13,7 @@ export default new Action({
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const { resendVerificationEmail } = await import('@stacksjs/auth')
|
|
16
|
-
const result = await resendVerificationEmail(user)
|
|
16
|
+
const result = await resendVerificationEmail({ ...user, id: Number(user.id) })
|
|
17
17
|
|
|
18
18
|
if (!result.success) {
|
|
19
19
|
return response.json({ success: false, message: result.message }, 422)
|
|
@@ -20,7 +20,7 @@ export default new Action({
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Verify the token belongs to the user
|
|
23
|
-
const userTokens = await tokens(user.id)
|
|
23
|
+
const userTokens = await tokens(Number(user.id))
|
|
24
24
|
const tokenToRevoke = userTokens.find(t => t.id === tokenId)
|
|
25
25
|
|
|
26
26
|
if (!tokenToRevoke) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { UserModel } from '@stacksjs/orm'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
|
-
import {
|
|
3
|
+
import { isBillable } from '@stacksjs/orm'
|
|
4
|
+
import { BILLING_NOT_ENABLED, manageCheckout } from '@stacksjs/payments'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
5
6
|
|
|
6
7
|
export default new Action({
|
|
@@ -20,16 +21,12 @@ export default new Action({
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// Checkout needs the billable trait, which the default User model leaves
|
|
23
|
-
// OFF ("not every app bills through the User model")
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
28
|
-
return response.
|
|
29
|
-
{ message: 'Billing is not enabled. Set `traits.billable` on the User model to use checkout.' },
|
|
30
|
-
503,
|
|
31
|
-
)
|
|
32
|
-
}
|
|
24
|
+
// OFF ("not every app bills through the User model"), so say which switch
|
|
25
|
+
// is missing instead of failing deeper in. This used to ask whether
|
|
26
|
+
// `hasStripeId` was a function - a method nothing binds, billable or not -
|
|
27
|
+
// so it answered 503 even in an app that had turned billing on.
|
|
28
|
+
if (!isBillable(user))
|
|
29
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
33
30
|
|
|
34
31
|
const session = await manageCheckout.create(user as unknown as UserModel, params)
|
|
35
32
|
|
|
@@ -18,8 +18,11 @@ export default new Action({
|
|
|
18
18
|
if (!user)
|
|
19
19
|
return response.unauthorized('Authentication required')
|
|
20
20
|
|
|
21
|
+
// `name` is a column of the default User model, not of every User model,
|
|
22
|
+
// so it is read as what it is: possibly absent. An author with no name
|
|
23
|
+
// falls back to the email, which every authenticated user has.
|
|
21
24
|
const author = await authors.findOrCreate({
|
|
22
|
-
name: user.name,
|
|
25
|
+
name: typeof user.name === 'string' && user.name !== '' ? user.name : user.email,
|
|
23
26
|
email: user.email,
|
|
24
27
|
})
|
|
25
28
|
|
|
@@ -73,7 +73,7 @@ export default new Action({
|
|
|
73
73
|
settleBillableRead(user, 'activeSubscription'),
|
|
74
74
|
settleBillableRead(user, 'paymentMethods'),
|
|
75
75
|
settleBillableRead(user, 'defaultPaymentMethod'),
|
|
76
|
-
PaymentTransaction.where('user_id', authenticatedUser.id).orderByDesc('id').get(),
|
|
76
|
+
PaymentTransaction.where('user_id', Number(authenticatedUser.id)).orderByDesc('id').get(),
|
|
77
77
|
])
|
|
78
78
|
|
|
79
79
|
const providerReads: SettledBillingRead[] = [
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable, SubscriptionNotOwnedError } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -6,15 +8,31 @@ export default new Action({
|
|
|
6
8
|
description: 'Cancel Subscription for stripe',
|
|
7
9
|
method: 'POST',
|
|
8
10
|
async handle(request: RequestInstance) {
|
|
9
|
-
const providerId = request.get('providerId')
|
|
11
|
+
const providerId = request.get<unknown>('providerId')
|
|
10
12
|
|
|
11
13
|
const user = await request.user()
|
|
12
14
|
|
|
13
15
|
if (!user)
|
|
14
16
|
return response.unauthorized('Authentication required')
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
if (!isBillable(user))
|
|
19
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
if (typeof providerId !== 'string' || !providerId)
|
|
22
|
+
return response.json({ error: 'providerId is required' }, 422)
|
|
23
|
+
|
|
24
|
+
// Only the caller's own subscription: the trait checks ownership, and one
|
|
25
|
+
// 404 covers both "no such subscription" and "someone else's".
|
|
26
|
+
try {
|
|
27
|
+
// `cancelSubscription` answers `{ subscription }` and nothing else. This
|
|
28
|
+
// returned `.paymentIntent` off it, which is always undefined.
|
|
29
|
+
const { subscription } = await user.cancelSubscription(providerId)
|
|
30
|
+
return response.json(subscription)
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof SubscriptionNotOwnedError)
|
|
34
|
+
return response.json({ error: 'Subscription not found' }, 404)
|
|
35
|
+
throw error
|
|
36
|
+
}
|
|
19
37
|
},
|
|
20
38
|
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,10 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
const checkout = await user.checkout([
|
|
15
20
|
{
|
|
16
21
|
priceId: 'price_1QBEfsBv6MhUdo23avVV0kqx',
|
|
17
22
|
quantity: 1,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,16 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
// There is no `newSubscriptionInvoice` on a billable user, and never was, so
|
|
20
|
+
// this threw "is not a function". An invoiced subscription is an ordinary
|
|
21
|
+
// one that Stripe bills by emailing an invoice instead of charging a card.
|
|
22
|
+
const subscription = await user.newSubscription('pro', 'stacks_pro_monthly', {
|
|
23
|
+
collection_method: 'send_invoice',
|
|
24
|
+
days_until_due: 30,
|
|
25
|
+
})
|
|
15
26
|
|
|
16
27
|
return response.json(subscription)
|
|
17
28
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { HttpError } from '@stacksjs/error-handling'
|
|
3
|
+
import { isBillable } from '@stacksjs/orm'
|
|
4
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
3
5
|
import { response } from '@stacksjs/router'
|
|
4
6
|
|
|
5
7
|
export default new Action({
|
|
@@ -16,14 +18,17 @@ export default new Action({
|
|
|
16
18
|
if (!user)
|
|
17
19
|
return response.unauthorized('Authentication required')
|
|
18
20
|
|
|
21
|
+
if (!isBillable(user))
|
|
22
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
23
|
+
|
|
19
24
|
if (!product) {
|
|
20
25
|
throw new HttpError(422, 'Product not found!')
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// The Product model's column is `price`; `unit_price` has never been one,
|
|
29
|
+
// so that read was undefined and the intent was created for NaN. And the
|
|
30
|
+
// method is `createPayment`: `user.paymentIntent()` never existed.
|
|
31
|
+
const paymentIntent = await user.createPayment(Number(product.get('price')), {
|
|
27
32
|
currency: 'usd',
|
|
28
33
|
payment_method_types: ['card'],
|
|
29
34
|
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,10 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
const setupIntent = await user.createSetupIntent({
|
|
15
20
|
payment_method_types: ['card', 'link', 'us_bank_account'],
|
|
16
21
|
})
|
|
17
22
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -16,8 +18,13 @@ export default new Action({
|
|
|
16
18
|
if (!user)
|
|
17
19
|
return response.unauthorized('Authentication required')
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
if (!isBillable(user))
|
|
22
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
// `plan` is the subscription's name ('pro') and `type` the price lookup key
|
|
25
|
+
// ('stacks_pro_monthly'), which is the order `newSubscription` takes them in.
|
|
26
|
+
const { paymentIntent } = await user.newSubscription(plan, type, { description, metadata: { period } })
|
|
27
|
+
|
|
28
|
+
return response.json(paymentIntent)
|
|
22
29
|
},
|
|
23
30
|
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,8 +13,11 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
14
19
|
const paymentMethod = Number(request.get('paymentMethod'))
|
|
15
20
|
|
|
16
|
-
await user
|
|
21
|
+
await user.deletePaymentMethod(paymentMethod)
|
|
17
22
|
},
|
|
18
23
|
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,10 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
const subscription = await user.activeSubscription()
|
|
15
20
|
|
|
16
21
|
return response.json(subscription)
|
|
17
22
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,10 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
const paymentMethod = await user.defaultPaymentMethod()
|
|
15
20
|
|
|
16
21
|
return response.json(paymentMethod)
|
|
17
22
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,12 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
// `asStripeUser()` does not exist; `retrieveStripeUser()` is the lookup,
|
|
20
|
+
// and answers undefined for a user with no Stripe customer yet.
|
|
21
|
+
const customer = await user.retrieveStripeUser()
|
|
15
22
|
|
|
16
23
|
return response.json(customer)
|
|
17
24
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,7 +13,10 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
const paymentMethods = await user.paymentMethods()
|
|
15
20
|
|
|
16
21
|
return response.json(paymentMethods)
|
|
17
22
|
},
|
|
@@ -11,7 +11,7 @@ export default new Action({
|
|
|
11
11
|
if (!user)
|
|
12
12
|
return response.unauthorized('Authentication required')
|
|
13
13
|
|
|
14
|
-
const transactions = await PaymentTransaction.where('user_id', user
|
|
14
|
+
const transactions = await PaymentTransaction.where('user_id', Number(user.id)).get()
|
|
15
15
|
|
|
16
16
|
return response.json(transactions)
|
|
17
17
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,9 +13,15 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
// The payment store posts `{ paymentId }`; this read only `setupIntent`, so
|
|
20
|
+
// it saw undefined and asked for payment method NaN. `setupIntent` is still
|
|
21
|
+
// read for any client written against the old name.
|
|
22
|
+
const paymentId = Number(request.get('paymentId') ?? request.get('setupIntent'))
|
|
23
|
+
|
|
24
|
+
const paymentMethod = await user.setDefaultPaymentMethod(paymentId)
|
|
17
25
|
|
|
18
26
|
return response.json(paymentMethod)
|
|
19
27
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,9 +13,14 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
14
19
|
const paymentId = String(request.get('setupIntent'))
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
// A string id is Stripe's own `pm_...`, which `setDefaultPaymentMethod`
|
|
22
|
+
// resolves on Stripe's side. `setUserDefaultPaymentMethod()` never existed.
|
|
23
|
+
const paymentMethod = await user.setDefaultPaymentMethod(paymentId)
|
|
17
24
|
|
|
18
25
|
return response.json(paymentMethod)
|
|
19
26
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,9 +13,12 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
14
19
|
const paymentIntent = request.get('setupIntent') as string
|
|
15
20
|
|
|
16
|
-
const paymentMethod = await user
|
|
21
|
+
const paymentMethod = await user.addPaymentMethod(paymentIntent)
|
|
17
22
|
|
|
18
23
|
return response.json(paymentMethod)
|
|
19
24
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -13,7 +15,23 @@ export default new Action({
|
|
|
13
15
|
if (!user)
|
|
14
16
|
return response.unauthorized('Authentication required')
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
if (!isBillable(user))
|
|
19
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
20
|
+
|
|
21
|
+
// A transaction records a payment that already happened, so it needs the
|
|
22
|
+
// Stripe id of that payment and the card brand it was made with. The call
|
|
23
|
+
// used to pass neither, and threw reading `options.description`.
|
|
24
|
+
const providerId = String(request.get('providerId') ?? '')
|
|
25
|
+
const brand = String(request.get('brand') ?? '')
|
|
26
|
+
|
|
27
|
+
if (!providerId || !brand)
|
|
28
|
+
return response.error('A transaction needs the `providerId` of the Stripe payment and the card `brand`.', 422)
|
|
29
|
+
|
|
30
|
+
const transaction = await user.storeTransaction(productId, {
|
|
31
|
+
provider_id: providerId,
|
|
32
|
+
brand,
|
|
33
|
+
description: request.get('description') ? String(request.get('description')) : undefined,
|
|
34
|
+
})
|
|
17
35
|
|
|
18
36
|
return response.json(transaction)
|
|
19
37
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,13 +13,27 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
19
|
+
// The address comes from the request. It was a hard-coded placeholder, so
|
|
20
|
+
// every customer synced through here was moved to the same street in
|
|
21
|
+
// Austin.
|
|
22
|
+
const field = (key: string): string | undefined => {
|
|
23
|
+
const value = request.get(key)
|
|
24
|
+
return typeof value === 'string' && value !== '' ? value : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const customer = await user.syncStripeCustomerDetails({
|
|
28
|
+
address: {
|
|
29
|
+
line1: field('line1'),
|
|
30
|
+
line2: field('line2'),
|
|
31
|
+
city: field('city'),
|
|
32
|
+
state: field('state'),
|
|
33
|
+
postal_code: field('postal_code'),
|
|
34
|
+
country: field('country'),
|
|
35
|
+
},
|
|
36
|
+
})
|
|
21
37
|
|
|
22
38
|
return response.json(customer)
|
|
23
39
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -11,8 +13,11 @@ export default new Action({
|
|
|
11
13
|
if (!user)
|
|
12
14
|
return response.unauthorized('Authentication required')
|
|
13
15
|
|
|
16
|
+
if (!isBillable(user))
|
|
17
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
18
|
+
|
|
14
19
|
const paymentMethod = Number(request.get('paymentMethod'))
|
|
15
20
|
|
|
16
|
-
await user
|
|
21
|
+
await user.setDefaultPaymentMethod(paymentMethod)
|
|
17
22
|
},
|
|
18
23
|
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { isBillable } from '@stacksjs/orm'
|
|
3
|
+
import { BILLING_NOT_ENABLED } from '@stacksjs/payments'
|
|
2
4
|
import { response } from '@stacksjs/router'
|
|
3
5
|
|
|
4
6
|
export default new Action({
|
|
@@ -15,8 +17,11 @@ export default new Action({
|
|
|
15
17
|
if (!user)
|
|
16
18
|
return response.unauthorized('Authentication required')
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
if (!isBillable(user))
|
|
21
|
+
return response.error(BILLING_NOT_ENABLED, 503)
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
const { paymentIntent } = await user.updateSubscription(plan, type, { description })
|
|
24
|
+
|
|
25
|
+
return response.json(paymentIntent)
|
|
21
26
|
},
|
|
22
27
|
})
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.75.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
58
|
-
"@stacksjs/mobile": "^0.
|
|
58
|
+
"@stacksjs/mobile": "^0.75.0",
|
|
59
59
|
"@stacksjs/sanitizer": "^0.2.113",
|
|
60
60
|
"ts-qr-codes": "^0.1.8"
|
|
61
61
|
}
|
|
@@ -81,7 +81,7 @@ function handleSubmit(): void {
|
|
|
81
81
|
label="Email address"
|
|
82
82
|
placeholder="you@example.com"
|
|
83
83
|
v-model:value="email"
|
|
84
|
-
@blur="validateEmail"
|
|
84
|
+
@blur="validateEmail()"
|
|
85
85
|
:error="fieldError('email')"
|
|
86
86
|
required
|
|
87
87
|
:disabled="isLoading()"
|
|
@@ -94,7 +94,7 @@ function handleSubmit(): void {
|
|
|
94
94
|
label="Password"
|
|
95
95
|
placeholder="Enter your password"
|
|
96
96
|
v-model:value="password"
|
|
97
|
-
@blur="validatePassword"
|
|
97
|
+
@blur="validatePassword()"
|
|
98
98
|
:error="fieldError('password')"
|
|
99
99
|
required
|
|
100
100
|
:disabled="isLoading()"
|
|
@@ -144,7 +144,7 @@ function handleSubmit(): void {
|
|
|
144
144
|
label="Email address"
|
|
145
145
|
placeholder="you@example.com"
|
|
146
146
|
v-model:value="email"
|
|
147
|
-
@blur="validateEmail"
|
|
147
|
+
@blur="validateEmail()"
|
|
148
148
|
:error="fieldError('email')"
|
|
149
149
|
required
|
|
150
150
|
:disabled="isLoading()"
|
|
@@ -158,8 +158,8 @@ function handleSubmit(): void {
|
|
|
158
158
|
label="Password"
|
|
159
159
|
placeholder="At least 8 characters"
|
|
160
160
|
v-model:value="password"
|
|
161
|
-
@input="validatePassword"
|
|
162
|
-
@blur="validatePassword"
|
|
161
|
+
@input="validatePassword()"
|
|
162
|
+
@blur="validatePassword()"
|
|
163
163
|
:error="fieldError('password')"
|
|
164
164
|
required
|
|
165
165
|
:disabled="isLoading()"
|
|
@@ -185,7 +185,7 @@ function handleSubmit(): void {
|
|
|
185
185
|
label="Confirm password"
|
|
186
186
|
placeholder="Confirm your password"
|
|
187
187
|
v-model:value="passwordConfirmation"
|
|
188
|
-
@input="validateConfirmation"
|
|
188
|
+
@input="validateConfirmation()"
|
|
189
189
|
:error="fieldError('passwordConfirmation')"
|
|
190
190
|
required
|
|
191
191
|
:disabled="isLoading()"
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
DashboardLogType,
|
|
6
6
|
} from '../../../../app/Actions/Dashboard/Infrastructure/log-dashboard'
|
|
7
7
|
import { dashboardApi } from '../../../../functions/dashboard-api'
|
|
8
|
+
import { debounce } from '@stacksjs/browser/utils/debounce'
|
|
8
9
|
|
|
9
10
|
interface LogPagination {
|
|
10
11
|
page: number
|
|
@@ -449,7 +449,7 @@ onMount(() => {
|
|
|
449
449
|
confirmLabel="Remove member"
|
|
450
450
|
:busy="Boolean(busyMemberId())"
|
|
451
451
|
@close="memberToRemove.set(null)"
|
|
452
|
-
@confirm="removeMember"
|
|
452
|
+
@confirm="removeMember()"
|
|
453
453
|
/>
|
|
454
454
|
|
|
455
455
|
<ConfirmDialog
|
|
@@ -459,5 +459,5 @@ onMount(() => {
|
|
|
459
459
|
confirmLabel="Revoke invitation"
|
|
460
460
|
:busy="Boolean(busyInvitationId())"
|
|
461
461
|
@close="invitationToRevoke.set(null)"
|
|
462
|
-
@confirm="revokeInvitation"
|
|
462
|
+
@confirm="revokeInvitation()"
|
|
463
463
|
/>
|
|
@@ -17,6 +17,7 @@ const sizeClasses = {
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<script client>
|
|
20
|
+
import { useScrollLock } from '@stacksjs/composables'
|
|
20
21
|
const open = useReactiveProp('isOpen', false)
|
|
21
22
|
const liveClosable = useReactiveProp('closable', true)
|
|
22
23
|
const liveTitle = useReactiveProp('title', '')
|
|
@@ -41,6 +41,7 @@ const positionClasses = {
|
|
|
41
41
|
}
|
|
42
42
|
</script>
|
|
43
43
|
<script client>
|
|
44
|
+
import { useScrollLock } from '@stacksjs/composables'
|
|
44
45
|
const open = useReactiveProp('isOpen', false)
|
|
45
46
|
const liveClosable = useReactiveProp('closable', true)
|
|
46
47
|
const liveTitle = useReactiveProp('title', '')
|
|
@@ -278,7 +278,7 @@ export async function loadAutoImports() {
|
|
|
278
278
|
// Skip default exports and protected globals
|
|
279
279
|
if (name === 'default' || protectedGlobals.has(name)) continue
|
|
280
280
|
if (typeof value !== 'undefined') {
|
|
281
|
-
(globalThis
|
|
281
|
+
Reflect.set(globalThis, name, value)
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
} catch {
|
|
@@ -298,7 +298,7 @@ export async function loadAutoImports() {
|
|
|
298
298
|
// Skip default exports and protected globals
|
|
299
299
|
if (name === 'default' || protectedGlobals.has(name)) continue
|
|
300
300
|
if (typeof value !== 'undefined') {
|
|
301
|
-
(globalThis
|
|
301
|
+
Reflect.set(globalThis, name, value)
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
} catch {
|
|
@@ -331,7 +331,7 @@ export async function loadAutoImports() {
|
|
|
331
331
|
try {
|
|
332
332
|
const module = await import(file)
|
|
333
333
|
if (module.default) {
|
|
334
|
-
(globalThis
|
|
334
|
+
Reflect.set(globalThis, modelName, module.default)
|
|
335
335
|
loadedModels.add(modelName)
|
|
336
336
|
}
|
|
337
337
|
} catch {
|
|
@@ -362,7 +362,7 @@ export async function loadAutoImports() {
|
|
|
362
362
|
try {
|
|
363
363
|
const module = await import(file)
|
|
364
364
|
if (module.default) {
|
|
365
|
-
(globalThis
|
|
365
|
+
Reflect.set(globalThis, jobName, module.default)
|
|
366
366
|
loadedJobs.add(jobName)
|
|
367
367
|
}
|
|
368
368
|
} catch {
|
|
@@ -397,7 +397,7 @@ export async function loadAutoImports() {
|
|
|
397
397
|
try {
|
|
398
398
|
const module = await import(file)
|
|
399
399
|
if (module.default) {
|
|
400
|
-
(globalThis
|
|
400
|
+
Reflect.set(globalThis, controllerName, module.default)
|
|
401
401
|
loadedControllers.add(controllerName)
|
|
402
402
|
}
|
|
403
403
|
} catch {
|