@shaxpir/duiduidui-models 1.0.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 +1 -0
- package/decs.d.ts +87 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -0
- package/dist/models/OutboundMessage.d.ts +18 -0
- package/dist/models/OutboundMessage.js +25 -0
- package/dist/models/content/Activity.d.ts +10 -0
- package/dist/models/content/Activity.js +2 -0
- package/dist/models/content/ArrayView.d.ts +26 -0
- package/dist/models/content/ArrayView.js +174 -0
- package/dist/models/content/Billing.d.ts +144 -0
- package/dist/models/content/Billing.js +418 -0
- package/dist/models/content/Book.d.ts +77 -0
- package/dist/models/content/Book.js +407 -0
- package/dist/models/content/Category.d.ts +16 -0
- package/dist/models/content/Category.js +20 -0
- package/dist/models/content/Checkpointable.d.ts +21 -0
- package/dist/models/content/Checkpointable.js +156 -0
- package/dist/models/content/Comment.d.ts +19 -0
- package/dist/models/content/Comment.js +53 -0
- package/dist/models/content/ConceptArt.d.ts +31 -0
- package/dist/models/content/ConceptArt.js +84 -0
- package/dist/models/content/Content.d.ts +52 -0
- package/dist/models/content/Content.js +61 -0
- package/dist/models/content/ContentKind.d.ts +10 -0
- package/dist/models/content/ContentKind.js +16 -0
- package/dist/models/content/Context.d.ts +28 -0
- package/dist/models/content/Context.js +162 -0
- package/dist/models/content/DevEnv.d.ts +5 -0
- package/dist/models/content/DevEnv.js +9 -0
- package/dist/models/content/Device.d.ts +24 -0
- package/dist/models/content/Device.js +62 -0
- package/dist/models/content/Dictionary.d.ts +31 -0
- package/dist/models/content/Dictionary.js +5 -0
- package/dist/models/content/DictionaryEntry.d.ts +20 -0
- package/dist/models/content/DictionaryEntry.js +2 -0
- package/dist/models/content/ElasticModel.d.ts +149 -0
- package/dist/models/content/ElasticModel.js +179 -0
- package/dist/models/content/Environment.d.ts +61 -0
- package/dist/models/content/Environment.js +124 -0
- package/dist/models/content/ExportOptions.d.ts +64 -0
- package/dist/models/content/ExportOptions.js +213 -0
- package/dist/models/content/Folder.d.ts +16 -0
- package/dist/models/content/Folder.js +33 -0
- package/dist/models/content/Fragment.d.ts +54 -0
- package/dist/models/content/Fragment.js +181 -0
- package/dist/models/content/GeoLocation.d.ts +4 -0
- package/dist/models/content/GeoLocation.js +2 -0
- package/dist/models/content/Hanzi.d.ts +21 -0
- package/dist/models/content/Hanzi.js +2 -0
- package/dist/models/content/HighlightRule.d.ts +9 -0
- package/dist/models/content/HighlightRule.js +2 -0
- package/dist/models/content/Manifest.d.ts +42 -0
- package/dist/models/content/Manifest.js +114 -0
- package/dist/models/content/Media.d.ts +32 -0
- package/dist/models/content/Media.js +98 -0
- package/dist/models/content/Metric.d.ts +46 -0
- package/dist/models/content/Metric.js +183 -0
- package/dist/models/content/Migration.d.ts +68 -0
- package/dist/models/content/Migration.js +155 -0
- package/dist/models/content/Model.d.ts +45 -0
- package/dist/models/content/Model.js +280 -0
- package/dist/models/content/Permissions.d.ts +7 -0
- package/dist/models/content/Permissions.js +20 -0
- package/dist/models/content/Phrase.d.ts +8 -0
- package/dist/models/content/Phrase.js +2 -0
- package/dist/models/content/Placeholder.d.ts +8 -0
- package/dist/models/content/Placeholder.js +36 -0
- package/dist/models/content/Profile.d.ts +30 -0
- package/dist/models/content/Profile.js +95 -0
- package/dist/models/content/RichText.d.ts +58 -0
- package/dist/models/content/RichText.js +79 -0
- package/dist/models/content/Session.d.ts +39 -0
- package/dist/models/content/Session.js +173 -0
- package/dist/models/content/Speech.d.ts +67 -0
- package/dist/models/content/Speech.js +97 -0
- package/dist/models/content/Stub.d.ts +24 -0
- package/dist/models/content/Stub.js +179 -0
- package/dist/models/content/Time.d.ts +56 -0
- package/dist/models/content/Time.js +295 -0
- package/dist/models/content/User.d.ts +36 -0
- package/dist/models/content/User.js +95 -0
- package/dist/models/content/Workspace.d.ts +71 -0
- package/dist/models/content/Workspace.js +237 -0
- package/dist/models/content/index.d.ts +36 -0
- package/dist/models/content/index.js +53 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.js +21 -0
- package/dist/models/legacy/LegacyBodyFormat.d.ts +9 -0
- package/dist/models/legacy/LegacyBodyFormat.js +2 -0
- package/dist/models/legacy/LegacyComment.d.ts +12 -0
- package/dist/models/legacy/LegacyComment.js +2 -0
- package/dist/models/legacy/LegacyContent.d.ts +53 -0
- package/dist/models/legacy/LegacyContent.js +55 -0
- package/dist/models/legacy/LegacyConversion.d.ts +55 -0
- package/dist/models/legacy/LegacyConversion.js +401 -0
- package/dist/models/legacy/LegacyFragment.d.ts +21 -0
- package/dist/models/legacy/LegacyFragment.js +2 -0
- package/dist/models/legacy/LegacyLocator.d.ts +8 -0
- package/dist/models/legacy/LegacyLocator.js +31 -0
- package/dist/models/legacy/LegacyOutboundMessage.d.ts +16 -0
- package/dist/models/legacy/LegacyOutboundMessage.js +13 -0
- package/dist/models/legacy/LegacyPicture.d.ts +14 -0
- package/dist/models/legacy/LegacyPicture.js +2 -0
- package/dist/models/legacy/LegacyProfile.d.ts +9 -0
- package/dist/models/legacy/LegacyProfile.js +2 -0
- package/dist/models/legacy/LegacySession.d.ts +41 -0
- package/dist/models/legacy/LegacySession.js +35 -0
- package/dist/models/legacy/LegacyStory.d.ts +23 -0
- package/dist/models/legacy/LegacyStory.js +2 -0
- package/dist/models/legacy/LegacyStub.d.ts +15 -0
- package/dist/models/legacy/LegacyStub.js +2 -0
- package/dist/models/legacy/LegacyTransaction.d.ts +14 -0
- package/dist/models/legacy/LegacyTransaction.js +49 -0
- package/dist/models/legacy/LegacyUser.d.ts +28 -0
- package/dist/models/legacy/LegacyUser.js +32 -0
- package/dist/models/legacy/LegacyWorkspace.d.ts +23 -0
- package/dist/models/legacy/LegacyWorkspace.js +6 -0
- package/dist/models/legacy/index.d.ts +15 -0
- package/dist/models/legacy/index.js +32 -0
- package/dist/models/markup/BodyFormat.d.ts +14 -0
- package/dist/models/markup/BodyFormat.js +190 -0
- package/dist/models/markup/ChangeModel.d.ts +22 -0
- package/dist/models/markup/ChangeModel.js +107 -0
- package/dist/models/markup/DeltaOps.d.ts +5 -0
- package/dist/models/markup/DeltaOps.js +74 -0
- package/dist/models/markup/HtmlMarkup.d.ts +4 -0
- package/dist/models/markup/HtmlMarkup.js +21 -0
- package/dist/models/markup/Operation.d.ts +32 -0
- package/dist/models/markup/Operation.js +194 -0
- package/dist/models/markup/TextEditOps.d.ts +9 -0
- package/dist/models/markup/TextEditOps.js +50 -0
- package/dist/models/markup/index.d.ts +6 -0
- package/dist/models/markup/index.js +23 -0
- package/dist/repo/ConnectionListener.d.ts +9 -0
- package/dist/repo/ConnectionListener.js +21 -0
- package/dist/repo/PermissiveJson1.d.ts +58 -0
- package/dist/repo/PermissiveJson1.js +39 -0
- package/dist/repo/ShareSync.d.ts +60 -0
- package/dist/repo/ShareSync.js +348 -0
- package/dist/repo/index.d.ts +3 -0
- package/dist/repo/index.js +20 -0
- package/dist/util/Async.d.ts +8 -0
- package/dist/util/Async.js +18 -0
- package/dist/util/Base62.d.ts +6 -0
- package/dist/util/Base62.js +47 -0
- package/dist/util/BinarySearch.d.ts +7 -0
- package/dist/util/BinarySearch.js +46 -0
- package/dist/util/CachingHasher.d.ts +8 -0
- package/dist/util/CachingHasher.js +41 -0
- package/dist/util/Color.d.ts +32 -0
- package/dist/util/Color.js +204 -0
- package/dist/util/Dispatch.d.ts +15 -0
- package/dist/util/Dispatch.js +79 -0
- package/dist/util/EditDistance.d.ts +13 -0
- package/dist/util/EditDistance.js +184 -0
- package/dist/util/Encryption.d.ts +5 -0
- package/dist/util/Encryption.js +2 -0
- package/dist/util/Logging.d.ts +108 -0
- package/dist/util/Logging.js +412 -0
- package/dist/util/NumberFormat.d.ts +14 -0
- package/dist/util/NumberFormat.js +224 -0
- package/dist/util/Struct.d.ts +4 -0
- package/dist/util/Struct.js +15 -0
- package/dist/util/Template.d.ts +16 -0
- package/dist/util/Template.js +128 -0
- package/dist/util/Text.d.ts +45 -0
- package/dist/util/Text.js +243 -0
- package/dist/util/Tuples.d.ts +9 -0
- package/dist/util/Tuples.js +135 -0
- package/dist/util/Validate.d.ts +4 -0
- package/dist/util/Validate.js +11 -0
- package/dist/util/Vocabulary.d.ts +3 -0
- package/dist/util/Vocabulary.js +35 -0
- package/dist/util/index.d.ts +16 -0
- package/dist/util/index.js +33 -0
- package/lib/models/content/ArrayView.ts +203 -0
- package/lib/models/content/Billing.ts +558 -0
- package/lib/models/content/Content.ts +110 -0
- package/lib/models/content/ContentKind.ts +14 -0
- package/lib/models/content/DevEnv.ts +5 -0
- package/lib/models/content/Device.ts +86 -0
- package/lib/models/content/DictionaryEntry.ts +22 -0
- package/lib/models/content/GeoLocation.ts +4 -0
- package/lib/models/content/Hanzi.ts +25 -0
- package/lib/models/content/Manifest.ts +162 -0
- package/lib/models/content/Media.ts +126 -0
- package/lib/models/content/Model.ts +327 -0
- package/lib/models/content/Permissions.ts +21 -0
- package/lib/models/content/Phrase.ts +10 -0
- package/lib/models/content/Profile.ts +119 -0
- package/lib/models/content/Time.ts +328 -0
- package/lib/models/content/User.ts +130 -0
- package/lib/models/markup/ChangeModel.ts +95 -0
- package/lib/models/markup/DeltaOps.ts +71 -0
- package/lib/models/markup/Operation.ts +215 -0
- package/lib/models/markup/TextEditOps.ts +50 -0
- package/lib/repo/ConnectionListener.ts +25 -0
- package/lib/repo/PermissiveJson1.ts +14 -0
- package/lib/repo/ShareSync.ts +390 -0
- package/lib/util/Base62.ts +47 -0
- package/lib/util/CachingHasher.ts +38 -0
- package/lib/util/Dispatch.ts +92 -0
- package/lib/util/Encryption.ts +5 -0
- package/lib/util/Logging.ts +568 -0
- package/lib/util/NumberFormat.ts +194 -0
- package/lib/util/Struct.ts +14 -0
- package/lib/util/Tuples.ts +131 -0
- package/package.json +41 -0
- package/tsconfig.json +25 -0
- package/tslint.json +46 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
+
import { ShareSync, ShareSyncFactory } from '../../repo/ShareSync';
|
|
3
|
+
import { Struct } from '../../util/Struct';
|
|
4
|
+
import { CachingHasher } from '../../util/CachingHasher';
|
|
5
|
+
import { NumberFormat } from "../../util/NumberFormat";
|
|
6
|
+
import { BatchOperation } from '../markup/Operation';
|
|
7
|
+
import { ArrayView } from './ArrayView';
|
|
8
|
+
import { Content, ContentBody, ContentId, ContentMeta } from "./Content";
|
|
9
|
+
import { ContentKind } from './ContentKind';
|
|
10
|
+
import { CompactDateTime, MultiClock, MultiTime, Time } from './Time';
|
|
11
|
+
|
|
12
|
+
export interface CreditCardHarvest {
|
|
13
|
+
name:string;
|
|
14
|
+
number:string;
|
|
15
|
+
cvc:string;
|
|
16
|
+
exp_month:string;
|
|
17
|
+
exp_year:string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CreditCard {
|
|
21
|
+
name:string;
|
|
22
|
+
brand:string;
|
|
23
|
+
last4:string;
|
|
24
|
+
exp_month:string;
|
|
25
|
+
exp_year:string;
|
|
26
|
+
country:string;
|
|
27
|
+
funding:string;
|
|
28
|
+
wallet?:string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TransactionPayload {
|
|
32
|
+
at_utc_time?:CompactDateTime;
|
|
33
|
+
topic:TransactionTopic;
|
|
34
|
+
details?:any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export enum TransactionTopic {
|
|
38
|
+
|
|
39
|
+
START_7_DAY_TRIAL = "START_7_DAY_TRIAL",
|
|
40
|
+
|
|
41
|
+
APPLY_COUPON = "APPLY_COUPON",
|
|
42
|
+
|
|
43
|
+
PAYMENT_SUCCESS = "PAYMENT_SUCCESS",
|
|
44
|
+
PAYMENT_FAIL = "PAYMENT_FAIL",
|
|
45
|
+
|
|
46
|
+
CANCEL_SUBSCRIPTION = "CANCEL_SUBSCRIPTION",
|
|
47
|
+
RESTART_SUBSCRIPTION = "RESTART_SUBSCRIPTION"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export enum Interval {
|
|
51
|
+
MONTHLY = "MONTHLY",
|
|
52
|
+
ANNUAL = "ANNUAL",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export enum ProductLevel {
|
|
56
|
+
FREE = "FREE",
|
|
57
|
+
BASIC = "BASIC",
|
|
58
|
+
PRO = "PRO",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface BillingProduct {
|
|
62
|
+
id:string;
|
|
63
|
+
name:string;
|
|
64
|
+
level:ProductLevel;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export interface BillingPrice {
|
|
68
|
+
id:string;
|
|
69
|
+
name:string;
|
|
70
|
+
amount:number;
|
|
71
|
+
interval:Interval;
|
|
72
|
+
product_id:string;
|
|
73
|
+
trial_days:number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface BillingPlan {
|
|
77
|
+
id:string;
|
|
78
|
+
name:string;
|
|
79
|
+
amount:number;
|
|
80
|
+
level:ProductLevel;
|
|
81
|
+
interval:Interval;
|
|
82
|
+
trial_days:number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ProductCatalogConfig {
|
|
86
|
+
products:BillingProduct[];
|
|
87
|
+
prices:BillingPrice[];
|
|
88
|
+
plans:BillingPlan[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class ProductCatalog {
|
|
92
|
+
|
|
93
|
+
private productsById:Map<string,BillingProduct>;
|
|
94
|
+
private pricesById:Map<string,BillingPrice>;
|
|
95
|
+
private plansById:Map<string,BillingPlan>;
|
|
96
|
+
|
|
97
|
+
constructor(config:ProductCatalogConfig) {
|
|
98
|
+
this.productsById = new Map<string,BillingProduct>();
|
|
99
|
+
this.pricesById = new Map<string,BillingPrice>();
|
|
100
|
+
this.plansById = new Map<string,BillingPlan>();
|
|
101
|
+
for (const product of config.products) {
|
|
102
|
+
this.productsById.set(product.id, product);
|
|
103
|
+
}
|
|
104
|
+
for (const price of config.prices) {
|
|
105
|
+
this.pricesById.set(price.id, price);
|
|
106
|
+
}
|
|
107
|
+
for (const plan of config.plans) {
|
|
108
|
+
this.plansById.set(plan.id, plan);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public getProductById(id:string):BillingProduct {
|
|
113
|
+
return this.productsById.get(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public getPriceById(id:string):BillingPrice {
|
|
117
|
+
return this.pricesById.get(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public getPlanById(id:string):BillingPlan {
|
|
121
|
+
return this.plansById.get(id);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public getProductsByPredicate(
|
|
125
|
+
predicate:(product:BillingProduct) => boolean
|
|
126
|
+
):BillingProduct[] {
|
|
127
|
+
const products:BillingProduct[] = [];
|
|
128
|
+
for (const product of this.productsById.values()) {
|
|
129
|
+
if (predicate(product)) {
|
|
130
|
+
products.push(product);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return products;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public getPricesByPredicate(
|
|
137
|
+
predicate:(price:BillingPrice) => boolean
|
|
138
|
+
):BillingPrice[] {
|
|
139
|
+
const prices:BillingPrice[] = [];
|
|
140
|
+
for (const price of this.pricesById.values()) {
|
|
141
|
+
if (predicate(price)) {
|
|
142
|
+
prices.push(price);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return prices;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public getPlansByPredicate(
|
|
149
|
+
predicate:(plan:BillingPlan) => boolean
|
|
150
|
+
):BillingPlan[] {
|
|
151
|
+
const plans:BillingPlan[] = [];
|
|
152
|
+
for (const plan of this.plansById.values()) {
|
|
153
|
+
if (predicate(plan)) {
|
|
154
|
+
plans.push(plan);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return plans;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// TODO: cleanup "PRE_TRIAL" status, and update all users who have this status
|
|
162
|
+
export enum PaymentStatus {
|
|
163
|
+
PRE_TRIAL = "PRE_TRIAL",
|
|
164
|
+
IN_TRIAL = "IN_TRIAL",
|
|
165
|
+
PAID = "PAID",
|
|
166
|
+
UNPAID = "UNPAID",
|
|
167
|
+
CANCELLED = "CANCELLED",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface BillingBody extends ContentBody {
|
|
171
|
+
meta:ContentMeta;
|
|
172
|
+
payload:BillingPayload;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface BillingPayload {
|
|
176
|
+
payment_status:PaymentStatus;
|
|
177
|
+
paid_until:CompactDateTime;
|
|
178
|
+
stripe_customer_id:string;
|
|
179
|
+
stripe_payment_method_id?:string;
|
|
180
|
+
stripe_payment_method_type?:string;
|
|
181
|
+
transactions:TransactionPayload[];
|
|
182
|
+
credit_card?:CreditCard;
|
|
183
|
+
product_id?:string;
|
|
184
|
+
price_id?:string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export class Billing extends Content {
|
|
188
|
+
|
|
189
|
+
private _transactionsView:ArrayView<TransactionPayload>;
|
|
190
|
+
|
|
191
|
+
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
192
|
+
super(doc, shouldAcquire, shareSync);
|
|
193
|
+
this._transactionsView = new ArrayView<TransactionPayload>(this, [ 'payload', 'transactions' ]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public get payload():BillingPayload {
|
|
197
|
+
this.checkDisposed("Billing.payload");
|
|
198
|
+
return this.doc.data.payload as BillingPayload;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public static makeBillingId(userId:ContentId):ContentId {
|
|
202
|
+
return CachingHasher.makeMd5ContentId(userId + "-" + ContentKind.BILLING);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public static create(
|
|
206
|
+
userId:ContentId,
|
|
207
|
+
payload:BillingPayload,
|
|
208
|
+
createdAt?:MultiTime
|
|
209
|
+
):Billing {
|
|
210
|
+
// Billing creation always happens in UTC time
|
|
211
|
+
const now = MultiClock.utcNow();
|
|
212
|
+
// If the createdAt time is not provided, use the current time
|
|
213
|
+
createdAt ??= now;
|
|
214
|
+
const billingId = Billing.makeBillingId(userId);
|
|
215
|
+
return ShareSyncFactory.get().createContent(
|
|
216
|
+
{
|
|
217
|
+
meta : {
|
|
218
|
+
ref : billingId,
|
|
219
|
+
is_head : true,
|
|
220
|
+
kind : ContentKind.BILLING,
|
|
221
|
+
id : billingId,
|
|
222
|
+
owner : userId,
|
|
223
|
+
created_at : createdAt,
|
|
224
|
+
updated_at : now,
|
|
225
|
+
},
|
|
226
|
+
payload : payload
|
|
227
|
+
}
|
|
228
|
+
) as Billing;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public isSubscribedToPricedProduct():boolean {
|
|
232
|
+
this.checkDisposed("Billing.isSubscribedToPricedProduct");
|
|
233
|
+
return this.priceId != null && this.productId != null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public get priceId():string {
|
|
237
|
+
this.checkDisposed("Billing.priceId");
|
|
238
|
+
return this.payload.price_id || null;
|
|
239
|
+
}
|
|
240
|
+
public setPriceId(value:string) {
|
|
241
|
+
this.checkDisposed("Billing.setPriceId");
|
|
242
|
+
if (this.payload.price_id !== value) {
|
|
243
|
+
const batch = new BatchOperation(this);
|
|
244
|
+
batch.setPathValue([ 'payload', 'price_id' ] , value);
|
|
245
|
+
batch.commit();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public get productId():string {
|
|
250
|
+
this.checkDisposed("Billing.productId");
|
|
251
|
+
return this.payload.product_id || null;
|
|
252
|
+
}
|
|
253
|
+
public setProductId(value:string) {
|
|
254
|
+
this.checkDisposed("Billing.setProductId");
|
|
255
|
+
if (this.payload.product_id !== value) {
|
|
256
|
+
const batch = new BatchOperation(this);
|
|
257
|
+
batch.setPathValue([ 'payload', 'product_id' ] , value);
|
|
258
|
+
batch.commit();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
public get paidUntil():CompactDateTime {
|
|
263
|
+
this.checkDisposed("Billing.paidUntil");
|
|
264
|
+
return this.payload.paid_until;
|
|
265
|
+
}
|
|
266
|
+
public setPaidUntil(value:CompactDateTime) {
|
|
267
|
+
this.checkDisposed("Billing.setPaidUntil");
|
|
268
|
+
if (this.paidUntil !== value) {
|
|
269
|
+
const batch = new BatchOperation(this);
|
|
270
|
+
batch.setPathValue([ 'payload', 'paid_until' ] , value);
|
|
271
|
+
batch.commit();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public get paymentStatus():PaymentStatus {
|
|
276
|
+
this.checkDisposed("Billing.paymentStatus");
|
|
277
|
+
return this.payload.payment_status;
|
|
278
|
+
}
|
|
279
|
+
public setPaymentStatus(value:PaymentStatus) {
|
|
280
|
+
this.checkDisposed("Billing.setPaymentStatus");
|
|
281
|
+
if (this.paymentStatus !== value) {
|
|
282
|
+
const batch = new BatchOperation(this);
|
|
283
|
+
batch.setPathValue([ 'payload', 'payment_status' ] , value);
|
|
284
|
+
batch.commit();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public get stripeCustomerId():string {
|
|
289
|
+
this.checkDisposed("Billing.stripeCustomerId");
|
|
290
|
+
return this.payload.stripe_customer_id;
|
|
291
|
+
}
|
|
292
|
+
public setStripeCustomerId(value:string) {
|
|
293
|
+
this.checkDisposed("Billing.setStripeCustomerId");
|
|
294
|
+
if (this.stripeCustomerId !== value) {
|
|
295
|
+
const batch = new BatchOperation(this);
|
|
296
|
+
batch.setPathValue([ 'payload', 'stripe_customer_id' ] , value);
|
|
297
|
+
batch.commit();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public get stripePaymentMethodId():string {
|
|
302
|
+
this.checkDisposed("Billing.stripePaymentMethodId");
|
|
303
|
+
return this.payload.stripe_payment_method_id;
|
|
304
|
+
}
|
|
305
|
+
public setStripePaymentMethodId(value:string) {
|
|
306
|
+
this.checkDisposed("Billing.setStripePaymentMethodId");
|
|
307
|
+
if (this.stripeCustomerId !== value) {
|
|
308
|
+
const batch = new BatchOperation(this);
|
|
309
|
+
batch.setPathValue([ 'payload', 'stripe_payment_method_id' ] , value);
|
|
310
|
+
batch.commit();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public get stripePaymentMethodType():string {
|
|
315
|
+
this.checkDisposed("Billing.stripePaymentMethodType");
|
|
316
|
+
return this.payload.stripe_payment_method_id;
|
|
317
|
+
}
|
|
318
|
+
public setStripePaymentMethodType(value:string) {
|
|
319
|
+
this.checkDisposed("Billing.setStripePaymentMethodType");
|
|
320
|
+
if (this.stripeCustomerId !== value) {
|
|
321
|
+
const batch = new BatchOperation(this);
|
|
322
|
+
batch.setPathValue([ 'payload', 'stripe_payment_method_type' ] , value);
|
|
323
|
+
batch.commit();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
public get creditCard():CreditCard {
|
|
328
|
+
this.checkDisposed("Billing.creditCard");
|
|
329
|
+
return this.payload.credit_card;
|
|
330
|
+
}
|
|
331
|
+
public setCreditCard(value:CreditCard) {
|
|
332
|
+
this.checkDisposed("Billing.setCreditCard");
|
|
333
|
+
if (this.creditCard != value) {
|
|
334
|
+
const batch = new BatchOperation(this);
|
|
335
|
+
batch.setPathValue([ 'payload', 'credit_card' ] , value);
|
|
336
|
+
batch.commit();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
public get transactions():ArrayView<TransactionPayload> {
|
|
341
|
+
this.checkDisposed("Billing.transactions");
|
|
342
|
+
return this._transactionsView;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Somehow, the transactions array on many users is not in the correct order. Some users have
|
|
346
|
+
// transactions in reverse order, and some have transactions in random order. Eventually, we
|
|
347
|
+
// will correct the stored data, but for now, we will clone the transactions array and sort it
|
|
348
|
+
// by the transaction time, so that we can correctly iterate through the transactions.
|
|
349
|
+
public get sortedTransactions():TransactionPayload[] {
|
|
350
|
+
this.checkDisposed("Billing.sortedTransactions");
|
|
351
|
+
return Struct.clone(this._transactionsView.values).sort(
|
|
352
|
+
(a:TransactionPayload, b:TransactionPayload) => {
|
|
353
|
+
return Time.compareDateTime(a.at_utc_time, b.at_utc_time);
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public getLastTransaction():TransactionPayload {
|
|
359
|
+
this.checkDisposed("Billing.getLastTransaction");
|
|
360
|
+
if (this._transactionsView.length > 0) {
|
|
361
|
+
const transactions = this.sortedTransactions;
|
|
362
|
+
return transactions[transactions.length - 1];
|
|
363
|
+
} else {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public getLastPaymentTransaction():TransactionPayload {
|
|
369
|
+
this.checkDisposed("Billing.getLastPaymentTransaction");
|
|
370
|
+
// Starts iterating from the most-recent transaction, so this function will
|
|
371
|
+
// return only the most-recent matching transaction.
|
|
372
|
+
const transactions = this.sortedTransactions;
|
|
373
|
+
for (let i = transactions.length - 1; i >= 0; i--) {
|
|
374
|
+
const transaction:TransactionPayload = transactions[i];
|
|
375
|
+
const topic:TransactionTopic = transaction.topic;
|
|
376
|
+
if (topic === TransactionTopic.PAYMENT_FAIL || topic === TransactionTopic.PAYMENT_SUCCESS) {
|
|
377
|
+
return transaction;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public getActiveUserCoupon():any {
|
|
384
|
+
this.checkDisposed("Billing.getActiveUserCoupon");
|
|
385
|
+
const nowInSeconds:number = (new Date()).getTime() / 1000;
|
|
386
|
+
// Coupons with a duration of "once" or "forever" have a null "end" value. We treat "once" coupons
|
|
387
|
+
// as being still-in-effect until a payment on the next invoice is attempted (either a pass or fail
|
|
388
|
+
// tells us that the billing period has completed).
|
|
389
|
+
const transactions = this.sortedTransactions;
|
|
390
|
+
for (let i = transactions.length - 1; i >= 0; i--) {
|
|
391
|
+
const transaction = transactions[i];
|
|
392
|
+
const topic:TransactionTopic = transaction.topic;
|
|
393
|
+
if (topic === TransactionTopic.CANCEL_SUBSCRIPTION) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
if (topic === TransactionTopic.APPLY_COUPON) {
|
|
397
|
+
const discount:any = transaction.details;
|
|
398
|
+
const coupon:any = discount.coupon;
|
|
399
|
+
if (coupon.duration === "forever" || (nowInSeconds >= discount.start && nowInSeconds <= discount.end)) {
|
|
400
|
+
return coupon;
|
|
401
|
+
} else {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
public daysPastDue():number {
|
|
410
|
+
this.checkDisposed("Billing.daysPastDue");
|
|
411
|
+
const daysSincePaidUntil:number = Time.timeSince(this.paidUntil, 'days');
|
|
412
|
+
return daysSincePaidUntil < 0 ? 0 : daysSincePaidUntil;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public didLastPaymentFail():boolean {
|
|
416
|
+
this.checkDisposed("Billing.didLastPaymentFail");
|
|
417
|
+
const t:TransactionPayload = this.getLastPaymentTransaction();
|
|
418
|
+
return t != null && t.topic === TransactionTopic.PAYMENT_FAIL;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public didLastPaymentSucceed():boolean {
|
|
422
|
+
this.checkDisposed("Billing.didLastPaymentSucceed");
|
|
423
|
+
const t:TransactionPayload = this.getLastPaymentTransaction();
|
|
424
|
+
return t != null && t.topic === TransactionTopic.PAYMENT_SUCCESS;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
public findTransactionByTopic(topic:TransactionTopic):TransactionPayload {
|
|
428
|
+
this.checkDisposed("Billing.findTransactionByTopic");
|
|
429
|
+
const transactions = this.sortedTransactions;
|
|
430
|
+
// Starts iterating from the most-recent transaction, so this function will
|
|
431
|
+
// return only the most-recent matching transaction.
|
|
432
|
+
for (let i = transactions.length - 1; i >= 0; i--) {
|
|
433
|
+
const transaction:TransactionPayload = transactions[i];
|
|
434
|
+
if (transaction.topic === topic) {
|
|
435
|
+
return transaction;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
public cancel():void {
|
|
442
|
+
this.checkDisposed("Billing.cancel");
|
|
443
|
+
|
|
444
|
+
const now:MultiTime = MultiClock.utcNow();
|
|
445
|
+
const transaction:TransactionPayload = {
|
|
446
|
+
at_utc_time: now.utc_time,
|
|
447
|
+
topic: TransactionTopic.CANCEL_SUBSCRIPTION
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const batch = new BatchOperation(this, now);
|
|
451
|
+
|
|
452
|
+
// Add a transaction to the billing model for this cancellation
|
|
453
|
+
batch.pushIntoArray([ 'payload', 'transactions' ], transaction);
|
|
454
|
+
batch.setPathValue([ 'payload', 'plan' ], null);
|
|
455
|
+
batch.setPathValue([ 'payload', 'price_id' ], null);
|
|
456
|
+
batch.setPathValue([ 'payload', 'product_id' ], null);
|
|
457
|
+
batch.setPathValue([ 'payload', 'paid_until' ], null);
|
|
458
|
+
batch.setPathValue([ 'payload', 'payment_status' ], PaymentStatus.CANCELLED);
|
|
459
|
+
|
|
460
|
+
batch.commit();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
public restartPlan(
|
|
464
|
+
plan:string,
|
|
465
|
+
paidUntilEpochSeconds:number
|
|
466
|
+
):void {
|
|
467
|
+
this.checkDisposed("Billing.restartPlan");
|
|
468
|
+
|
|
469
|
+
const now:MultiTime = MultiClock.utcNow();
|
|
470
|
+
const transaction:TransactionPayload = {
|
|
471
|
+
at_utc_time: now.utc_time,
|
|
472
|
+
topic: TransactionTopic.RESTART_SUBSCRIPTION
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const batch = new BatchOperation(this, now);
|
|
476
|
+
|
|
477
|
+
// Add a transaction to the billing model for this cancellation
|
|
478
|
+
batch.pushIntoArray([ 'payload', 'transactions' ], transaction);
|
|
479
|
+
batch.setPathValue([ 'payload', 'plan' ], plan);
|
|
480
|
+
batch.setPathValue([ 'payload', 'product_id' ], null);
|
|
481
|
+
batch.setPathValue([ 'payload', 'price_id' ], null);
|
|
482
|
+
batch.setPathValue([ 'payload', 'paid_until' ], Time.utcFromEpochSeconds(paidUntilEpochSeconds));
|
|
483
|
+
batch.setPathValue([ 'payload', 'payment_status' ], PaymentStatus.PAID);
|
|
484
|
+
|
|
485
|
+
batch.commit();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
public restartPricedProduct(
|
|
489
|
+
productId:string,
|
|
490
|
+
priceId:string,
|
|
491
|
+
paidUntilEpochSeconds:number
|
|
492
|
+
):void {
|
|
493
|
+
this.checkDisposed("Billing.restartPricedProduct");
|
|
494
|
+
|
|
495
|
+
const now:MultiTime = MultiClock.utcNow();
|
|
496
|
+
const transaction:TransactionPayload = {
|
|
497
|
+
at_utc_time: now.utc_time,
|
|
498
|
+
topic: TransactionTopic.RESTART_SUBSCRIPTION
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const batch = new BatchOperation(this, now);
|
|
502
|
+
|
|
503
|
+
// Add a transaction to the billing model for this cancellation
|
|
504
|
+
batch.pushIntoArray([ 'payload', 'transactions' ], transaction);
|
|
505
|
+
batch.setPathValue([ 'payload', 'plan' ], null);
|
|
506
|
+
batch.setPathValue([ 'payload', 'product_id' ], productId);
|
|
507
|
+
batch.setPathValue([ 'payload', 'price_id' ], priceId);
|
|
508
|
+
batch.setPathValue([ 'payload', 'paid_until' ], Time.utcFromEpochSeconds(paidUntilEpochSeconds));
|
|
509
|
+
batch.setPathValue([ 'payload', 'payment_status' ], PaymentStatus.PAID);
|
|
510
|
+
|
|
511
|
+
batch.commit();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
public static async findByStripeCustomerId(stripeCustomerId:string):Promise<Billing[]> {
|
|
515
|
+
const shareSync = ShareSyncFactory.get();
|
|
516
|
+
return shareSync.findAndAcquire(
|
|
517
|
+
ContentKind.BILLING, { "payload.stripe_customer_id" : stripeCustomerId }
|
|
518
|
+
) as Promise<Billing[]>;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
public static describeCoupon(coupon:any):string {
|
|
522
|
+
|
|
523
|
+
let description:string = "";
|
|
524
|
+
if (coupon.hasOwnProperty("percent_off") && coupon.percent_off !== null) {
|
|
525
|
+
description = coupon.percent_off + "% OFF";
|
|
526
|
+
} else if (coupon.hasOwnProperty("amount_off") && coupon.amount_off !== null) {
|
|
527
|
+
description = NumberFormat.dollarsFromCents(coupon.amount_off) + " OFF";
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (coupon.duration === "forever") {
|
|
531
|
+
description += ", FOREVER";
|
|
532
|
+
} else if (coupon.duration === "once") {
|
|
533
|
+
description += " YOUR NEXT INVOICE";
|
|
534
|
+
} else if (coupon.duration === "repeating") {
|
|
535
|
+
description += " FOR " + coupon.duration_in_months + " MONTHS";
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return description;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
public static normalizeCreditCardBrand(brand:string):string {
|
|
542
|
+
const lowerCaseNoSpaces:string = brand.toLowerCase().replace(/\s/g, '');
|
|
543
|
+
if (lowerCaseNoSpaces.startsWith("visa")) {
|
|
544
|
+
return "VISA";
|
|
545
|
+
} else if (lowerCaseNoSpaces.startsWith("mastercard")) {
|
|
546
|
+
return "MC";
|
|
547
|
+
} else if (lowerCaseNoSpaces.startsWith("americanexpress")) {
|
|
548
|
+
return "AMEX";
|
|
549
|
+
} else if (lowerCaseNoSpaces.startsWith("diners")) {
|
|
550
|
+
return "DINERS";
|
|
551
|
+
} else if (lowerCaseNoSpaces.startsWith("jcb")) {
|
|
552
|
+
return "JCB";
|
|
553
|
+
} else if (lowerCaseNoSpaces.startsWith("discover")) {
|
|
554
|
+
return "DISC";
|
|
555
|
+
}
|
|
556
|
+
return brand;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Doc } from '@shaxpir/sharedb/lib/client';
|
|
2
|
+
import { ShareSync } from '../../repo/ShareSync';
|
|
3
|
+
import { Struct } from '../../util/Struct';
|
|
4
|
+
import { BillingPayload } from "./Billing";
|
|
5
|
+
import { ContentKind } from './ContentKind';
|
|
6
|
+
import { DevicePayload } from './Device';
|
|
7
|
+
import { HanziPayload } from './Hanzi';
|
|
8
|
+
import { Model } from './Model';
|
|
9
|
+
import { PhrasePayload } from './Phrase';
|
|
10
|
+
import { ProfilePayload } from "./Profile";
|
|
11
|
+
import { CompactDateTime, MultiTime } from './Time';
|
|
12
|
+
import { UserPayload } from './User';
|
|
13
|
+
import { MediaPayload } from './Media';
|
|
14
|
+
|
|
15
|
+
enum ContentIdBrand {}
|
|
16
|
+
enum ContentRefBrand {}
|
|
17
|
+
|
|
18
|
+
export type ContentId = string & ContentIdBrand;
|
|
19
|
+
export type ContentRef = (string & ContentRefBrand) | ContentId;
|
|
20
|
+
|
|
21
|
+
export interface ParsedContentRef {
|
|
22
|
+
id:ContentId;
|
|
23
|
+
kind?:ContentKind;
|
|
24
|
+
timestamp?:CompactDateTime;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ContentBody {
|
|
28
|
+
meta:ContentMeta;
|
|
29
|
+
payload:ContentPayload;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ContentMeta {
|
|
33
|
+
ref:ContentRef;
|
|
34
|
+
kind:ContentKind;
|
|
35
|
+
is_head:boolean;
|
|
36
|
+
id:ContentId;
|
|
37
|
+
owner:ContentId;
|
|
38
|
+
created_at:MultiTime;
|
|
39
|
+
updated_at:MultiTime;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ContentPayload =
|
|
43
|
+
BillingPayload
|
|
44
|
+
| HanziPayload
|
|
45
|
+
| PhrasePayload
|
|
46
|
+
| DevicePayload
|
|
47
|
+
| MediaPayload
|
|
48
|
+
| ProfilePayload
|
|
49
|
+
| UserPayload;
|
|
50
|
+
|
|
51
|
+
export abstract class Content extends Model {
|
|
52
|
+
|
|
53
|
+
public static ID_LENGTH:number = 16;
|
|
54
|
+
|
|
55
|
+
constructor(doc:Doc, shouldAcquire:boolean, shareSync:ShareSync) {
|
|
56
|
+
super(doc, shouldAcquire, shareSync);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public get isHead():boolean {
|
|
60
|
+
this.checkDisposed("Content.isHead");
|
|
61
|
+
return this.meta.is_head;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public get meta():ContentMeta {
|
|
65
|
+
this.checkDisposed("Content.meta");
|
|
66
|
+
return this.doc.data.meta as ContentMeta;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public get payload():ContentPayload {
|
|
70
|
+
this.checkDisposed("Content.payload");
|
|
71
|
+
return this.doc.data.payload as ContentPayload;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public cloneContentBody():ContentBody {
|
|
75
|
+
this.checkDisposed("Content.cloneContentBody");
|
|
76
|
+
if (this.exists()) {
|
|
77
|
+
return Struct.clone(this.doc.data);
|
|
78
|
+
} else {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public get id():ContentId {
|
|
84
|
+
this.checkDisposed("Content.id");
|
|
85
|
+
return this.meta.id as ContentId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public get updatedAt():MultiTime {
|
|
89
|
+
this.checkDisposed("Content.updatedAt");
|
|
90
|
+
return this.meta.updated_at as MultiTime;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public get createdAt():MultiTime {
|
|
94
|
+
this.checkDisposed("Content.createdAt");
|
|
95
|
+
return this.meta.created_at as MultiTime;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public get owner():ContentId {
|
|
99
|
+
this.checkDisposed("Content.owner");
|
|
100
|
+
return this.meta.owner as ContentId;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public async modelUpdated():Promise<void> {
|
|
104
|
+
this.checkDisposed("Content.modelUpdated");
|
|
105
|
+
this.acquire();
|
|
106
|
+
await this.shareSync.updateManifestForContent(this);
|
|
107
|
+
this.release();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export enum ContentKind {
|
|
2
|
+
|
|
3
|
+
// These are all the current Content subclasses
|
|
4
|
+
BILLING = "billing",
|
|
5
|
+
DEVICE = "device",
|
|
6
|
+
METRIC = "metric",
|
|
7
|
+
PROFILE = "profile",
|
|
8
|
+
SESSION = "session",
|
|
9
|
+
USER = "user",
|
|
10
|
+
MEDIA = "media",
|
|
11
|
+
|
|
12
|
+
// These are used in the ShareDB system, but for internal bookkeeping, not for Content subclasses.
|
|
13
|
+
MANIFEST = "manifest",
|
|
14
|
+
}
|