@owlmeans/payment 0.1.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/LICENSE +21 -0
- package/README.md +524 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +62 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +87 -0
- package/build/consts.js.map +1 -0
- package/build/errors.d.ts +42 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +81 -0
- package/build/errors.js.map +1 -0
- package/build/helper.d.ts +3 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +3 -0
- package/build/helper.js.map +1 -0
- package/build/i18n/en.json +39 -0
- package/build/i18n.d.ts +2 -0
- package/build/i18n.d.ts.map +1 -0
- package/build/i18n.js +4 -0
- package/build/i18n.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +8 -0
- package/build/index.js.map +1 -0
- package/build/model/index.d.ts +6 -0
- package/build/model/index.d.ts.map +1 -0
- package/build/model/index.js +6 -0
- package/build/model/index.js.map +1 -0
- package/build/model/localization.d.ts +4 -0
- package/build/model/localization.d.ts.map +1 -0
- package/build/model/localization.js +19 -0
- package/build/model/localization.js.map +1 -0
- package/build/model/plan.d.ts +4 -0
- package/build/model/plan.d.ts.map +1 -0
- package/build/model/plan.js +43 -0
- package/build/model/plan.js.map +1 -0
- package/build/model/product.d.ts +4 -0
- package/build/model/product.d.ts.map +1 -0
- package/build/model/product.js +17 -0
- package/build/model/product.js.map +1 -0
- package/build/model/subscription.d.ts +5 -0
- package/build/model/subscription.d.ts.map +1 -0
- package/build/model/subscription.js +46 -0
- package/build/model/subscription.js.map +1 -0
- package/build/model/utils.d.ts +5 -0
- package/build/model/utils.d.ts.map +1 -0
- package/build/model/utils.js +28 -0
- package/build/model/utils.js.map +1 -0
- package/build/modules.d.ts +2 -0
- package/build/modules.d.ts.map +1 -0
- package/build/modules.js +9 -0
- package/build/modules.js.map +1 -0
- package/build/service.d.ts +5 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +71 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +116 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +2 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/types.d.ts +7 -0
- package/build/utils/types.d.ts.map +1 -0
- package/build/utils/types.js +2 -0
- package/build/utils/types.js.map +1 -0
- package/package.json +52 -0
- package/src/consts.ts +100 -0
- package/src/errors.ts +102 -0
- package/src/helper.ts +5 -0
- package/src/i18n/en.json +39 -0
- package/src/i18n.ts +6 -0
- package/src/index.ts +9 -0
- package/src/model/index.ts +6 -0
- package/src/model/localization.ts +24 -0
- package/src/model/plan.ts +51 -0
- package/src/model/product.ts +21 -0
- package/src/model/subscription.ts +49 -0
- package/src/model/utils.ts +32 -0
- package/src/modules.ts +15 -0
- package/src/service.ts +95 -0
- package/src/types.ts +119 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/types.ts +9 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
import type { JSONSchemaType } from 'ajv'
|
|
3
|
+
import type { LimitConfig, CapabilityUsage } from '../types.js'
|
|
4
|
+
import { PlanDurationSchema } from '../consts.js'
|
|
5
|
+
import { DateSchema } from '@owlmeans/auth'
|
|
6
|
+
|
|
7
|
+
export const LimitConfigSchema: JSONSchemaType<LimitConfig> = {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
interval: PlanDurationSchema,
|
|
11
|
+
measurment: { type: 'string', nullable: true, maxLength: 16 },
|
|
12
|
+
limit: { type: 'number' }
|
|
13
|
+
},
|
|
14
|
+
required: ['interval', 'limit'],
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const CapabilityUsageSchema: JSONSchemaType<CapabilityUsage> = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
interval: PlanDurationSchema,
|
|
22
|
+
measurment: { type: 'string', nullable: true, maxLength: 16 },
|
|
23
|
+
startedAt: DateSchema,
|
|
24
|
+
limit: { type: 'number' },
|
|
25
|
+
refreshedAt: { ...DateSchema, nullable: true },
|
|
26
|
+
refreshAt: { ...DateSchema, nullable: true },
|
|
27
|
+
lastConsumedAt: { ...DateSchema, nullable: true },
|
|
28
|
+
consumption: { type: 'number' }
|
|
29
|
+
},
|
|
30
|
+
required: ['interval', 'limit', 'startedAt', 'consumption'],
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
}
|
package/src/modules.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { body, filter, module } from '@owlmeans/module'
|
|
2
|
+
import { route, backend, RouteMethod } from '@owlmeans/route'
|
|
3
|
+
import { paymentApi } from './consts.js'
|
|
4
|
+
import { SubscriptionPropogateBodySchema } from './model/subscription.js'
|
|
5
|
+
|
|
6
|
+
export const modules = [
|
|
7
|
+
module(route(paymentApi.subscription.base, '/subscription', backend())),
|
|
8
|
+
module(
|
|
9
|
+
route(paymentApi.subscription.propogate, '/propogate', backend(
|
|
10
|
+
paymentApi.subscription.base,
|
|
11
|
+
RouteMethod.POST
|
|
12
|
+
)),
|
|
13
|
+
filter(body(SubscriptionPropogateBodySchema))
|
|
14
|
+
)
|
|
15
|
+
]
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createService, assertContext } from '@owlmeans/context'
|
|
2
|
+
import type { ConfigRecord } from '@owlmeans/context'
|
|
3
|
+
import { DEFAULT_ALIAS, PaymentEntityType } from './consts.js'
|
|
4
|
+
import type { Localization, PaymentService } from './types.js'
|
|
5
|
+
import type { Config, Context } from './utils/types.js'
|
|
6
|
+
import { PLAN_RECORD_TYPE, PRODUCT_RECORD_PREFIX } from './consts.js'
|
|
7
|
+
import { PaymentIdentificationError, UnknownProduct } from './errors.js'
|
|
8
|
+
import type { Product, ProductPlan } from './types.js'
|
|
9
|
+
import { fromConfigRecord } from '@owlmeans/config'
|
|
10
|
+
import type { ResourceRecord } from '@owlmeans/resource'
|
|
11
|
+
import { l10nToId } from './helper.js'
|
|
12
|
+
import { DEFAULT_LNG } from '@owlmeans/i18n'
|
|
13
|
+
import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
|
|
14
|
+
import type { AuthCredentials } from '@owlmeans/auth'
|
|
15
|
+
|
|
16
|
+
export const makePaymentService = (alias: string = DEFAULT_ALIAS): PaymentService => {
|
|
17
|
+
const service: PaymentService = createService<PaymentService>(alias, {
|
|
18
|
+
product: async sku => {
|
|
19
|
+
const context = assertContext(service.ctx) as Context
|
|
20
|
+
|
|
21
|
+
const configRes = context.getConfigResource()
|
|
22
|
+
|
|
23
|
+
const result = fromConfigRecord<ConfigRecord, Product & ResourceRecord>(await configRes.get(`${PRODUCT_RECORD_PREFIX}:${sku}`))
|
|
24
|
+
|
|
25
|
+
if (result == null) {
|
|
26
|
+
throw new UnknownProduct(sku)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result as Product
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
plans: async (productSku, duration) => {
|
|
33
|
+
const context = assertContext(service.ctx) as Context
|
|
34
|
+
const configRes = context.getConfigResource()
|
|
35
|
+
|
|
36
|
+
const result = await configRes.list({
|
|
37
|
+
productSku, duration, recordType: PLAN_RECORD_TYPE
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return result.items.map(
|
|
41
|
+
item => fromConfigRecord<ConfigRecord, ProductPlan & ResourceRecord>(item)
|
|
42
|
+
) as ProductPlan[]
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
localize: async (lng, entity) => {
|
|
46
|
+
const context = assertContext(service.ctx) as Context
|
|
47
|
+
const configRes = context.getConfigResource()
|
|
48
|
+
|
|
49
|
+
const idsToCheck: string[] = []
|
|
50
|
+
if ("productSku" in entity) {
|
|
51
|
+
idsToCheck.push(l10nToId({ lng, type: PaymentEntityType.Plan, sku: entity.sku }, true))
|
|
52
|
+
idsToCheck.push(l10nToId({ lng: DEFAULT_LNG, type: PaymentEntityType.Plan, sku: entity.sku }, true))
|
|
53
|
+
|
|
54
|
+
idsToCheck.push(l10nToId({ lng, type: PaymentEntityType.Product, sku: entity.productSku }, true))
|
|
55
|
+
idsToCheck.push(l10nToId({ lng: DEFAULT_LNG, type: PaymentEntityType.Product, sku: entity.productSku }, true))
|
|
56
|
+
} else if ("sku" in entity) {
|
|
57
|
+
idsToCheck.push(l10nToId({ lng, type: PaymentEntityType.Product, sku: entity.sku }, true))
|
|
58
|
+
idsToCheck.push(l10nToId({ lng: DEFAULT_LNG, type: PaymentEntityType.Product, sku: entity.sku }, true))
|
|
59
|
+
} else {
|
|
60
|
+
idsToCheck.push(l10nToId({ lng, type: PaymentEntityType.CapabilitySet, sku: entity.scope }, true))
|
|
61
|
+
idsToCheck.push(l10nToId({ lng: DEFAULT_LNG, type: PaymentEntityType.CapabilitySet, sku: entity.scope }, true))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (let id of idsToCheck) {
|
|
65
|
+
const l10n = await configRes.load(id)
|
|
66
|
+
if (l10n != null) {
|
|
67
|
+
return fromConfigRecord<ConfigRecord, Localization & ResourceRecord>(l10n)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
shallowAuthentication: async token => {
|
|
75
|
+
if (token == null) {
|
|
76
|
+
throw new PaymentIdentificationError('token')
|
|
77
|
+
}
|
|
78
|
+
const envelope = makeEnvelopeModel<AuthCredentials>(token, EnvelopeKind.Token)
|
|
79
|
+
const auth = envelope.message()
|
|
80
|
+
if (auth.profileId == null) {
|
|
81
|
+
throw new PaymentIdentificationError('profileId')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return auth.profileId
|
|
85
|
+
}
|
|
86
|
+
}, service => async () => {
|
|
87
|
+
service.initialized = true
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return service
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const appendPaymentService = <C extends Config, T extends Context<C>>(
|
|
94
|
+
context: T, alias: string = DEFAULT_ALIAS
|
|
95
|
+
): T => context.registerService(makePaymentService(alias))
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { InitializedService } from '@owlmeans/context'
|
|
2
|
+
|
|
3
|
+
import type { PaymentEntityType, PlanDuration, PlanStatus, ProductType, SubscriptionStatus } from './consts.js'
|
|
4
|
+
import type { PermissionSet } from '@owlmeans/auth'
|
|
5
|
+
|
|
6
|
+
export interface Product {
|
|
7
|
+
type: ProductType
|
|
8
|
+
sku: string
|
|
9
|
+
title: string
|
|
10
|
+
description?: string
|
|
11
|
+
defaultLng?: string
|
|
12
|
+
/**
|
|
13
|
+
* Related software services.
|
|
14
|
+
* Actually even if it's a physical product, it can have related
|
|
15
|
+
* software services.
|
|
16
|
+
*/
|
|
17
|
+
services?: string[]
|
|
18
|
+
capabilities?: PermissionSet[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Localization {
|
|
22
|
+
sku: string
|
|
23
|
+
type: PaymentEntityType
|
|
24
|
+
lng: string
|
|
25
|
+
title?: string
|
|
26
|
+
description?: string
|
|
27
|
+
keywords?: { [key: string]: string }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ProductPlan {
|
|
31
|
+
productSku: string
|
|
32
|
+
sku: string
|
|
33
|
+
status: PlanStatus
|
|
34
|
+
payagateAliases?: {
|
|
35
|
+
[paygate: string]: string
|
|
36
|
+
}
|
|
37
|
+
duration: PlanDuration
|
|
38
|
+
trial?: number
|
|
39
|
+
gatedTrial?: boolean
|
|
40
|
+
price: number
|
|
41
|
+
order?: number
|
|
42
|
+
highlight?: string
|
|
43
|
+
currency?: string
|
|
44
|
+
originalPrice?: number
|
|
45
|
+
discount?: number
|
|
46
|
+
awaitingInterval?: number
|
|
47
|
+
title: string
|
|
48
|
+
description?: string
|
|
49
|
+
customUrl?: string
|
|
50
|
+
createdAt?: Date
|
|
51
|
+
archivedAt?: Date
|
|
52
|
+
/**
|
|
53
|
+
* Deprecation date should be set alongside suspnsion date (that should be set to the future).
|
|
54
|
+
* This way we can plan event when users loose access in case they don't upgrade.
|
|
55
|
+
*/
|
|
56
|
+
deprecatedAt?: Date
|
|
57
|
+
supsendedAt?: Date
|
|
58
|
+
capabilities?: PermissionSet[]
|
|
59
|
+
limits?: { [key: string]: LimitConfig }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PlanSubscription {
|
|
63
|
+
sku: string
|
|
64
|
+
entityId: string
|
|
65
|
+
paymentMethod?: string
|
|
66
|
+
externalId?: string
|
|
67
|
+
createdAt: Date
|
|
68
|
+
lastPaymentAt?: Date
|
|
69
|
+
trialUntil?: Date
|
|
70
|
+
startsdAt: Date
|
|
71
|
+
expirationAt?: Date
|
|
72
|
+
endsAt?: Date
|
|
73
|
+
archiveAt?: Date
|
|
74
|
+
suspendedAt?: Date
|
|
75
|
+
canceledAt?: Date
|
|
76
|
+
blockedAt?: Date
|
|
77
|
+
suspendedUntil?: Date
|
|
78
|
+
status: SubscriptionStatus
|
|
79
|
+
/**
|
|
80
|
+
* Custom capabilities for the particular subscription.
|
|
81
|
+
*/
|
|
82
|
+
capabilities?: PermissionSet[]
|
|
83
|
+
/**
|
|
84
|
+
* Custom limits for the particular subscription.
|
|
85
|
+
*/
|
|
86
|
+
limits?: { [key: string]: LimitConfig }
|
|
87
|
+
consumptions?: { [key: string]: CapabilityUsage }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SubscriptionPropogateBody extends PlanSubscription {
|
|
91
|
+
service: string
|
|
92
|
+
externalId: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface LimitConfig {
|
|
96
|
+
interval: PlanDuration
|
|
97
|
+
limit: number
|
|
98
|
+
measurment?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CapabilityUsage extends LimitConfig {
|
|
102
|
+
startedAt: Date
|
|
103
|
+
refreshedAt?: Date
|
|
104
|
+
refreshAt?: Date
|
|
105
|
+
lastConsumedAt?: Date
|
|
106
|
+
consumption: number
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type PaymentEntity = Product | ProductPlan | PermissionSet
|
|
110
|
+
|
|
111
|
+
export interface PaymentService extends InitializedService {
|
|
112
|
+
product: (sku: string) => Promise<Product>
|
|
113
|
+
|
|
114
|
+
plans: (productSku: string, duration: PlanDuration) => Promise<ProductPlan[]>
|
|
115
|
+
|
|
116
|
+
localize: (lng: string, entity: PaymentEntity) => Promise<Localization | null>
|
|
117
|
+
|
|
118
|
+
shallowAuthentication: (token: string | null) => Promise<string>
|
|
119
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
import type { CommonConfig, ConfigResourceAppend } from '@owlmeans/config'
|
|
3
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
4
|
+
|
|
5
|
+
export interface Config extends CommonConfig { }
|
|
6
|
+
|
|
7
|
+
export interface Context<C extends Config = Config> extends BasicContext<C>
|
|
8
|
+
, ConfigResourceAppend {
|
|
9
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
7
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
8
|
+
"moduleResolution": "Bundler"
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"./dist/**/*",
|
|
12
|
+
"./build/**/*",
|
|
13
|
+
"./*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/errors.ts","./src/helper.ts","./src/i18n.ts","./src/index.ts","./src/modules.ts","./src/service.ts","./src/types.ts","./src/model/index.ts","./src/model/localization.ts","./src/model/plan.ts","./src/model/product.ts","./src/model/subscription.ts","./src/model/utils.ts","./src/utils/index.ts","./src/utils/types.ts"],"version":"5.6.3"}
|