@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.
Files changed (211) hide show
  1. package/README.md +1 -0
  2. package/decs.d.ts +87 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +20 -0
  5. package/dist/models/OutboundMessage.d.ts +18 -0
  6. package/dist/models/OutboundMessage.js +25 -0
  7. package/dist/models/content/Activity.d.ts +10 -0
  8. package/dist/models/content/Activity.js +2 -0
  9. package/dist/models/content/ArrayView.d.ts +26 -0
  10. package/dist/models/content/ArrayView.js +174 -0
  11. package/dist/models/content/Billing.d.ts +144 -0
  12. package/dist/models/content/Billing.js +418 -0
  13. package/dist/models/content/Book.d.ts +77 -0
  14. package/dist/models/content/Book.js +407 -0
  15. package/dist/models/content/Category.d.ts +16 -0
  16. package/dist/models/content/Category.js +20 -0
  17. package/dist/models/content/Checkpointable.d.ts +21 -0
  18. package/dist/models/content/Checkpointable.js +156 -0
  19. package/dist/models/content/Comment.d.ts +19 -0
  20. package/dist/models/content/Comment.js +53 -0
  21. package/dist/models/content/ConceptArt.d.ts +31 -0
  22. package/dist/models/content/ConceptArt.js +84 -0
  23. package/dist/models/content/Content.d.ts +52 -0
  24. package/dist/models/content/Content.js +61 -0
  25. package/dist/models/content/ContentKind.d.ts +10 -0
  26. package/dist/models/content/ContentKind.js +16 -0
  27. package/dist/models/content/Context.d.ts +28 -0
  28. package/dist/models/content/Context.js +162 -0
  29. package/dist/models/content/DevEnv.d.ts +5 -0
  30. package/dist/models/content/DevEnv.js +9 -0
  31. package/dist/models/content/Device.d.ts +24 -0
  32. package/dist/models/content/Device.js +62 -0
  33. package/dist/models/content/Dictionary.d.ts +31 -0
  34. package/dist/models/content/Dictionary.js +5 -0
  35. package/dist/models/content/DictionaryEntry.d.ts +20 -0
  36. package/dist/models/content/DictionaryEntry.js +2 -0
  37. package/dist/models/content/ElasticModel.d.ts +149 -0
  38. package/dist/models/content/ElasticModel.js +179 -0
  39. package/dist/models/content/Environment.d.ts +61 -0
  40. package/dist/models/content/Environment.js +124 -0
  41. package/dist/models/content/ExportOptions.d.ts +64 -0
  42. package/dist/models/content/ExportOptions.js +213 -0
  43. package/dist/models/content/Folder.d.ts +16 -0
  44. package/dist/models/content/Folder.js +33 -0
  45. package/dist/models/content/Fragment.d.ts +54 -0
  46. package/dist/models/content/Fragment.js +181 -0
  47. package/dist/models/content/GeoLocation.d.ts +4 -0
  48. package/dist/models/content/GeoLocation.js +2 -0
  49. package/dist/models/content/Hanzi.d.ts +21 -0
  50. package/dist/models/content/Hanzi.js +2 -0
  51. package/dist/models/content/HighlightRule.d.ts +9 -0
  52. package/dist/models/content/HighlightRule.js +2 -0
  53. package/dist/models/content/Manifest.d.ts +42 -0
  54. package/dist/models/content/Manifest.js +114 -0
  55. package/dist/models/content/Media.d.ts +32 -0
  56. package/dist/models/content/Media.js +98 -0
  57. package/dist/models/content/Metric.d.ts +46 -0
  58. package/dist/models/content/Metric.js +183 -0
  59. package/dist/models/content/Migration.d.ts +68 -0
  60. package/dist/models/content/Migration.js +155 -0
  61. package/dist/models/content/Model.d.ts +45 -0
  62. package/dist/models/content/Model.js +280 -0
  63. package/dist/models/content/Permissions.d.ts +7 -0
  64. package/dist/models/content/Permissions.js +20 -0
  65. package/dist/models/content/Phrase.d.ts +8 -0
  66. package/dist/models/content/Phrase.js +2 -0
  67. package/dist/models/content/Placeholder.d.ts +8 -0
  68. package/dist/models/content/Placeholder.js +36 -0
  69. package/dist/models/content/Profile.d.ts +30 -0
  70. package/dist/models/content/Profile.js +95 -0
  71. package/dist/models/content/RichText.d.ts +58 -0
  72. package/dist/models/content/RichText.js +79 -0
  73. package/dist/models/content/Session.d.ts +39 -0
  74. package/dist/models/content/Session.js +173 -0
  75. package/dist/models/content/Speech.d.ts +67 -0
  76. package/dist/models/content/Speech.js +97 -0
  77. package/dist/models/content/Stub.d.ts +24 -0
  78. package/dist/models/content/Stub.js +179 -0
  79. package/dist/models/content/Time.d.ts +56 -0
  80. package/dist/models/content/Time.js +295 -0
  81. package/dist/models/content/User.d.ts +36 -0
  82. package/dist/models/content/User.js +95 -0
  83. package/dist/models/content/Workspace.d.ts +71 -0
  84. package/dist/models/content/Workspace.js +237 -0
  85. package/dist/models/content/index.d.ts +36 -0
  86. package/dist/models/content/index.js +53 -0
  87. package/dist/models/index.d.ts +4 -0
  88. package/dist/models/index.js +21 -0
  89. package/dist/models/legacy/LegacyBodyFormat.d.ts +9 -0
  90. package/dist/models/legacy/LegacyBodyFormat.js +2 -0
  91. package/dist/models/legacy/LegacyComment.d.ts +12 -0
  92. package/dist/models/legacy/LegacyComment.js +2 -0
  93. package/dist/models/legacy/LegacyContent.d.ts +53 -0
  94. package/dist/models/legacy/LegacyContent.js +55 -0
  95. package/dist/models/legacy/LegacyConversion.d.ts +55 -0
  96. package/dist/models/legacy/LegacyConversion.js +401 -0
  97. package/dist/models/legacy/LegacyFragment.d.ts +21 -0
  98. package/dist/models/legacy/LegacyFragment.js +2 -0
  99. package/dist/models/legacy/LegacyLocator.d.ts +8 -0
  100. package/dist/models/legacy/LegacyLocator.js +31 -0
  101. package/dist/models/legacy/LegacyOutboundMessage.d.ts +16 -0
  102. package/dist/models/legacy/LegacyOutboundMessage.js +13 -0
  103. package/dist/models/legacy/LegacyPicture.d.ts +14 -0
  104. package/dist/models/legacy/LegacyPicture.js +2 -0
  105. package/dist/models/legacy/LegacyProfile.d.ts +9 -0
  106. package/dist/models/legacy/LegacyProfile.js +2 -0
  107. package/dist/models/legacy/LegacySession.d.ts +41 -0
  108. package/dist/models/legacy/LegacySession.js +35 -0
  109. package/dist/models/legacy/LegacyStory.d.ts +23 -0
  110. package/dist/models/legacy/LegacyStory.js +2 -0
  111. package/dist/models/legacy/LegacyStub.d.ts +15 -0
  112. package/dist/models/legacy/LegacyStub.js +2 -0
  113. package/dist/models/legacy/LegacyTransaction.d.ts +14 -0
  114. package/dist/models/legacy/LegacyTransaction.js +49 -0
  115. package/dist/models/legacy/LegacyUser.d.ts +28 -0
  116. package/dist/models/legacy/LegacyUser.js +32 -0
  117. package/dist/models/legacy/LegacyWorkspace.d.ts +23 -0
  118. package/dist/models/legacy/LegacyWorkspace.js +6 -0
  119. package/dist/models/legacy/index.d.ts +15 -0
  120. package/dist/models/legacy/index.js +32 -0
  121. package/dist/models/markup/BodyFormat.d.ts +14 -0
  122. package/dist/models/markup/BodyFormat.js +190 -0
  123. package/dist/models/markup/ChangeModel.d.ts +22 -0
  124. package/dist/models/markup/ChangeModel.js +107 -0
  125. package/dist/models/markup/DeltaOps.d.ts +5 -0
  126. package/dist/models/markup/DeltaOps.js +74 -0
  127. package/dist/models/markup/HtmlMarkup.d.ts +4 -0
  128. package/dist/models/markup/HtmlMarkup.js +21 -0
  129. package/dist/models/markup/Operation.d.ts +32 -0
  130. package/dist/models/markup/Operation.js +194 -0
  131. package/dist/models/markup/TextEditOps.d.ts +9 -0
  132. package/dist/models/markup/TextEditOps.js +50 -0
  133. package/dist/models/markup/index.d.ts +6 -0
  134. package/dist/models/markup/index.js +23 -0
  135. package/dist/repo/ConnectionListener.d.ts +9 -0
  136. package/dist/repo/ConnectionListener.js +21 -0
  137. package/dist/repo/PermissiveJson1.d.ts +58 -0
  138. package/dist/repo/PermissiveJson1.js +39 -0
  139. package/dist/repo/ShareSync.d.ts +60 -0
  140. package/dist/repo/ShareSync.js +348 -0
  141. package/dist/repo/index.d.ts +3 -0
  142. package/dist/repo/index.js +20 -0
  143. package/dist/util/Async.d.ts +8 -0
  144. package/dist/util/Async.js +18 -0
  145. package/dist/util/Base62.d.ts +6 -0
  146. package/dist/util/Base62.js +47 -0
  147. package/dist/util/BinarySearch.d.ts +7 -0
  148. package/dist/util/BinarySearch.js +46 -0
  149. package/dist/util/CachingHasher.d.ts +8 -0
  150. package/dist/util/CachingHasher.js +41 -0
  151. package/dist/util/Color.d.ts +32 -0
  152. package/dist/util/Color.js +204 -0
  153. package/dist/util/Dispatch.d.ts +15 -0
  154. package/dist/util/Dispatch.js +79 -0
  155. package/dist/util/EditDistance.d.ts +13 -0
  156. package/dist/util/EditDistance.js +184 -0
  157. package/dist/util/Encryption.d.ts +5 -0
  158. package/dist/util/Encryption.js +2 -0
  159. package/dist/util/Logging.d.ts +108 -0
  160. package/dist/util/Logging.js +412 -0
  161. package/dist/util/NumberFormat.d.ts +14 -0
  162. package/dist/util/NumberFormat.js +224 -0
  163. package/dist/util/Struct.d.ts +4 -0
  164. package/dist/util/Struct.js +15 -0
  165. package/dist/util/Template.d.ts +16 -0
  166. package/dist/util/Template.js +128 -0
  167. package/dist/util/Text.d.ts +45 -0
  168. package/dist/util/Text.js +243 -0
  169. package/dist/util/Tuples.d.ts +9 -0
  170. package/dist/util/Tuples.js +135 -0
  171. package/dist/util/Validate.d.ts +4 -0
  172. package/dist/util/Validate.js +11 -0
  173. package/dist/util/Vocabulary.d.ts +3 -0
  174. package/dist/util/Vocabulary.js +35 -0
  175. package/dist/util/index.d.ts +16 -0
  176. package/dist/util/index.js +33 -0
  177. package/lib/models/content/ArrayView.ts +203 -0
  178. package/lib/models/content/Billing.ts +558 -0
  179. package/lib/models/content/Content.ts +110 -0
  180. package/lib/models/content/ContentKind.ts +14 -0
  181. package/lib/models/content/DevEnv.ts +5 -0
  182. package/lib/models/content/Device.ts +86 -0
  183. package/lib/models/content/DictionaryEntry.ts +22 -0
  184. package/lib/models/content/GeoLocation.ts +4 -0
  185. package/lib/models/content/Hanzi.ts +25 -0
  186. package/lib/models/content/Manifest.ts +162 -0
  187. package/lib/models/content/Media.ts +126 -0
  188. package/lib/models/content/Model.ts +327 -0
  189. package/lib/models/content/Permissions.ts +21 -0
  190. package/lib/models/content/Phrase.ts +10 -0
  191. package/lib/models/content/Profile.ts +119 -0
  192. package/lib/models/content/Time.ts +328 -0
  193. package/lib/models/content/User.ts +130 -0
  194. package/lib/models/markup/ChangeModel.ts +95 -0
  195. package/lib/models/markup/DeltaOps.ts +71 -0
  196. package/lib/models/markup/Operation.ts +215 -0
  197. package/lib/models/markup/TextEditOps.ts +50 -0
  198. package/lib/repo/ConnectionListener.ts +25 -0
  199. package/lib/repo/PermissiveJson1.ts +14 -0
  200. package/lib/repo/ShareSync.ts +390 -0
  201. package/lib/util/Base62.ts +47 -0
  202. package/lib/util/CachingHasher.ts +38 -0
  203. package/lib/util/Dispatch.ts +92 -0
  204. package/lib/util/Encryption.ts +5 -0
  205. package/lib/util/Logging.ts +568 -0
  206. package/lib/util/NumberFormat.ts +194 -0
  207. package/lib/util/Struct.ts +14 -0
  208. package/lib/util/Tuples.ts +131 -0
  209. package/package.json +41 -0
  210. package/tsconfig.json +25 -0
  211. 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
+ }
@@ -0,0 +1,5 @@
1
+ export enum DevEnv {
2
+ LOCAL = "LOCAL",
3
+ STAGING = "STAGING",
4
+ PRODUCTION = "PRODUCTION",
5
+ }