mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
Compare commits
1649 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
edb50cbf2c | ||
|
3a12b514f1 | ||
|
dc96b96359 | ||
|
cd3aa46f34 | ||
|
d8f1b2e0bd | ||
|
5d8664d725 | ||
|
a921e7e894 | ||
|
e1832d890a | ||
|
b10ef272f1 | ||
|
fd76b1b226 | ||
|
5db26cb7fe | ||
|
350ed9fcaf | ||
|
bd3b715cf2 | ||
|
51925b03b7 | ||
|
c4ac7f5b03 | ||
|
039daa2bfa | ||
|
e0a51948d9 | ||
|
ebad0b7cbf | ||
|
523e651a10 | ||
|
40916803e7 | ||
|
5bd2c70c3c | ||
|
c61bccb671 | ||
|
735ba2ce56 | ||
|
cbc2f495ff | ||
|
e3bc788660 | ||
|
51c6c82003 | ||
|
5120d97633 | ||
|
77f24e2b56 | ||
|
683ec852fd | ||
|
028874bab3 | ||
|
0119b383a6 | ||
|
d3125f59e1 | ||
|
e1f02dc6f2 | ||
|
cd0e55cb95 | ||
|
dda6f1e123 | ||
|
c26e2ab178 | ||
|
0958345416 | ||
|
83236dc283 | ||
|
d6c80d1ce7 | ||
|
e046e6e228 | ||
|
a46640b325 | ||
|
bd548eb7f3 | ||
|
7d42d7b4db | ||
|
3a374f5e68 | ||
|
d5a327f122 | ||
|
3649293d71 | ||
|
a6c2c16666 | ||
|
ccc1f3cd85 | ||
|
c372ceb7a2 | ||
|
e289c5835d | ||
|
ff12072f18 | ||
|
1e11e929ec | ||
|
a293aeee23 | ||
|
ffabebd7f3 | ||
|
699bf262ca | ||
|
02ba302009 | ||
|
f1cb9be79c | ||
|
925bf7b907 | ||
|
31e98b8b65 | ||
|
7fd582025f | ||
|
0aa438e199 | ||
|
21a86f3ef7 | ||
|
0d66d3fafd | ||
|
72bb3fb6c2 | ||
|
fe28d0731e | ||
|
3a26daf911 | ||
|
65f6bea7d7 | ||
|
16042ba15d | ||
|
76488bd2fe | ||
|
dec8c7dba4 | ||
|
a2373a63cd | ||
|
6da9aeee7d | ||
|
674b254b65 | ||
|
575f320009 | ||
|
9ebad5d505 | ||
|
49fd2e65de | ||
|
3470e541c8 | ||
|
c240008edb | ||
|
849c296f90 | ||
|
b6cf14608d | ||
|
d7f9a21519 | ||
|
e4832cc9da | ||
|
8fdb62a6bc | ||
|
70bfcd3402 | ||
|
4826393ec4 | ||
|
0ed6dab806 | ||
|
6ed6bcef05 | ||
|
b62bd83083 | ||
|
28769e040f | ||
|
0e65e74566 | ||
|
b5347c78b2 | ||
|
e7ead7383d | ||
|
4da28970fa | ||
|
970c7d1ddf | ||
|
0f3125582b | ||
|
9f2995a6f6 | ||
|
9572c5ddb0 | ||
|
abe67a3000 | ||
|
c1a47012fd | ||
|
2d015d85d6 | ||
|
e8251bb758 | ||
|
2d4c0ef292 | ||
|
aae9773f09 | ||
|
ab41f13575 | ||
|
7adc7ed490 | ||
|
744cadc33c | ||
|
3ef9d18baf | ||
|
4825479600 | ||
|
32757b489b | ||
|
361d2d0688 | ||
|
11ea8ae9fe | ||
|
a494244427 | ||
|
a7a5188030 | ||
|
a4b294da98 | ||
|
309aef4c27 | ||
|
20769f0fe9 | ||
|
36472413e4 | ||
|
ae3881b472 | ||
|
61ebee6993 | ||
|
24445309e2 | ||
|
5e4bd9f57d | ||
|
e91f22c416 | ||
|
2abe325147 | ||
|
1a5e07e0f9 | ||
|
22e3955262 | ||
|
4b1229c072 | ||
|
5f0492b3b1 | ||
|
ab97a84f3a | ||
|
7b22fec532 | ||
|
47c85fa02e | ||
|
fd3be00fbd | ||
|
71958fc1d8 | ||
|
942e419b85 | ||
|
19672241e6 | ||
|
9c68a97472 | ||
|
bb6a75c3dc | ||
|
0c37b9f116 | ||
|
001ec76e1f | ||
|
afe2197210 | ||
|
dee4e3fb89 | ||
|
f34a5d6828 | ||
|
c2cb804ebe | ||
|
3127fb4dbd | ||
|
f0ecb93515 | ||
|
3968fe9fdf | ||
|
9add47ba0f | ||
|
f5197518a9 | ||
|
4e0a79d146 | ||
|
023f8a0def | ||
|
56a675d5ec | ||
|
e12c433745 | ||
|
4e21cc0152 | ||
|
13b9e90a40 | ||
|
2a2e744b27 | ||
|
ee8ff4df66 | ||
|
a0041afaf9 | ||
|
b80a095196 | ||
|
1a3c195f7e | ||
|
e8b32a4b67 | ||
|
39ac378220 | ||
|
e984107966 | ||
|
c109690cf8 | ||
|
4bd28f7ae6 | ||
|
96f096f38e | ||
|
ebdd55814c | ||
|
a6d3bbfc63 | ||
|
55ead5b77f | ||
|
4fef10f4e6 | ||
|
01d34a8575 | ||
|
9ce6cc5097 | ||
|
e7c8077abc | ||
|
954a98b49f | ||
|
6116ede5f7 | ||
|
8e88ca3a46 | ||
|
d7043eec49 | ||
|
d5a0769fc0 | ||
|
3e5e4ce561 | ||
|
1d788980be | ||
|
73d26c8b28 | ||
|
022c8beaf5 | ||
|
8df0d95559 | ||
|
85f7ac8172 | ||
|
1f4de8328d | ||
|
ac55a5222c | ||
|
65489b0391 | ||
|
bffc473ec8 | ||
|
b1ce237624 | ||
|
f9ee393e4f | ||
|
5779320874 | ||
|
8f680c3dac | ||
|
b4e2313cbc | ||
|
fbd73ba637 | ||
|
a7fb7d1f5c | ||
|
9ce5d51bdc | ||
|
6375d72862 | ||
|
7f23b7fb98 | ||
|
3e615fd8b0 | ||
|
ab8b50453b | ||
|
150d9c9d3e | ||
|
e8ba08367b | ||
|
893e1475bb | ||
|
1bcd7dfbad | ||
|
6613f4c22c | ||
|
2d11e163dd | ||
|
4fab08172b | ||
|
8f7e38c559 | ||
|
55179f913c | ||
|
88fae0b6a9 | ||
|
5649b954ef | ||
|
5fd9748e13 | ||
|
76bc253a8a | ||
|
2162d06e7c | ||
|
f4221f2655 | ||
|
0420ecadeb | ||
|
998437c123 | ||
|
b8221555d2 | ||
|
9b25f8789f | ||
|
18ffec626f | ||
|
e1772b3b8f | ||
|
07bac66db7 | ||
|
4a56980bff | ||
|
b7a0a45f01 | ||
|
805ec5fbdb | ||
|
0d0007742f | ||
|
a1a7047819 | ||
|
090227e809 | ||
|
9986e41999 | ||
|
a37558b021 | ||
|
db6e6472b1 | ||
|
60f2213451 | ||
|
fa2b5df107 | ||
|
3f385cae38 | ||
|
cf5b21d422 | ||
|
5bf437b964 | ||
|
8ac5ebbf39 | ||
|
ec3ca2b30e | ||
|
8fc3a1f6b7 | ||
|
6e0764f32d | ||
|
50a53a4fc6 | ||
|
1159480ef8 | ||
|
89467538c7 | ||
|
9231e5453c | ||
|
84dd501775 | ||
|
757e0e39b8 | ||
|
f29aff0de6 | ||
|
5c0dc326eb | ||
|
2ecd02aa5d | ||
|
2f9a38971d | ||
|
a3996098cb | ||
|
dc663057ce | ||
|
c665f809f2 | ||
|
837483b026 | ||
|
6777b94d41 | ||
|
9bac192241 | ||
|
88c1d868d2 | ||
|
dccfadc1a5 | ||
|
78d98168a4 | ||
|
c2fcfc087a | ||
|
643d4cacd3 | ||
|
8d404dbcc6 | ||
|
9dc4b04e52 | ||
|
42deef8c57 | ||
|
52a513b0a8 | ||
|
75145fdb58 | ||
|
03e12e1921 | ||
|
aea93f9826 | ||
|
1e65cdd072 | ||
|
a964977960 | ||
|
a2751d90c2 | ||
|
a197959959 | ||
|
c176891c2e | ||
|
5641afc6e2 | ||
|
1b0b72c827 | ||
|
81745ba171 | ||
|
4ccbeeb4a9 | ||
|
a5324b6ea7 | ||
|
6a1017ea94 | ||
|
9018ef5175 | ||
|
21b51ba8d1 | ||
|
7331cdc0f5 | ||
|
0181086d0f | ||
|
88ed75d06d | ||
|
9b740df277 | ||
|
4aa2ac1b52 | ||
|
421cda0e34 | ||
|
fe280fc38c | ||
|
83a7595feb | ||
|
7c11fe819a | ||
|
03c71dad17 | ||
|
8eac361fb5 | ||
|
370cf1a2cb | ||
|
f2b26b9bcc | ||
|
6994fad237 | ||
|
ff90c0101c | ||
|
ad071ced47 | ||
|
2dc447d707 | ||
|
434094e599 | ||
|
9f2c52c86e | ||
|
3b7d899532 | ||
|
eea711a805 | ||
|
852756aeb3 | ||
|
77446a710c | ||
|
025df33d7a | ||
|
681f07f98f | ||
|
4cb9a472e6 | ||
|
17a9f20f76 | ||
|
63702d50e6 | ||
|
df436a55b4 | ||
|
5fec84221e | ||
|
1228e541d7 | ||
|
1279249a09 | ||
|
f7cf759740 | ||
|
4480846aa5 | ||
|
17c906d6bb | ||
|
6de77d30eb | ||
|
afe575f02e | ||
|
dfd52ebe1f | ||
|
49b16b5b01 | ||
|
b89769420a | ||
|
ce1db751d0 | ||
|
537b7612cb | ||
|
1e811b42ec | ||
|
bec875a9af | ||
|
33a322baa2 | ||
|
9c377a05f3 | ||
|
8cf5810fc0 | ||
|
2ebd3f244d | ||
|
42d886a6d5 | ||
|
c89356fabf | ||
|
c172001f22 | ||
|
45d07e8472 | ||
|
a64c434b9e | ||
|
5057406f16 | ||
|
746870df65 | ||
|
268c25e95f | ||
|
2b81d66537 | ||
|
513d666ba1 | ||
|
0ef5d1f9c8 | ||
|
ac861f2b7d | ||
|
7e904c3a1a | ||
|
8b1591f263 | ||
|
d4cd789213 | ||
|
c07f85250d | ||
|
67f2533ba3 | ||
|
382d3085c4 | ||
|
10211e7565 | ||
|
19a4c6de6a | ||
|
68552c31cb | ||
|
01d9a3b02c | ||
|
664ee8d017 | ||
|
ccce83d0f0 | ||
|
9cdcfad920 | ||
|
8f3a0927c3 | ||
|
f1ae0e2e94 | ||
|
0451c42e5c | ||
|
8aee6a1dd7 | ||
|
bacf896228 | ||
|
85535ae09c | ||
|
6c18ca9cac | ||
|
ac86074869 | ||
|
ad7a7b246e | ||
|
7958ac96a8 | ||
|
112eb01728 | ||
|
267dd00a05 | ||
|
2944075f70 | ||
|
2900537ffa | ||
|
9e3423129b | ||
|
93ddf2ca37 | ||
|
09ab874642 | ||
|
fcf2ffe8db | ||
|
3e3d8527b0 | ||
|
47b5f9a4a3 | ||
|
dc1fd61adf | ||
|
356c5bfb0e | ||
|
3c4e3ddcdb | ||
|
96aa1d4b37 | ||
|
988af1c803 | ||
|
f993fdd480 | ||
|
5fd43fcf56 | ||
|
1660856bf5 | ||
|
7824ce32a0 | ||
|
f9b60b4803 | ||
|
5050b595a0 | ||
|
2dc500b11e | ||
|
5f76a65b1d | ||
|
0051ca5db6 | ||
|
32aa4e02ca | ||
|
4ec82b28f7 | ||
|
5ba84d75ce | ||
|
981f09d950 | ||
|
c0792808e2 | ||
|
0a8598ce65 | ||
|
dfaa2e481d | ||
|
25d6107a4f | ||
|
b7179f8f79 | ||
|
e5bf0c0a69 | ||
|
b37ed12df2 | ||
|
79d888223f | ||
|
601b5eb036 | ||
|
f3c9b87c30 | ||
|
b4e1403cf9 | ||
|
c17ddceeae | ||
|
df7b63cd5e | ||
|
f099fd9e28 | ||
|
d3dff61016 | ||
|
6678a95b20 | ||
|
19429c3ad7 | ||
|
54d92446db | ||
|
0949419f68 | ||
|
b53857b168 | ||
|
7d07dc66fd | ||
|
716bcad393 | ||
|
03d66abd9e | ||
|
060f40c356 | ||
|
17a5d6c1f7 | ||
|
c70face2dc | ||
|
e6493ef0c8 | ||
|
a92a5f19f0 | ||
|
14b2ed649b | ||
|
164adcd433 | ||
|
50ef91bb08 | ||
|
46f4e58782 | ||
|
c886b53a3d | ||
|
f7b18bf68c | ||
|
4e4c33fee2 | ||
|
bb7c9a544b | ||
|
b2fa7a2293 | ||
|
aae90d5d6b | ||
|
58fa142c7d | ||
|
1f0d319f9f | ||
|
0c5f4d43f9 | ||
|
00ca98348a | ||
|
d2b7a24c26 | ||
|
5037b2c5c4 | ||
|
ac4aa16962 | ||
|
aad010fd27 | ||
|
e9e075f6e8 | ||
|
a1994122ca | ||
|
74f0d32a21 | ||
|
71761bc2da | ||
|
6797dea6c3 | ||
|
05ad6a357b | ||
|
348f370b79 | ||
|
6e20a9a419 | ||
|
e09ef69a8d | ||
|
4d68abd54f | ||
|
d5fc02557c | ||
|
45d50715c5 | ||
|
22b67a5207 | ||
|
e29533df0f | ||
|
4219cc497d | ||
|
b94892d633 | ||
|
b7c5ae7bca | ||
|
73eed42b3c | ||
|
254bf4c08f | ||
|
b9f7b08af2 | ||
|
7a58f574bf | ||
|
2ad1a27b28 | ||
|
4c603990d2 | ||
|
53436766c1 | ||
|
035138dd98 | ||
|
c34a8f2e69 | ||
|
34ef37b1fe | ||
|
fbaa497dbb | ||
|
a55e101bef | ||
|
475108b784 | ||
|
a3ac94fa26 | ||
|
3e5f89f2fe | ||
|
726767ece5 | ||
|
82e453d9fe | ||
|
40884dbb1d | ||
|
884b67bea7 | ||
|
020f2d0db5 | ||
|
060de48800 | ||
|
82ace8ccc8 | ||
|
b3e8e36a8d | ||
|
2f634f8c06 | ||
|
8f1dba6aad | ||
|
fbffbf8ef0 | ||
|
c263674507 | ||
|
b89a10676a | ||
|
37c78abe14 | ||
|
534bf8f806 | ||
|
4dcccf32f8 | ||
|
3f0bae8c38 | ||
|
bf65f86a0b | ||
|
91348a6aee | ||
|
68c20c610e | ||
|
15763f8545 | ||
|
d9ef3fd505 | ||
|
b3594c984a | ||
|
6f8da1bb27 | ||
|
9a30b51e6e | ||
|
9893256668 | ||
|
ed0abb8a53 | ||
|
a8fe6a864d | ||
|
632262e484 | ||
|
f1f7f72119 | ||
|
6c3bb507b0 | ||
|
848b2f687c | ||
|
c6371bb3ef | ||
|
539e5e5f41 | ||
|
0ffc2ce183 | ||
|
b1cb38e7a0 | ||
|
2afdbb139d | ||
|
c6352c4d9d | ||
|
d9781ec46d | ||
|
58cf938aa2 | ||
|
aea4e014c3 | ||
|
2f11c44c34 | ||
|
259f87fcdc | ||
|
728b4069f5 | ||
|
7f602f41c9 | ||
|
95bbcba089 | ||
|
aaf20d2768 | ||
|
a028fb710b | ||
|
6cbbbbfd5a | ||
|
5b7eec3d02 | ||
|
c1d18c6976 | ||
|
7773a277d9 | ||
|
39d455e217 | ||
|
fdcb55873a | ||
|
6825b9d3b6 | ||
|
c64ad40a04 | ||
|
2b86e10eda | ||
|
fc903e0b0e | ||
|
7354678215 | ||
|
261475c2fd | ||
|
110fb7cd7b | ||
|
e566a450c4 | ||
|
13de959121 | ||
|
828577c3b2 | ||
|
9ddadd39f4 | ||
|
1aab1594e2 | ||
|
9c589ac7d9 | ||
|
c9cc3ae451 | ||
|
cfc1138abc | ||
|
4bfea4cebe | ||
|
e8fbd855e6 | ||
|
7f3724dc5d | ||
|
87c95c6f41 | ||
|
36714342a3 | ||
|
f2b6a2c68a | ||
|
39f28a0d0f | ||
|
d2069f7720 | ||
|
1da17be52a | ||
|
902bb7a80c | ||
|
6b0bf828f7 | ||
|
9ee2ee046a | ||
|
2bcf83d940 | ||
|
071e32985c | ||
|
acaf001cdd | ||
|
766ff38586 | ||
|
b22f6c6a99 | ||
|
43fdb00fc7 | ||
|
5424fca168 | ||
|
d8a081b94c | ||
|
21c71a0c52 | ||
|
a55f5b59f6 | ||
|
efc1c74792 | ||
|
0f75be3e8e | ||
|
a7c92a2590 | ||
|
0c6175f27e | ||
|
99721755e8 | ||
|
194dd5fd30 | ||
|
25229ffeea | ||
|
f8f971bfd6 | ||
|
4251543426 | ||
|
d1b189b2cd | ||
|
e26cba062b | ||
|
efb9128aaa | ||
|
42c262b807 | ||
|
c9414a3572 | ||
|
2adb0734d1 | ||
|
047817b6b0 | ||
|
689d8221e5 | ||
|
21f4aeff4f | ||
|
925475e5b1 | ||
|
2ba24bdd09 | ||
|
bd5745ab35 | ||
|
6eda129741 | ||
|
48ccb3ad18 | ||
|
b33e0ad539 | ||
|
801b04c057 | ||
|
04372e3300 | ||
|
bdf36bdc3d | ||
|
d815239aac | ||
|
453b65bde3 | ||
|
e15b279fb3 | ||
|
c1d40e22a7 | ||
|
a65cb91cc1 | ||
|
ecb506f13c | ||
|
7e1520b01f | ||
|
105732ab1f | ||
|
f5c1a69737 | ||
|
84494c169a | ||
|
3f4f43ab91 | ||
|
e07ad67624 | ||
|
e06867fec3 | ||
|
5a9d0c87bb | ||
|
b7fb919dfa | ||
|
492230f405 | ||
|
d844a070fd | ||
|
c3a0a22661 | ||
|
995a26f4e5 | ||
|
bdd7a64d19 | ||
|
1cbcb06157 | ||
|
4468311620 | ||
|
39cdc926a5 | ||
|
4baa65f38a | ||
|
0c48dad1da | ||
|
425df1c905 | ||
|
51438281c7 | ||
|
079dfadf1a | ||
|
3ee69f3da9 | ||
|
9e9bd2456f | ||
|
2547d4ea07 | ||
|
c1551405aa | ||
|
d94b0da3c8 | ||
|
343e124270 | ||
|
26c6ef1834 | ||
|
7e8825bc6f | ||
|
81e6a022e6 | ||
|
4e1c0589c2 | ||
|
56ee70e2a9 | ||
|
b88c3cd0d4 | ||
|
78a3cb5887 | ||
|
4df618f584 | ||
|
4eb80bf3b4 | ||
|
008879eb22 | ||
|
dce43e1a0e | ||
|
59993aea80 | ||
|
c189409ffd | ||
|
1b02fb6d6d | ||
|
a3d67e52fe | ||
|
788128f05c | ||
|
1965cc1491 | ||
|
0caaed4e1f | ||
|
9fd18a2c7f | ||
|
0ce7e613c7 | ||
|
f985125eb0 | ||
|
5a313ccb90 | ||
|
517718de02 | ||
|
6b7a4f8611 | ||
|
f2e06128c6 | ||
|
e3c8e8a839 | ||
|
a1919971e4 | ||
|
b63f8b59b2 | ||
|
8cfea958de | ||
|
c03615b982 | ||
|
1ff3dd0ef7 | ||
|
2e533f32c2 | ||
|
ff9714eb32 | ||
|
af6f45adab | ||
|
092b73d0b7 | ||
|
1a72256e05 | ||
|
5d550145bd | ||
|
013b169469 | ||
|
8444326bf3 | ||
|
7b5f282aee | ||
|
cb691694d1 | ||
|
6507804637 | ||
|
bb8750c22b | ||
|
4fed3d2014 | ||
|
d8d703af45 | ||
|
a2614956d5 | ||
|
423db23c36 | ||
|
7d58b05a51 | ||
|
a8301ca14a | ||
|
e3aed0f7bc | ||
|
28d344b4b5 | ||
|
bdaeccbca1 | ||
|
0c668d13b6 | ||
|
55b4646212 | ||
|
4d5ce0b285 | ||
|
1ad30c6858 | ||
|
7e17b8a392 | ||
|
b4bc6b4445 | ||
|
4da28efc90 | ||
|
16328732d4 | ||
|
d3139266c8 | ||
|
e54a25070c | ||
|
c190959b58 | ||
|
17ffbdabe2 | ||
|
75134ef614 | ||
|
55c1a2e525 | ||
|
053c4a2250 | ||
|
4d97909764 | ||
|
bcea4f10e6 | ||
|
022c6d1e52 | ||
|
f51ca9f398 | ||
|
22f39ff8e7 | ||
|
e48c9245ff | ||
|
50e14dbe97 | ||
|
93e3543e5e | ||
|
235b0db82b | ||
|
4612a84a67 | ||
|
74ff77d366 | ||
|
f0c7424957 | ||
|
e7ef0904f5 | ||
|
edd3699e4f | ||
|
41b7d9a4c8 | ||
|
44f117ac72 | ||
|
e35fa80745 | ||
|
806fa1a834 | ||
|
41fbc699ad | ||
|
938438364f | ||
|
b655cfe379 | ||
|
93b1795ed1 | ||
|
ed196bb980 | ||
|
eebf526657 | ||
|
331984c322 | ||
|
772026e66b | ||
|
25793aefa7 | ||
|
93914d8cd8 | ||
|
5a8ad84381 | ||
|
13f8eeb1ac | ||
|
6f5b1903c4 | ||
|
cddce5bd34 | ||
|
96bb27fa84 | ||
|
e5f7d65231 | ||
|
d999e79a8b | ||
|
31f82705e2 | ||
|
830dd68e45 | ||
|
104c6ea3f3 | ||
|
053a41d816 | ||
|
56a893cd94 | ||
|
95e213df87 | ||
|
4fec91d5b1 | ||
|
3fc8597119 | ||
|
c293e37237 | ||
|
1903cc8ac7 | ||
|
411743e6dd | ||
|
dc5321fed3 | ||
|
9b4f804fd1 | ||
|
a52eeb364b | ||
|
bcc92744c2 | ||
|
773e85309c | ||
|
4e0969bb49 | ||
|
4b2181ffa2 | ||
|
2fdb44803d | ||
|
8121d19122 | ||
|
76a73e00f4 | ||
|
b928bb3b26 | ||
|
43fd8e658d | ||
|
7a80456832 | ||
|
c4fd0a659f | ||
|
1b539a51de | ||
|
8bab4ceba4 | ||
|
9c584e6bb8 | ||
|
da663104f3 | ||
|
776bdab567 | ||
|
ccd0badeed | ||
|
776b981dde | ||
|
015af605cf | ||
|
c35afb942f | ||
|
4a2a3e3640 | ||
|
488f558d49 | ||
|
8a02e177e3 | ||
|
a378407c57 | ||
|
c995e8e2ae | ||
|
e8f2d6f32b | ||
|
955dfc6397 | ||
|
7c38300949 | ||
|
653416ab23 | ||
|
e7220fd71f | ||
|
5633049292 | ||
|
b4a28f3147 | ||
|
7ab1406962 | ||
|
8176d112fe | ||
|
d086efd33f | ||
|
7b5d15d254 | ||
|
8ec91fc42d | ||
|
84a31ca800 | ||
|
72576b3a77 | ||
|
ca22302763 | ||
|
a3000940f7 | ||
|
30a61d7505 | ||
|
bcae08470c | ||
|
bf8db6f634 | ||
|
0844c9a6ec | ||
|
2da0cb7a44 | ||
|
f8720fddd6 | ||
|
dc39d66bca | ||
|
fb56720054 | ||
|
d8e16143d4 | ||
|
292683f0d5 | ||
|
abbe7b8f65 | ||
|
19645354c7 | ||
|
1656fd7729 | ||
|
bd49b2ad63 | ||
|
6d29525ef0 | ||
|
9c3f38a564 | ||
|
0fb90dcdb3 | ||
|
f9e4d5d28e | ||
|
0960e654ab | ||
|
78fea79c06 | ||
|
a4dccec356 | ||
|
acbb174100 | ||
|
84c968ab36 | ||
|
0510424e1c | ||
|
af1c7a9573 | ||
|
00a75bda3b | ||
|
49dbd85a89 | ||
|
9d058f9193 | ||
|
11fcbe52a6 | ||
|
bd0596e711 | ||
|
ba77303ee4 | ||
|
c2ced75d5a | ||
|
424127012f | ||
|
eca1ce7f1f | ||
|
6d51c065d3 | ||
|
124cb9cca3 | ||
|
3c70a61132 | ||
|
8426785286 | ||
|
ca89e9aec0 | ||
|
a56dbdb0f3 | ||
|
dedf56bf31 | ||
|
d813d29290 | ||
|
fc41360ed9 | ||
|
ad1df0ebeb | ||
|
34f5218a0d | ||
|
dd946fff1f | ||
|
5c196d70bd | ||
|
a093d45064 | ||
|
3f1005dab1 | ||
|
3060ee9397 | ||
|
ac8a164709 | ||
|
7371fd2647 | ||
|
f4f59a7557 | ||
|
ba69dcc74c | ||
|
2c719bdb3f | ||
|
712054e128 | ||
|
bf152b94b5 | ||
|
bdb3da4504 | ||
|
392175d6e8 | ||
|
66c02e4dc7 | ||
|
5e12eb8d34 | ||
|
42cbf2071b | ||
|
295cbe7b9c | ||
|
80ba105be5 | ||
|
b732fe819e | ||
|
88cf6777d0 | ||
|
ebf5104923 | ||
|
d84d3777fd | ||
|
d2b47dc270 | ||
|
d469c14d11 | ||
|
ad9e063a5c | ||
|
12b3e79661 | ||
|
c87a533b3b | ||
|
34e39248fc | ||
|
2dc1dc8ec2 | ||
|
888388cf89 | ||
|
a0da4f6d30 | ||
|
2558f101af | ||
|
209df75ffa | ||
|
35901cc955 | ||
|
bce90a3340 | ||
|
47c4a2d21a | ||
|
128a08e0a4 | ||
|
612e70a64b | ||
|
7a44d99b96 | ||
|
0543e8fbc1 | ||
|
b88a5700f6 | ||
|
b29d66f34b | ||
|
b2fc017671 | ||
|
b10c23c233 | ||
|
cb9b526f0c | ||
|
d8c454b097 | ||
|
e9f889d57e | ||
|
d005f4edd3 | ||
|
d9912cb23c | ||
|
cf445861f1 | ||
|
6b69cc0b39 | ||
|
3003517430 | ||
|
a3c4ea51f0 | ||
|
dc54d290fc | ||
|
8637bdeb30 | ||
|
f3c6d91e69 | ||
|
7485cb2a50 | ||
|
8a90441d2f | ||
|
37189e1f9d | ||
|
f47f263655 | ||
|
430ed061c1 | ||
|
2cca6a1be8 | ||
|
adebe1bd4e | ||
|
ed9430f3a6 | ||
|
6416737e70 | ||
|
7d23076a0e | ||
|
ebd6d84326 | ||
|
1584789916 | ||
|
e22a50a3e3 | ||
|
29052b36a5 | ||
|
c82233c435 | ||
|
142d9cb63f | ||
|
9198fa4ec9 | ||
|
dbffa66b6d | ||
|
840210a0ba | ||
|
e38496ddc0 | ||
|
0779d343fa | ||
|
046ecc5d50 | ||
|
fc7639ed39 | ||
|
9059c4d223 | ||
|
c6cde1cf5c | ||
|
389c46b4e5 | ||
|
30834b7dd8 | ||
|
2be3065249 | ||
|
3afb299aec | ||
|
43bedc0909 | ||
|
728c601ff4 | ||
|
5d31c57638 | ||
|
f2173bc2a7 | ||
|
35b904b184 | ||
|
057ee6633a | ||
|
9861cdfeb6 | ||
|
9ba7b4fcbb | ||
|
d7aa5fcc38 | ||
|
bdc4876948 | ||
|
6c7725d21f | ||
|
372cb20f9e | ||
|
022e926d64 | ||
|
9c776f4fcf | ||
|
8b5a86b992 | ||
|
41a5f6a49d | ||
|
903bce9bdd | ||
|
10786375db | ||
|
25a8dbef11 | ||
|
448303b3f0 | ||
|
85b2627191 | ||
|
79bcb4af47 | ||
|
3231306736 | ||
|
29dce7c9b9 | ||
|
32f24bff8d | ||
|
930e65d1be | ||
|
008722b809 | ||
|
a4c80089f3 | ||
|
625388ccf0 | ||
|
e0c8492464 | ||
|
401ac7702d | ||
|
4105fb683b | ||
|
a63eae48cc | ||
|
111eda604b | ||
|
c8cee5d900 | ||
|
012bb7b6c7 | ||
|
aea074e744 | ||
|
7a22d47338 | ||
|
87851b0db0 | ||
|
f18ce4f923 | ||
|
d75ff80eb2 | ||
|
bf6f5e7bc5 | ||
|
10b045f91b | ||
|
ba04ec7e85 | ||
|
a8718c9051 | ||
|
69da22d998 | ||
|
67981725ec | ||
|
8c30c36006 | ||
|
501ae0c3d6 | ||
|
e8c602ec58 | ||
|
d54fb5f40d | ||
|
0fd6ff722b | ||
|
647103a4e0 | ||
|
d815e5a299 | ||
|
626e191201 | ||
|
81271b0d20 | ||
|
0a5221a819 | ||
|
fdf3114f59 | ||
|
5dd646f33f | ||
|
c9b75a8a65 | ||
|
8a95526ced | ||
|
b245c661da | ||
|
e7746809d2 | ||
|
76c6cf8c3a | ||
|
08e315e962 | ||
|
af78800525 | ||
|
e5b3d39ce9 | ||
|
1054d63820 | ||
|
ce187b0899 | ||
|
7febf724f3 | ||
|
9ccb3a2ad1 | ||
|
cd118bbc7e | ||
|
b8a45d889e | ||
|
ba6ef745fe | ||
|
a6ca95dfc4 | ||
|
8a1794caa3 | ||
|
ee901ac8b1 | ||
|
982a70431f | ||
|
d6217eb862 | ||
|
5d5b1afd2d | ||
|
e92e982342 | ||
|
8a11c94a2d | ||
|
b5e65a2ea0 | ||
|
33fb750275 | ||
|
e9900b2bdf | ||
|
27f3919259 | ||
|
914ae88c62 | ||
|
149132348f | ||
|
9c8eeaed85 | ||
|
a0b0ccf972 | ||
|
e0ec288928 | ||
|
c9d54f7854 | ||
|
d2aa81858c | ||
|
711b0fda8b | ||
|
97766102df | ||
|
c3aa7909ce | ||
|
15d4b71e2e | ||
|
9f8cc925b8 | ||
|
b7127a395b | ||
|
2a39852e91 | ||
|
88368996fb | ||
|
0d48f47eba | ||
|
05c4855cc9 | ||
|
dfddb0a636 | ||
|
14bfa74069 | ||
|
354109e5e9 | ||
|
536de666df | ||
|
de9cefff8c | ||
|
be9b6a6dee | ||
|
8711fe7bae | ||
|
a86faecff5 | ||
|
a1d99adce7 | ||
|
8f0ccdf1c9 | ||
|
b90eeee1ec | ||
|
e6be407e34 | ||
|
63768858ff | ||
|
9bfea2adba | ||
|
37d2a831d3 | ||
|
38aa44ef31 | ||
|
7d12e2019d | ||
|
cae718f184 | ||
|
d7c2577ffb | ||
|
ba46dc83c6 | ||
|
7026a8c574 | ||
|
5b8997754c | ||
|
9423f8e31c | ||
|
630968b632 | ||
|
a1ca8c262a | ||
|
a1ff44a230 | ||
|
b218c2a70e | ||
|
467694c1fa | ||
|
b2939cdf19 | ||
|
838dd6d273 | ||
|
46d0ecdfe8 | ||
|
e827e36429 | ||
|
78e20c82a8 | ||
|
3544688b35 | ||
|
2a1a55af8a | ||
|
5a4a357afc | ||
|
3acc6fe080 | ||
|
b9aecabdb7 | ||
|
fcf31cedbc | ||
|
1d07d0a532 | ||
|
a0dbcde063 | ||
|
0ddb242583 | ||
|
1cecf0f142 | ||
|
1dc741b963 | ||
|
b5645848d6 | ||
|
ead356971c | ||
|
a2fd901bec | ||
|
7cbf031f18 | ||
|
0914037bed | ||
|
ef77496897 | ||
|
a39c7096c0 | ||
|
6d22634e56 | ||
|
75bdd2c62c | ||
|
bc74abae41 | ||
|
441dca0eaf | ||
|
5c2e7a169f | ||
|
7e6f30caab | ||
|
e603237010 | ||
|
67f0b1bbca | ||
|
9d32857c37 | ||
|
7ddcbd0d3a | ||
|
b3a4b78c6d | ||
|
978b37379b | ||
|
1b18f8b896 | ||
|
3bcb524d33 | ||
|
82ee496efc | ||
|
76aebac568 | ||
|
027d5e90c5 | ||
|
5fb683e788 | ||
|
9e48b2f17f | ||
|
fc1c146d31 | ||
|
15e0304d95 | ||
|
be0604dcf0 | ||
|
9da4b58c8e | ||
|
55b875f6c9 | ||
|
51b39901b9 | ||
|
18f90754da | ||
|
2a0b23df46 | ||
|
18233654d0 | ||
|
cc54dddd84 | ||
|
950d592279 | ||
|
a4a25c3e14 | ||
|
2507c90381 | ||
|
ff6c81b0c3 | ||
|
460249704e | ||
|
1adc40f014 | ||
|
c9b801efde | ||
|
f4c5a45f4f | ||
|
412dc8dcda | ||
|
d8d0492d64 | ||
|
671ceccd78 | ||
|
36b43a6d7a | ||
|
f80bab58c0 | ||
|
9e06ab45bf | ||
|
9de1bd44e2 | ||
|
32c6b81b3a | ||
|
fb7bfbe9ab | ||
|
0fa4fdcff9 | ||
|
2bac6de6c6 | ||
|
1859faccb7 | ||
|
ebfa4b746d | ||
|
557aca2aa7 | ||
|
60de598cb5 | ||
|
cfeb87c1c9 | ||
|
8e4d6fa045 | ||
|
94dfff1c4e | ||
|
a3c2d49f0c | ||
|
6dd69b9bff | ||
|
408af032a0 | ||
|
5254d77b7c | ||
|
6689913f77 | ||
|
e1f33e0f6b | ||
|
8ed4933005 | ||
|
3d935a957c | ||
|
e59ff6e6db | ||
|
0ff3a7da05 | ||
|
3c91c9c927 | ||
|
93ef3795b4 | ||
|
d2454142da | ||
|
53ec6ab45e | ||
|
badc0eff0d | ||
|
83424381cd | ||
|
c476f0eca0 | ||
|
2fe128d8e9 | ||
|
f5f320d5fc | ||
|
f694d01a1b | ||
|
380231af9d | ||
|
b47535d8bb | ||
|
d723dbfef7 | ||
|
196ec78261 | ||
|
c26f5c6ee3 | ||
|
ce95973c22 | ||
|
ba7e78bc65 | ||
|
c21247abf9 | ||
|
44ef8baff1 | ||
|
67fba69b0c | ||
|
bcb142e68e | ||
|
dac543891f | ||
|
d9439e819e | ||
|
9ba860b4f2 | ||
|
02a79b677b | ||
|
d5f2eb3790 | ||
|
ee3b4f3ee9 | ||
|
3079cf803c | ||
|
9ea4f50c1b | ||
|
250a2b5164 | ||
|
a0ca502f16 | ||
|
4af1061974 | ||
|
a9391e0499 | ||
|
7a2527aed1 | ||
|
6bfcebe6da | ||
|
1804d6c9ce | ||
|
979fae0406 | ||
|
586ce70b8b | ||
|
a8113ae296 | ||
|
4ff25d0366 | ||
|
86536059b4 | ||
|
fc4b6d8f5e | ||
|
788b98d041 | ||
|
815388b023 | ||
|
6de6b0ed31 | ||
|
e7c5bc0cee | ||
|
901100bebf | ||
|
7d0d4b5eec | ||
|
7db1dc2434 | ||
|
5875eea1b5 | ||
|
9a9dd158c4 | ||
|
b32f54e775 | ||
|
11f896809f | ||
|
98cab354bd | ||
|
90def916bc | ||
|
b961093c14 | ||
|
0e2c509a24 | ||
|
524ef509fe | ||
|
fa59573a65 | ||
|
c21a05a3b1 | ||
|
2453ab2a21 | ||
|
84f7a779c4 | ||
|
f52e9d143b | ||
|
e542ff0e62 | ||
|
92dc3637df | ||
|
0fddf9a610 | ||
|
c740d1b093 | ||
|
333b8ef668 | ||
|
4c1e80032a | ||
|
e13de3e629 | ||
|
74007e7f00 | ||
|
24917f2e9b | ||
|
d96624490e | ||
|
779ed37cfa | ||
|
05be16e051 | ||
|
22d8de73e9 | ||
|
e8742f1dd5 | ||
|
307799113e | ||
|
339fa86373 | ||
|
c8cbf39a16 | ||
|
c4d3578de3 | ||
|
473f2650fd | ||
|
ec3f6434a6 | ||
|
623e3f3c9f | ||
|
bc18eb1d41 | ||
|
846dfb2fe4 | ||
|
636e54389b | ||
|
f930502312 | ||
|
b4d5c7e68a | ||
|
45227e84fc | ||
|
84d0b541e3 | ||
|
212ee015d4 | ||
|
bea328b8b6 | ||
|
43f25eb4a5 | ||
|
13af91869d | ||
|
7859a441ac | ||
|
b80de69932 | ||
|
4b6ae10fb7 | ||
|
d11d1313d9 | ||
|
1f41a65c88 | ||
|
cee29483cf | ||
|
20bbc375a5 | ||
|
53e99c0a55 | ||
|
682235af80 | ||
|
07adda84da | ||
|
48a3975ec0 | ||
|
0191bb593a | ||
|
48618adf13 | ||
|
36f54e689b | ||
|
5696ad5490 | ||
|
c9c842ae2b | ||
|
18e2118fc8 | ||
|
34bf5412b1 | ||
|
06f25115d8 | ||
|
1303f8df20 | ||
|
72db5d5a48 | ||
|
c59da02936 | ||
|
16b7737da3 | ||
|
5a47ac96de | ||
|
1ba037201c | ||
|
7523282e48 | ||
|
60d3c17b6b | ||
|
e609e10229 | ||
|
31556e45d8 | ||
|
81b99782b9 | ||
|
df5475996f | ||
|
a3b38d8402 | ||
|
b827efb461 | ||
|
c5b2cfd777 | ||
|
8f915da2ca | ||
|
3190c3ffeb | ||
|
d0f71881f4 | ||
|
82ea61a3cc | ||
|
30e5b5fe55 | ||
|
4f022d3428 | ||
|
d8c8fc1f03 | ||
|
6781345b9b | ||
|
ba243416e3 | ||
|
990d7fb37d | ||
|
654c3240e4 | ||
|
42f1d2a33b | ||
|
b726cd4b3e | ||
|
e908da7831 | ||
|
209dd6dd37 | ||
|
83b127cec1 | ||
|
01927fb470 | ||
|
2889de6d90 | ||
|
f3033207b9 | ||
|
7a2d93ed6e | ||
|
8a60ab7314 | ||
|
d828eba2f6 | ||
|
c4e50a34d3 | ||
|
6b1d724f5c | ||
|
5fdc2e5161 | ||
|
f3ecb05762 | ||
|
d302fae31b | ||
|
10d3d94a2a | ||
|
b3b1b47fb0 | ||
|
053778fdd7 | ||
|
02f09e2473 | ||
|
8a6158c663 | ||
|
10fb3539ae | ||
|
ed0292629b | ||
|
d581ad82ca | ||
|
a61eebd9e3 | ||
|
9bad4003c6 | ||
|
0bfcd2294e | ||
|
19bdf54f33 | ||
|
1d15d72775 | ||
|
a694c3cafd | ||
|
d60b697cc5 | ||
|
33a4d12101 | ||
|
1597511de0 | ||
|
e39ebb7358 | ||
|
fe3270d2b9 | ||
|
28c38a44c8 | ||
|
20b6d08ef7 | ||
|
b2aeffd947 | ||
|
cd8f4dfac0 | ||
|
16967d6761 | ||
|
478674a6d6 | ||
|
0932a27647 | ||
|
7aed0a2a66 | ||
|
d28527b629 | ||
|
6035304619 | ||
|
5ef86b6d22 | ||
|
681368b937 | ||
|
e4946dd182 | ||
|
ccdf893af7 | ||
|
604364eb65 | ||
|
f1f60bc9bb | ||
|
51950ad352 | ||
|
d8c5ad2a26 | ||
|
0774bd7df0 | ||
|
61c8247403 | ||
|
191d8101a3 | ||
|
f0538293de | ||
|
698aeddc85 | ||
|
33ec86aa00 | ||
|
f43668a7ba | ||
|
d8e5e2d960 | ||
|
05c774bcbe | ||
|
723fb60476 | ||
|
5f3c8be6d1 | ||
|
b79d63e9c3 | ||
|
65a9ee476c | ||
|
5a56925495 | ||
|
d9e8380981 | ||
|
c43a00fd60 | ||
|
3ef0d6016c | ||
|
e159f052bb | ||
|
a5b533ba64 | ||
|
b3c8e56913 | ||
|
bddf23894d | ||
|
1890c30ee1 | ||
|
3aa490edbe | ||
|
e978158519 | ||
|
731f76cea1 | ||
|
1e9d0718eb | ||
|
dd0126ff1b | ||
|
7fb674cc58 | ||
|
91b90460ee | ||
|
8ffc437e70 | ||
|
68a06abb79 | ||
|
adf27aae4c | ||
|
6a9e6503b7 | ||
|
c679920c01 | ||
|
e06668c7e8 | ||
|
58a6dafa68 | ||
|
d00b889402 | ||
|
10e8e2acac | ||
|
9c224ccdd3 | ||
|
7b930124d9 | ||
|
38a6003472 | ||
|
beb80219a8 | ||
|
3bb0956046 | ||
|
00826486bd | ||
|
38f81d5d20 | ||
|
c2e9f5eb0a | ||
|
3a7788217e | ||
|
e57a932692 | ||
|
ad35e371fe | ||
|
bd1e96cf63 | ||
|
1335dfd295 | ||
|
ff68757358 | ||
|
7bc23a8f85 | ||
|
8343ed6ceb | ||
|
a8b95ff801 | ||
|
1ec9097f55 | ||
|
a2c0ec207e | ||
|
e2838a49db | ||
|
0dffb58797 | ||
|
6f569719c9 | ||
|
9f471a3e7d | ||
|
882cf224f8 | ||
|
189e5e1fbd | ||
|
6afbc2726f | ||
|
08daefa4a3 | ||
|
043baa2af8 | ||
|
64960383e4 | ||
|
6b637428a7 | ||
|
2fb1e51d71 | ||
|
46acb608b1 | ||
|
67d209ade9 | ||
|
60d3e6b49b | ||
|
b79213d260 | ||
|
d36dc5e2d1 | ||
|
1abb1447df | ||
|
9afb1c3459 | ||
|
ba640bf89d | ||
|
3258ce1fa4 | ||
|
3e3eb2aa7f | ||
|
eae9251cb7 | ||
|
cfe603f342 | ||
|
0975bbb39e | ||
|
767acfa2ac | ||
|
d8804a2474 | ||
|
541bc0afda | ||
|
d5f53164b4 | ||
|
fb408316f1 | ||
|
10a21ed949 | ||
|
99506b486a | ||
|
61e0c2693d | ||
|
8470affb5b | ||
|
63614f28fe | ||
|
1d1e209d15 | ||
|
6b99b53584 | ||
|
91b02373dc | ||
|
0670976cb1 | ||
|
b847a100a3 | ||
|
2e04cdbe5e | ||
|
a7c33e12cb | ||
|
c1d1378125 | ||
|
79e590f50f | ||
|
d95364b94f | ||
|
2fa77d9d54 | ||
|
ededdc512b | ||
|
68d81cb25b | ||
|
82b9f2a3db | ||
|
3e860fbc39 | ||
|
55362263dd | ||
|
89c278d1e5 | ||
|
5759d5a762 | ||
|
a394778cde | ||
|
9d398ee335 | ||
|
5d291eb52c | ||
|
209dd5db67 | ||
|
9e4058efd3 | ||
|
74ee41912a | ||
|
86801d350a | ||
|
f37e4df471 | ||
|
6546fb3f1d | ||
|
51f8ac91ec | ||
|
a172ac264d | ||
|
b615f0df4d | ||
|
c6ad12d058 | ||
|
3a9c81ed8c | ||
|
91e4784b92 | ||
|
6c1a96174e | ||
|
7ae8907b3f | ||
|
d7a0bd4a6c | ||
|
f69a4f11e3 | ||
|
c0fd8660d2 | ||
|
f3e529c694 | ||
|
eb4d279bbc | ||
|
cc33e4cd0c | ||
|
3091412806 | ||
|
f961408782 | ||
|
51aa313833 | ||
|
5a539d44b0 | ||
|
8cf93a25c5 | ||
|
27d79352da | ||
|
aa4e9cec2d | ||
|
b666a6bd6c | ||
|
44cbcdf00c | ||
|
2505a2f3cc | ||
|
a3460d1ef6 | ||
|
c69c40a55a | ||
|
fd0b249a29 | ||
|
f386c64759 | ||
|
08e44aac0d | ||
|
eda138343b | ||
|
6d88210110 | ||
|
ea26372083 | ||
|
aff84cd1e9 | ||
|
546fd5d60e | ||
|
47e464528c | ||
|
ab7d0de782 | ||
|
d9958610a4 | ||
|
71174d53cb | ||
|
f1c65975ad | ||
|
67c3593b39 | ||
|
ae6fc0a471 | ||
|
75ead3379d | ||
|
a2371a96b7 | ||
|
8ab26df9f0 | ||
|
95ba947f13 | ||
|
11ffd6b370 | ||
|
6aee656f6b | ||
|
f8882db8a0 | ||
|
d3138d486e | ||
|
5f69452d48 | ||
|
7fafcd438e | ||
|
2d140bae3e | ||
|
20c41f3d3e | ||
|
33c6390c14 | ||
|
766bdbfe89 | ||
|
41c0e9ba47 | ||
|
a942732d83 | ||
|
3eb2b2be5f | ||
|
a86e7fbd9f | ||
|
c90085b28e | ||
|
394cd4c6b6 | ||
|
ee9d321d9f | ||
|
2fe78ffe21 | ||
|
d86dd165da | ||
|
710095920f | ||
|
8e639543fb | ||
|
8f8ceab062 | ||
|
cd76d4c308 | ||
|
4b38260a27 | ||
|
630dd8ac1b | ||
|
1854ddbcd0 | ||
|
31e328c544 | ||
|
1fff890b71 | ||
|
ea98a44db3 | ||
|
ced4d81856 | ||
|
3133998af9 | ||
|
e55fc144b9 | ||
|
97062c4017 | ||
|
de6c08a842 | ||
|
b667dff4bc | ||
|
c7515a853c | ||
|
3de1289d3e | ||
|
754795c0b7 | ||
|
0a171bf9bf | ||
|
a49ba8c577 | ||
|
39e5c61d4a | ||
|
500fcbb5e3 | ||
|
f7f071b52f | ||
|
8981b65bbc | ||
|
b46fb5eaad | ||
|
fe8d400e3a | ||
|
fba5b329ab | ||
|
11c595a33e | ||
|
c4b197cd4f | ||
|
96bd1328a5 | ||
|
1657f0d161 | ||
|
7121903fc5 | ||
|
77473c2a6a | ||
|
b46aeaf00b | ||
|
c1aed13afe | ||
|
4f91a564ef | ||
|
8f5cbb5c8c | ||
|
3ae8e79c5a | ||
|
1444fb8870 | ||
|
89eebad14f | ||
|
54dd7f0a43 | ||
|
614624d232 | ||
|
67fa755158 | ||
|
5a9f00dae4 | ||
|
4c8f439ece | ||
|
96284bd336 | ||
|
08529507c8 | ||
|
53201c5411 | ||
|
bb959acbaf | ||
|
e79e0018f7 | ||
|
6a89446e80 | ||
|
c08a3833e2 | ||
|
453c35d0f7 | ||
|
e7bb833a37 | ||
|
f729840040 | ||
|
f812b76c3b | ||
|
c0a8f0a66e | ||
|
fb54ea82b2 | ||
|
ef8ea330fa | ||
|
253c034d90 | ||
|
2c06615909 | ||
|
fe931d9b66 | ||
|
349a9fe75a | ||
|
6c0dee5471 | ||
|
2de33e413f | ||
|
ce4088c9ed | ||
|
502243bb75 | ||
|
19db3088df | ||
|
f4a81b743e | ||
|
ff253fb1c7 | ||
|
6e15d4efc7 | ||
|
90491692b7 | ||
|
c73eb25c83 | ||
|
ed973ebc71 | ||
|
d039ef871b | ||
|
5ec9eb819b | ||
|
1789cbea30 | ||
|
70a426c7ce | ||
|
1d11fc578e | ||
|
d5e1e8e5e3 | ||
|
aa3682f9fc | ||
|
8c8cb725bf | ||
|
5c8b774628 | ||
|
10570d508f | ||
|
106e5c3ed9 | ||
|
e88998e6d4 | ||
|
6179bba2bd | ||
|
17be6e9165 | ||
|
33c2bbf0f1 | ||
|
95781c913d | ||
|
bec0336092 | ||
|
cd4d447d90 | ||
|
ff9a6c5ced | ||
|
70c16ddbcc | ||
|
e802be0fde | ||
|
39dc5f2691 | ||
|
226a2826b5 | ||
|
592dd290b2 | ||
|
881e046c37 | ||
|
2e89ffb0e2 | ||
|
9b6b842f00 | ||
|
0e2f360177 | ||
|
d05f731c1c | ||
|
b66304e167 | ||
|
fa6f642c9c | ||
|
e1113c7011 | ||
|
ab24cc2535 | ||
|
c26768ea63 | ||
|
c8a2081526 | ||
|
ffa4daf936 | ||
|
18a76c54de | ||
|
eb2269c614 | ||
|
dfadbcd3bc | ||
|
88f50b97fd | ||
|
bf3f7d93d0 | ||
|
0878d86706 | ||
|
1e12b13a56 | ||
|
06f8d0af7e | ||
|
35e9e5928f | ||
|
b4f2e09b3a | ||
|
82b808e620 | ||
|
659c9dde00 | ||
|
f9a7451167 | ||
|
92a7f04149 | ||
|
6b6001c46c | ||
|
ff60055fa4 | ||
|
79d5e36ca3 | ||
|
df743e8948 | ||
|
af1acf9239 | ||
|
2cf46f0122 | ||
|
0cf4cddc5b | ||
|
32232377f7 | ||
|
0e854288ee | ||
|
7252861785 | ||
|
2fd0cfe708 | ||
|
55afadc286 | ||
|
dc8f893494 | ||
|
56ecd227ff | ||
|
50e3f85557 | ||
|
652477daea | ||
|
207d7a8ee6 | ||
|
f9d867e138 | ||
|
e14a35b490 | ||
|
bf77158dbc | ||
|
db51d40f4e |
@@ -1,7 +1,86 @@
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/continuous-integration.html for
|
||||
# high-level documentation on our CircleCI setup.
|
||||
# See CircleCI upstream's docs on this config format:
|
||||
# https://circleci.com/docs/2.0/language-python/
|
||||
#
|
||||
version: 2
|
||||
aliases:
|
||||
- &create_cache_directories
|
||||
run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- &restore_cache_package_json
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- &restore_cache_requirements
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- &install_dependencies
|
||||
run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
# Install moreutils so we can use `ts` and `mispipe` in the following.
|
||||
sudo apt-get install -y moreutils
|
||||
|
||||
# CircleCI sets the following in Git config at clone time:
|
||||
# url.ssh://git@github.com.insteadOf https://github.com
|
||||
# This breaks the Git clones in the NVM `install.sh` we run
|
||||
# in `install-node`.
|
||||
# TODO: figure out why that breaks, and whether we want it.
|
||||
# (Is it an optimization?)
|
||||
rm -f /home/circleci/.gitconfig
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
mispipe "tools/ci/setup-backend" ts
|
||||
|
||||
# Cleaning caches is mostly unnecessary in Circle, because
|
||||
# most builds don't get to write to the cache.
|
||||
# mispipe "scripts/lib/clean-unused-caches --verbose --threshold 0" ts
|
||||
|
||||
- &save_cache_package_json
|
||||
save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- &save_cache_requirements
|
||||
save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
|
||||
|
||||
- &run_backend_tests
|
||||
run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/ci/backend ts
|
||||
|
||||
- &run_frontend_tests
|
||||
run:
|
||||
name: run frontend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/ci/frontend ts
|
||||
|
||||
- &upload_coverage_report
|
||||
run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
jobs:
|
||||
"trusty-python-3.4":
|
||||
docker:
|
||||
@@ -13,71 +92,15 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
# Install moreutils so we can use `ts` and `mispipe` in the following.
|
||||
sudo apt-get install -y moreutils
|
||||
|
||||
# CircleCI sets the following in Git config at clone time:
|
||||
# url.ssh://git@github.com.insteadOf https://github.com
|
||||
# This breaks the Git clones in the NVM `install.sh` we run
|
||||
# in `install-node`.
|
||||
# TODO: figure out why that breaks, and whether we want it.
|
||||
# (Is it an optimization?)
|
||||
rm -f /home/circleci/.gitconfig
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
# Cleaning caches is mostly unnecessary in Circle, because
|
||||
# most builds don't get to write to the cache.
|
||||
# mispipe "scripts/lib/clean-unused-caches --verbose --threshold 0" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
|
||||
|
||||
# The moment of truth! Run the tests.
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: run frontend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/frontend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
- *create_cache_directories
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *run_frontend_tests
|
||||
- *upload_coverage_report
|
||||
|
||||
# - store_artifacts: # TODO
|
||||
# path: var/casper/
|
||||
@@ -93,50 +116,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y moreutils
|
||||
rm -f /home/circleci/.gitconfig
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
- *create_cache_directories
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *upload_coverage_report
|
||||
|
||||
"bionic-python-3.6":
|
||||
docker:
|
||||
@@ -148,53 +135,22 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- *create_cache_directories
|
||||
|
||||
- run:
|
||||
name: create cache directories
|
||||
name: do Bionic hack
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
# Temporary hack till `sudo service redis-server start` gets fixes in Bionic. See
|
||||
# https://chat.zulip.org/#narrow/stream/3-backend/topic/Ubuntu.20bionic.20CircleCI
|
||||
redis-server --daemonize yes
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.bionic-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.bionic-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y moreutils
|
||||
rm -f /home/circleci/.gitconfig
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.bionic-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.bionic-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *upload_coverage_report
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
@@ -26,11 +26,11 @@
|
||||
"_": false,
|
||||
"activity": false,
|
||||
"admin": false,
|
||||
"admin_sections": false,
|
||||
"alert_words": false,
|
||||
"alert_words_ui": false,
|
||||
"attachments_ui": false,
|
||||
"avatar": false,
|
||||
"billing": false,
|
||||
"blueslip": false,
|
||||
"bot_data": false,
|
||||
"bridge": false,
|
||||
@@ -38,6 +38,7 @@
|
||||
"buddy_list": false,
|
||||
"channel": false,
|
||||
"click_handlers": false,
|
||||
"color_data": false,
|
||||
"colorspace": false,
|
||||
"common": false,
|
||||
"components": false,
|
||||
@@ -60,12 +61,14 @@
|
||||
"emoji_picker": false,
|
||||
"favicon": false,
|
||||
"feature_flags": false,
|
||||
"feedback_widget": false,
|
||||
"fenced_code": false,
|
||||
"flatpickr": false,
|
||||
"floating_recipient_bar": false,
|
||||
"gear_menu": false,
|
||||
"hash_util": false,
|
||||
"hashchange": false,
|
||||
"helpers": false,
|
||||
"home_msg_list": false,
|
||||
"hotspots": false,
|
||||
"i18n": false,
|
||||
@@ -117,6 +120,7 @@
|
||||
"pygments_data": false,
|
||||
"reactions": false,
|
||||
"realm_icon": false,
|
||||
"realm_logo": false,
|
||||
"recent_senders": false,
|
||||
"reload": false,
|
||||
"reload_state": false,
|
||||
@@ -141,7 +145,7 @@
|
||||
"settings_bots": false,
|
||||
"settings_display": false,
|
||||
"settings_emoji": false,
|
||||
"settings_filters": false,
|
||||
"settings_linkifiers": false,
|
||||
"settings_invites": false,
|
||||
"settings_muting": false,
|
||||
"settings_notifications": false,
|
||||
@@ -164,6 +168,7 @@
|
||||
"stream_muting": false,
|
||||
"stream_popover": false,
|
||||
"stream_sort": false,
|
||||
"StripeCheckout": false,
|
||||
"submessage": false,
|
||||
"subs": false,
|
||||
"tab_bar": false,
|
||||
@@ -185,19 +190,23 @@
|
||||
"typing_events": false,
|
||||
"typing_status": false,
|
||||
"ui": false,
|
||||
"ui_init": false,
|
||||
"ui_report": false,
|
||||
"ui_util": false,
|
||||
"unread": false,
|
||||
"unread_ops": false,
|
||||
"unread_ui": false,
|
||||
"upgrade": false,
|
||||
"upload": false,
|
||||
"upload_widget": false,
|
||||
"user_events": false,
|
||||
"user_groups": false,
|
||||
"user_pill": false,
|
||||
"user_search": false,
|
||||
"user_status": false,
|
||||
"user_status_ui": false,
|
||||
"util": false,
|
||||
"voting_widget": false,
|
||||
"poll_widget": false,
|
||||
"widgetize": false,
|
||||
"zcommand": false,
|
||||
"zform": false,
|
||||
@@ -222,6 +231,12 @@
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"comma-spacing": [ "error",
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"complexity": [ 0, 4 ],
|
||||
"curly": 2,
|
||||
"dot-notation": [ "error", { "allowKeywords": true } ],
|
||||
@@ -239,6 +254,12 @@
|
||||
"FunctionExpression": {"parameters": "first"},
|
||||
"FunctionDeclaration": {"parameters": "first"}
|
||||
}],
|
||||
"key-spacing": [ "error",
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [ "error",
|
||||
{
|
||||
"before": true,
|
||||
@@ -365,6 +386,7 @@
|
||||
"quotes": [ 0, "single" ],
|
||||
"radix": 2,
|
||||
"semi": 2,
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": [ "error",
|
||||
{
|
||||
|
@@ -62,5 +62,6 @@
|
||||
|
||||
# Limit language features
|
||||
"color-no-hex": true,
|
||||
"color-named": "never",
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/travis.html for
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/continuous-integration.html for
|
||||
# high-level documentation on our Travis CI setup.
|
||||
dist: trusty
|
||||
group: deprecated-2017Q4
|
||||
@@ -15,7 +15,7 @@ install:
|
||||
- mispipe "pip install codecov" ts || mispipe "pip install codecov" ts
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
- mispipe "tools/travis/setup-$TEST_SUITE" ts
|
||||
- mispipe "tools/ci/setup-$TEST_SUITE" ts
|
||||
|
||||
# Clean any caches that are not in use to avoid our cache
|
||||
# becoming huge.
|
||||
@@ -26,7 +26,7 @@ script:
|
||||
# broken running their system puppet with Ruby. See
|
||||
# https://travis-ci.org/zulip/zulip/jobs/240120991 for an example traceback.
|
||||
- unset GEM_PATH
|
||||
- mispipe "./tools/travis/$TEST_SUITE" ts
|
||||
- mispipe "./tools/ci/$TEST_SUITE" ts
|
||||
cache:
|
||||
yarn: true
|
||||
apt: false
|
||||
@@ -38,7 +38,7 @@ cache:
|
||||
- $HOME/misc
|
||||
env:
|
||||
global:
|
||||
- BOTO_CONFIG=/tmp/nowhere
|
||||
- BOTO_CONFIG=/nonexistent
|
||||
language: python
|
||||
# Our test suites generally run on Python 3.4, the version in
|
||||
# Ubuntu 14.04 trusty, which is the oldest OS release we support.
|
||||
|
@@ -69,10 +69,11 @@ to help.
|
||||
if you run into any troubles.
|
||||
* Read the
|
||||
[Zulip guide to Git](https://zulip.readthedocs.io/en/latest/git/index.html)
|
||||
and do the Git tutorial (coming soon) if you are unfamiliar with Git,
|
||||
getting help in
|
||||
[#git help](https://chat.zulip.org/#narrow/stream/44-git-help) if you run
|
||||
into any troubles.
|
||||
and do the Git tutorial (coming soon) if you are unfamiliar with
|
||||
Git, getting help in
|
||||
[#git help](https://chat.zulip.org/#narrow/stream/44-git-help) if
|
||||
you run into any troubles. Be sure to check out the
|
||||
[extremely useful Zulip-specific tools page](https://zulip.readthedocs.io/en/latest/git/zulip-tools.html).
|
||||
* Sign the
|
||||
[Dropbox Contributor License Agreement](https://opensource.dropbox.com/cla/).
|
||||
|
||||
|
@@ -11,7 +11,7 @@ RUN useradd -d /home/zulip -m zulip && echo 'zulip ALL=(ALL) NOPASSWD:ALL' >> /e
|
||||
USER zulip
|
||||
|
||||
RUN ln -nsf /srv/zulip ~/zulip
|
||||
|
||||
RUN echo 'export LC_ALL="en_US.UTF-8" LANG="en_US.UTF-8" LANGUAGE="en_US.UTF-8"' >> ~zulip/.bashrc
|
||||
RUN echo 'export LC_ALL="en_US.UTF-8" LANG="en_US.UTF-8" LANGUAGE="en_US.UTF-8"' >> ~zulip/.bash_profile
|
||||
|
||||
WORKDIR /srv/zulip
|
||||
|
@@ -8,10 +8,11 @@ allows users to easily process hundreds or thousands of messages a day. With
|
||||
over 300 contributors merging over 500 commits a month, Zulip is also the
|
||||
largest and fastest growing open source group chat project.
|
||||
|
||||
[](https://circleci.com/gh/zulip/zulip)
|
||||
[](https://circleci.com/gh/zulip/zulip)
|
||||
[](https://travis-ci.org/zulip/zulip)
|
||||
[](https://codecov.io/gh/zulip/zulip)
|
||||
[][mypy-coverage]
|
||||
[](https://github.com/zulip/zulip/releases/latest)
|
||||
[](https://zulip.readthedocs.io/en/latest/)
|
||||
[](https://chat.zulip.org)
|
||||
[](https://twitter.com/zulip)
|
||||
@@ -58,7 +59,7 @@ You might be interested in:
|
||||
* **Running a Zulip server**. Setting up a server takes just a couple
|
||||
of minutes. Zulip runs on Ubuntu 18.04 Bionic, Ubuntu 16.04 Xenial,
|
||||
Ubuntu 14.04 Trusty, and Debian 9 Stretch. The installation process is
|
||||
[documented here](https://zulip.readthedocs.io/en/stable/prod.html).
|
||||
[documented here](https://zulip.readthedocs.io/en/stable/production/install.html).
|
||||
Commercial support is available; see <https://zulipchat.com/plans>
|
||||
for details.
|
||||
|
||||
|
@@ -2,14 +2,14 @@ import time
|
||||
from collections import OrderedDict, defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, Callable, Dict, List, \
|
||||
from typing import Callable, Dict, List, \
|
||||
Optional, Tuple, Type, Union
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connection, models
|
||||
from django.db import connection
|
||||
from django.db.models import F
|
||||
|
||||
from analytics.models import Anomaly, BaseCount, \
|
||||
from analytics.models import BaseCount, \
|
||||
FillState, InstallationCount, RealmCount, StreamCount, \
|
||||
UserCount, installation_epoch, last_successful_fill
|
||||
from zerver.lib.logging_util import log_to_file
|
||||
@@ -226,7 +226,6 @@ def do_drop_all_analytics_tables() -> None:
|
||||
RealmCount.objects.all().delete()
|
||||
InstallationCount.objects.all().delete()
|
||||
FillState.objects.all().delete()
|
||||
Anomaly.objects.all().delete()
|
||||
|
||||
def do_drop_single_stat(property: str) -> None:
|
||||
UserCount.objects.filter(property=property).delete()
|
||||
|
@@ -1,10 +1,9 @@
|
||||
from argparse import ArgumentParser
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
from analytics.models import InstallationCount, installation_epoch, \
|
||||
from analytics.models import installation_epoch, \
|
||||
last_successful_fill
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from zerver.lib.timestamp import floor_to_hour, floor_to_day, verify_UTC, \
|
||||
@@ -12,7 +11,6 @@ from zerver.lib.timestamp import floor_to_hour, floor_to_day, verify_UTC, \
|
||||
from zerver.models import Realm
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Mapping, Optional, Type, Union
|
||||
from typing import Any, Dict, List, Mapping, Optional, Type
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import now as timezone_now
|
||||
@@ -11,10 +11,10 @@ from analytics.lib.fixtures import generate_time_series_data
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import BaseCount, FillState, RealmCount, UserCount, \
|
||||
StreamCount, InstallationCount
|
||||
from zerver.lib.actions import do_change_is_admin
|
||||
from zerver.lib.actions import do_change_is_admin, STREAM_ASSIGNMENT_COLORS
|
||||
from zerver.lib.timestamp import floor_to_day
|
||||
from zerver.models import Realm, UserProfile, Stream, Message, Client, \
|
||||
RealmAuditLog, Recipient
|
||||
from zerver.models import Realm, UserProfile, Stream, Client, \
|
||||
RealmAuditLog, Recipient, Subscription
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """Populates analytics tables with randomly generated data."""
|
||||
@@ -72,7 +72,16 @@ class Command(BaseCommand):
|
||||
do_change_is_admin(shylock, True)
|
||||
stream = Stream.objects.create(
|
||||
name='all', realm=realm, date_created=installation_time)
|
||||
Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
||||
recipient = Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
||||
|
||||
# Subscribe shylock to the stream to avoid invariant failures.
|
||||
# TODO: This should use subscribe_users_to_streams from populate_db.
|
||||
subs = [
|
||||
Subscription(recipient=recipient,
|
||||
user_profile=shylock,
|
||||
color=STREAM_ASSIGNMENT_COLORS[0]),
|
||||
]
|
||||
Subscription.objects.bulk_create(subs)
|
||||
|
||||
def insert_fixture_data(stat: CountStat,
|
||||
fixture_data: Mapping[Optional[str], List[int]],
|
||||
|
@@ -2,7 +2,6 @@ import datetime
|
||||
from argparse import ArgumentParser
|
||||
from typing import Any, List
|
||||
|
||||
import pytz
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Count
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
@@ -25,20 +25,33 @@ class Command(BaseCommand):
|
||||
realms = Realm.objects.all()
|
||||
|
||||
for realm in realms:
|
||||
print(realm.string_id)
|
||||
print("------------")
|
||||
print("%25s %15s %10s" % ("stream", "subscribers", "messages"))
|
||||
streams = Stream.objects.filter(realm=realm).exclude(Q(name__istartswith="tutorial-"))
|
||||
invite_only_count = 0
|
||||
# private stream count
|
||||
private_count = 0
|
||||
# public stream count
|
||||
public_count = 0
|
||||
for stream in streams:
|
||||
if stream.invite_only:
|
||||
invite_only_count += 1
|
||||
continue
|
||||
private_count += 1
|
||||
else:
|
||||
public_count += 1
|
||||
print("------------")
|
||||
print(realm.string_id, end=' ')
|
||||
print("%10s %d public streams and" % ("(", public_count), end=' ')
|
||||
print("%d private streams )" % (private_count,))
|
||||
print("------------")
|
||||
print("%25s %15s %10s %12s" % ("stream", "subscribers", "messages", "type"))
|
||||
|
||||
for stream in streams:
|
||||
if stream.invite_only:
|
||||
stream_type = 'private'
|
||||
else:
|
||||
stream_type = 'public'
|
||||
print("%25s" % (stream.name,), end=' ')
|
||||
recipient = Recipient.objects.filter(type=Recipient.STREAM, type_id=stream.id)
|
||||
print("%10d" % (len(Subscription.objects.filter(recipient=recipient,
|
||||
active=True)),), end=' ')
|
||||
num_messages = len(Message.objects.filter(recipient=recipient))
|
||||
print("%12d" % (num_messages,))
|
||||
print("%d private streams" % (invite_only_count,))
|
||||
print("%12d" % (num_messages,), end=' ')
|
||||
print("%15s" % (stream_type,))
|
||||
print("")
|
||||
|
@@ -3,8 +3,6 @@ import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import zerver.lib.str_utils
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations, models
|
||||
|
||||
import zerver.lib.str_utils
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-16 20:50
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
34
analytics/migrations/0013_remove_anomaly.py
Normal file
34
analytics/migrations/0013_remove_anomaly.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-02-02 02:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0012_add_on_delete'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='installationcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='realmcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='streamcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='usercount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Anomaly',
|
||||
),
|
||||
]
|
@@ -1,10 +1,10 @@
|
||||
import datetime
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
from typing import Optional
|
||||
|
||||
from django.db import models
|
||||
|
||||
from zerver.lib.timestamp import floor_to_day
|
||||
from zerver.models import Realm, Recipient, Stream, UserProfile
|
||||
from zerver.models import Realm, Stream, UserProfile
|
||||
|
||||
class FillState(models.Model):
|
||||
property = models.CharField(max_length=40, unique=True) # type: str
|
||||
@@ -34,13 +34,6 @@ def last_successful_fill(property: str) -> Optional[datetime.datetime]:
|
||||
return fillstate.end_time
|
||||
return fillstate.end_time - datetime.timedelta(hours=1)
|
||||
|
||||
# would only ever make entries here by hand
|
||||
class Anomaly(models.Model):
|
||||
info = models.CharField(max_length=1000) # type: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "<Anomaly: %s... %s>" % (self.info, self.id)
|
||||
|
||||
class BaseCount(models.Model):
|
||||
# Note: When inheriting from BaseCount, you may want to rearrange
|
||||
# the order of the columns in the migration to make sure they
|
||||
@@ -49,7 +42,6 @@ class BaseCount(models.Model):
|
||||
subgroup = models.CharField(max_length=16, null=True) # type: Optional[str]
|
||||
end_time = models.DateTimeField() # type: datetime.datetime
|
||||
value = models.BigIntegerField() # type: int
|
||||
anomaly = models.ForeignKey(Anomaly, on_delete=models.SET_NULL, null=True) # type: Optional[Anomaly]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@@ -1,6 +1,6 @@
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
|
||||
import ujson
|
||||
from django.apps import apps
|
||||
@@ -10,19 +10,20 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.timezone import utc as timezone_utc
|
||||
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, DataCollector, \
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, \
|
||||
DependentCountStat, LoggingCountStat, do_aggregate_to_summary_table, \
|
||||
do_drop_all_analytics_tables, do_drop_single_stat, \
|
||||
do_fill_count_stat_at_hour, do_increment_logging_stat, \
|
||||
process_count_stat, sql_data_collector
|
||||
from analytics.models import Anomaly, BaseCount, \
|
||||
from analytics.models import BaseCount, \
|
||||
FillState, InstallationCount, RealmCount, StreamCount, \
|
||||
UserCount, installation_epoch, last_successful_fill
|
||||
UserCount, installation_epoch
|
||||
from zerver.lib.actions import do_activate_user, do_create_user, \
|
||||
do_deactivate_user, do_reactivate_user, update_user_activity_interval, \
|
||||
do_invite_users, do_revoke_user_invite, do_resend_user_invite_email, \
|
||||
InvitationError
|
||||
from zerver.lib.timestamp import TimezoneNotUTCException, floor_to_day
|
||||
from zerver.lib.topic import DB_TOPIC_NAME
|
||||
from zerver.models import Client, Huddle, Message, Realm, \
|
||||
RealmAuditLog, Recipient, Stream, UserActivityInterval, \
|
||||
UserProfile, get_client, get_user, PreregistrationUser
|
||||
@@ -56,7 +57,10 @@ class AnalyticsTestCase(TestCase):
|
||||
'api_key': '42'}
|
||||
for key, value in defaults.items():
|
||||
kwargs[key] = kwargs.get(key, value)
|
||||
return UserProfile.objects.create(**kwargs)
|
||||
kwargs['delivery_email'] = kwargs['email']
|
||||
user_profile = UserProfile.objects.create(**kwargs)
|
||||
# TODO: Make this pass user_profile.full_clean()
|
||||
return user_profile
|
||||
|
||||
def create_stream_with_recipient(self, **kwargs: Any) -> Tuple[Stream, Recipient]:
|
||||
self.name_counter += 1
|
||||
@@ -82,7 +86,7 @@ class AnalyticsTestCase(TestCase):
|
||||
defaults = {
|
||||
'sender': sender,
|
||||
'recipient': recipient,
|
||||
'subject': 'subject',
|
||||
DB_TOPIC_NAME: 'subject',
|
||||
'content': 'hi',
|
||||
'pub_date': self.TIME_LAST_HOUR,
|
||||
'sending_client': get_client("website")}
|
||||
@@ -842,7 +846,6 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
RealmCount.objects.create(realm=user.realm, **count_args)
|
||||
InstallationCount.objects.create(**count_args)
|
||||
FillState.objects.create(property='test', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
Anomaly.objects.create(info='test anomaly')
|
||||
|
||||
analytics = apps.get_app_config('analytics')
|
||||
for table in list(analytics.models.values()):
|
||||
@@ -865,7 +868,6 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
InstallationCount.objects.create(**count_args)
|
||||
FillState.objects.create(property='to_delete', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
FillState.objects.create(property='to_save', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
Anomaly.objects.create(info='test anomaly')
|
||||
|
||||
analytics = apps.get_app_config('analytics')
|
||||
for table in list(analytics.models.values()):
|
||||
@@ -873,11 +875,8 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
|
||||
do_drop_single_stat('to_delete')
|
||||
for table in list(analytics.models.values()):
|
||||
if table._meta.db_table == 'analytics_anomaly':
|
||||
self.assertTrue(table.objects.exists())
|
||||
else:
|
||||
self.assertFalse(table.objects.filter(property='to_delete').exists())
|
||||
self.assertTrue(table.objects.filter(property='to_save').exists())
|
||||
self.assertFalse(table.objects.filter(property='to_delete').exists())
|
||||
self.assertTrue(table.objects.filter(property='to_save').exists())
|
||||
|
||||
class TestActiveUsersAudit(AnalyticsTestCase):
|
||||
def setUp(self) -> None:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import mock
|
||||
from django.utils.timezone import utc
|
||||
@@ -8,8 +8,8 @@ from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import FillState, \
|
||||
RealmCount, UserCount, last_successful_fill
|
||||
from analytics.views import get_chart_data, rewrite_client_arrays, \
|
||||
sort_by_totals, sort_client_labels, stats
|
||||
from analytics.views import rewrite_client_arrays, \
|
||||
sort_by_totals, sort_client_labels
|
||||
from zerver.lib.test_classes import ZulipTestCase
|
||||
from zerver.lib.timestamp import ceiling_to_day, \
|
||||
ceiling_to_hour, datetime_to_timestamp
|
||||
|
@@ -16,6 +16,12 @@ i18n_urlpatterns = [
|
||||
name='analytics.views.stats_for_realm'),
|
||||
url(r'^stats/installation$', analytics.views.stats_for_installation,
|
||||
name='analytics.views.stats_for_installation'),
|
||||
url(r'^stats/remote/(?P<remote_server_id>[\S]+)/installation$',
|
||||
analytics.views.stats_for_remote_installation,
|
||||
name='analytics.views.stats_for_remote_installation'),
|
||||
url(r'^stats/remote/(?P<remote_server_id>[\S]+)/realm/(?P<remote_realm_id>[\S]+)/$',
|
||||
analytics.views.stats_for_remote_realm,
|
||||
name='analytics.views.stats_for_remote_realm'),
|
||||
|
||||
# User-visible stats page
|
||||
url(r'^stats$', analytics.views.stats,
|
||||
@@ -38,6 +44,11 @@ v1_api_and_json_patterns = [
|
||||
{'GET': 'analytics.views.get_chart_data_for_realm'}),
|
||||
url(r'^analytics/chart_data/installation$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_installation'}),
|
||||
url(r'^analytics/chart_data/remote/(?P<remote_server_id>[\S]+)/installation$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_remote_installation'}),
|
||||
url(r'^analytics/chart_data/remote/(?P<remote_server_id>[\S]+)/realm/(?P<remote_realm_id>[\S]+)$',
|
||||
rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_remote_realm'}),
|
||||
]
|
||||
|
||||
i18n_urlpatterns += [
|
||||
|
@@ -1,28 +1,26 @@
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Callable, Dict, List, \
|
||||
Optional, Set, Tuple, Type, Union
|
||||
Optional, Set, Tuple, Type, Union, cast
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.db import connection
|
||||
from django.db.models import Sum
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
||||
from django.shortcuts import render
|
||||
from django.template import RequestContext, loader
|
||||
from django.template import loader
|
||||
from django.utils.timezone import now as timezone_now, utc as timezone_utc
|
||||
from django.utils.translation import ugettext as _
|
||||
from jinja2 import Markup as mark_safe
|
||||
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, process_count_stat
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import BaseCount, InstallationCount, \
|
||||
RealmCount, StreamCount, UserCount, last_successful_fill, installation_epoch
|
||||
@@ -32,16 +30,25 @@ from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.json_encoder_for_html import JSONEncoderForHTML
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import ceiling_to_day, \
|
||||
ceiling_to_hour, convert_to_UTC, timestamp_to_datetime
|
||||
from zerver.lib.timestamp import convert_to_UTC, timestamp_to_datetime
|
||||
from zerver.models import Client, get_realm, Realm, \
|
||||
UserActivity, UserActivityInterval, UserProfile
|
||||
|
||||
if settings.ZILENCER_ENABLED:
|
||||
from zilencer.models import RemoteInstallationCount, RemoteRealmCount, \
|
||||
RemoteZulipServer
|
||||
else:
|
||||
from mock import Mock
|
||||
RemoteInstallationCount = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
RemoteZulipServer = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
RemoteRealmCount = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
|
||||
def render_stats(request: HttpRequest, data_url_suffix: str, target_name: str,
|
||||
for_installation: bool=False) -> HttpRequest:
|
||||
for_installation: bool=False, remote: bool=False) -> HttpRequest:
|
||||
page_params = dict(
|
||||
data_url_suffix=data_url_suffix,
|
||||
for_installation=for_installation,
|
||||
remote=remote,
|
||||
debug_mode=False,
|
||||
)
|
||||
return render(request,
|
||||
@@ -67,6 +74,14 @@ def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
|
||||
|
||||
return render_stats(request, '/realm/%s' % (realm_str,), realm.name or realm.string_id)
|
||||
|
||||
@require_server_admin
|
||||
@has_request_variables
|
||||
def stats_for_remote_realm(request: HttpRequest, remote_server_id: str,
|
||||
remote_realm_id: str) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return render_stats(request, '/remote/%s/realm/%s' % (server.id, remote_realm_id),
|
||||
"Realm %s on server %s" % (remote_realm_id, server.hostname))
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
|
||||
@@ -77,26 +92,65 @@ def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
|
||||
|
||||
return get_chart_data(request=request, user_profile=user_profile, realm=realm, **kwargs)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_remote_realm(
|
||||
request: HttpRequest, user_profile: UserProfile, remote_server_id: str,
|
||||
remote_realm_id: str, **kwargs: Any) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return get_chart_data(request=request, user_profile=user_profile, server=server,
|
||||
remote=True, remote_realm_id=int(remote_realm_id), **kwargs)
|
||||
|
||||
@require_server_admin
|
||||
def stats_for_installation(request: HttpRequest) -> HttpResponse:
|
||||
return render_stats(request, '/installation', 'Installation', True)
|
||||
|
||||
@require_server_admin
|
||||
def stats_for_remote_installation(request: HttpRequest, remote_server_id: str) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return render_stats(request, '/remote/%s/installation' % (server.id,),
|
||||
'remote Installation %s' % (server.hostname), True, True)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_installation(request: HttpRequest, user_profile: UserProfile,
|
||||
chart_name: str=REQ(), **kwargs: Any) -> HttpResponse:
|
||||
return get_chart_data(request=request, user_profile=user_profile, for_installation=True, **kwargs)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_remote_installation(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
remote_server_id: str,
|
||||
chart_name: str=REQ(),
|
||||
**kwargs: Any) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return get_chart_data(request=request, user_profile=user_profile, for_installation=True,
|
||||
remote=True, server=server, **kwargs)
|
||||
|
||||
@require_non_guest_user
|
||||
@has_request_variables
|
||||
def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name: str=REQ(),
|
||||
min_length: Optional[int]=REQ(converter=to_non_negative_int, default=None),
|
||||
start: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
|
||||
end: Optional[datetime]=REQ(converter=to_utc_datetime, default=None),
|
||||
realm: Optional[Realm]=None, for_installation: bool=False) -> HttpResponse:
|
||||
aggregate_table = RealmCount
|
||||
realm: Optional[Realm]=None, for_installation: bool=False,
|
||||
remote: bool=False, remote_realm_id: Optional[int]=None,
|
||||
server: Optional[RemoteZulipServer]=None) -> HttpResponse:
|
||||
if for_installation:
|
||||
aggregate_table = InstallationCount
|
||||
if remote:
|
||||
aggregate_table = RemoteInstallationCount
|
||||
assert server is not None
|
||||
else:
|
||||
aggregate_table = InstallationCount
|
||||
else:
|
||||
if remote:
|
||||
aggregate_table = RemoteRealmCount
|
||||
assert server is not None
|
||||
assert remote_realm_id is not None
|
||||
else:
|
||||
aggregate_table = RealmCount
|
||||
|
||||
if chart_name == 'number_of_humans':
|
||||
stats = [
|
||||
@@ -147,29 +201,60 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
|
||||
{'start': start, 'end': end})
|
||||
|
||||
if realm is None:
|
||||
# Note that this value is invalid for Remote tables; be
|
||||
# careful not to access it in those code paths.
|
||||
realm = user_profile.realm
|
||||
if start is None:
|
||||
if for_installation:
|
||||
start = installation_epoch()
|
||||
else:
|
||||
start = realm.date_created
|
||||
if end is None:
|
||||
end = max(last_successful_fill(stat.property) or
|
||||
datetime.min.replace(tzinfo=timezone_utc) for stat in stats)
|
||||
if end is None or start > end:
|
||||
logging.warning("User from realm %s attempted to access /stats, but the computed "
|
||||
"start time: %s (creation of realm or installation) is later than the computed "
|
||||
"end time: %s (last successful analytics update). Is the "
|
||||
"analytics cron job running?" % (realm.string_id, start, end))
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
|
||||
if remote:
|
||||
# For remote servers, we don't have fillstate data, and thus
|
||||
# should simply use the first and last data points for the
|
||||
# table.
|
||||
assert server is not None
|
||||
if not aggregate_table.objects.filter(server=server).exists():
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
if start is None:
|
||||
start = aggregate_table.objects.filter(server=server).first().end_time
|
||||
if end is None:
|
||||
end = aggregate_table.objects.filter(server=server).last().end_time
|
||||
else:
|
||||
# Otherwise, we can use tables on the current server to
|
||||
# determine a nice range, and some additional validation.
|
||||
if start is None:
|
||||
if for_installation:
|
||||
start = installation_epoch()
|
||||
else:
|
||||
start = realm.date_created
|
||||
if end is None:
|
||||
end = max(last_successful_fill(stat.property) or
|
||||
datetime.min.replace(tzinfo=timezone_utc) for stat in stats)
|
||||
if end is None or start > end:
|
||||
logging.warning("User from realm %s attempted to access /stats, but the computed "
|
||||
"start time: %s (creation of realm or installation) is later than the computed "
|
||||
"end time: %s (last successful analytics update). Is the "
|
||||
"analytics cron job running?" % (realm.string_id, start, end))
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
|
||||
assert len(set([stat.frequency for stat in stats])) == 1
|
||||
end_times = time_range(start, end, stats[0].frequency, min_length)
|
||||
data = {'end_times': end_times, 'frequency': stats[0].frequency} # type: Dict[str, Any]
|
||||
|
||||
aggregation_level = {InstallationCount: 'everyone', RealmCount: 'everyone', UserCount: 'user'}
|
||||
aggregation_level = {
|
||||
InstallationCount: 'everyone',
|
||||
RealmCount: 'everyone',
|
||||
RemoteInstallationCount: 'everyone',
|
||||
RemoteRealmCount: 'everyone',
|
||||
UserCount: 'user',
|
||||
}
|
||||
# -1 is a placeholder value, since there is no relevant filtering on InstallationCount
|
||||
id_value = {InstallationCount: -1, RealmCount: realm.id, UserCount: user_profile.id}
|
||||
id_value = {
|
||||
InstallationCount: -1,
|
||||
RealmCount: realm.id,
|
||||
RemoteInstallationCount: cast(RemoteZulipServer, server).id if server is not None else None,
|
||||
# TODO: RemoteRealmCount logic doesn't correctly handle
|
||||
# filtering by server_id as well.
|
||||
RemoteRealmCount: remote_realm_id,
|
||||
UserCount: user_profile.id,
|
||||
}
|
||||
for table in tables:
|
||||
data[aggregation_level[table]] = {}
|
||||
for stat in stats:
|
||||
@@ -213,6 +298,10 @@ def table_filtered_to_id(table: Type[BaseCount], key_id: int) -> QuerySet:
|
||||
return StreamCount.objects.filter(stream_id=key_id)
|
||||
elif table == InstallationCount:
|
||||
return InstallationCount.objects.all()
|
||||
elif table == RemoteInstallationCount:
|
||||
return RemoteInstallationCount.objects.filter(server_id=key_id)
|
||||
elif table == RemoteRealmCount:
|
||||
return RemoteRealmCount.objects.filter(realm_id=key_id)
|
||||
else:
|
||||
raise AssertionError("Unknown table: %s" % (table,))
|
||||
|
||||
@@ -476,7 +565,7 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
for row in rows:
|
||||
row['date_created_day'] = row['date_created'].strftime('%Y-%m-%d')
|
||||
row['plan_type_string'] = [
|
||||
'', 'self hosted', 'limited', 'standard', 'standard free'][row['plan_type']]
|
||||
'', 'self hosted', 'limited', 'standard', 'open source'][row['plan_type']]
|
||||
row['age_days'] = int((now - row['date_created']).total_seconds()
|
||||
/ 86400)
|
||||
row['is_new'] = row['age_days'] < 12 * 7
|
||||
@@ -490,6 +579,16 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
except Exception:
|
||||
row['history'] = ''
|
||||
|
||||
# estimate annual subscription revenue
|
||||
total_amount = 0
|
||||
if settings.BILLING_ENABLED:
|
||||
from corporate.lib.stripe import estimate_annual_recurring_revenue_by_realm
|
||||
estimated_arrs = estimate_annual_recurring_revenue_by_realm()
|
||||
for row in rows:
|
||||
if row['string_id'] in estimated_arrs:
|
||||
row['amount'] = estimated_arrs[row['string_id']]
|
||||
total_amount += sum(estimated_arrs.values())
|
||||
|
||||
# augment data with realm_minutes
|
||||
total_hours = 0.0
|
||||
for row in rows:
|
||||
@@ -525,9 +624,10 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
total_bot_count += int(row['bot_count'])
|
||||
total_wau_count += int(row['wau_count'])
|
||||
|
||||
rows.append(dict(
|
||||
total_row = dict(
|
||||
string_id='Total',
|
||||
plan_type_string="",
|
||||
amount=total_amount,
|
||||
stats_link = '',
|
||||
date_created_day='',
|
||||
realm_admin_email='',
|
||||
@@ -536,7 +636,9 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
bot_count=total_bot_count,
|
||||
hours=int(total_hours),
|
||||
wau_count=total_wau_count,
|
||||
))
|
||||
)
|
||||
|
||||
rows.insert(0, total_row)
|
||||
|
||||
content = loader.render_to_string(
|
||||
'analytics/realm_summary_table.html',
|
||||
@@ -562,15 +664,15 @@ def user_activity_intervals() -> Tuple[mark_safe, Dict[str, float]]:
|
||||
).only(
|
||||
'start',
|
||||
'end',
|
||||
'user_profile__email',
|
||||
'user_profile__delivery_email',
|
||||
'user_profile__realm__string_id'
|
||||
).order_by(
|
||||
'user_profile__realm__string_id',
|
||||
'user_profile__email'
|
||||
'user_profile__delivery_email'
|
||||
)
|
||||
|
||||
by_string_id = lambda row: row.user_profile.realm.string_id
|
||||
by_email = lambda row: row.user_profile.email
|
||||
by_email = lambda row: row.user_profile.delivery_email
|
||||
|
||||
realm_minutes = {}
|
||||
|
||||
@@ -664,7 +766,8 @@ def sent_messages_report(realm: str) -> str:
|
||||
return make_table(title, cols, rows)
|
||||
|
||||
def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
def get_page(query: str, cols: List[str], title: str) -> Dict[str, str]:
|
||||
def get_page(query: str, cols: List[str], title: str,
|
||||
totals_columns: List[int]=[]) -> Dict[str, str]:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query)
|
||||
rows = cursor.fetchall()
|
||||
@@ -676,11 +779,24 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
for row in rows:
|
||||
row[i] = fixup_func(row[i])
|
||||
|
||||
total_row = []
|
||||
for i, col in enumerate(cols):
|
||||
if col == 'Realm':
|
||||
fix_rows(i, realm_activity_link)
|
||||
elif col in ['Last time', 'Last visit']:
|
||||
fix_rows(i, format_date_for_activity_reports)
|
||||
elif col == 'Hostname':
|
||||
for row in rows:
|
||||
row[i] = remote_installation_stats_link(row[0], row[i])
|
||||
if len(totals_columns) > 0:
|
||||
if i == 0:
|
||||
total_row.append("Total")
|
||||
elif i in totals_columns:
|
||||
total_row.append(str(sum(row[i] for row in rows if row[i] is not None)))
|
||||
else:
|
||||
total_row.append('')
|
||||
if len(totals_columns) > 0:
|
||||
rows.insert(0, total_row)
|
||||
|
||||
content = make_table(title, cols, rows)
|
||||
|
||||
@@ -830,6 +946,49 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
|
||||
pages.append(get_page(query, cols, title))
|
||||
|
||||
title = 'Remote Zulip servers'
|
||||
|
||||
query = '''
|
||||
with icount as (
|
||||
select
|
||||
server_id,
|
||||
max(value) as max_value,
|
||||
max(end_time) as max_end_time
|
||||
from zilencer_remoteinstallationcount
|
||||
where
|
||||
property='active_users:is_bot:day'
|
||||
and subgroup='false'
|
||||
group by server_id
|
||||
),
|
||||
remote_push_devices as (
|
||||
select server_id, count(distinct(user_id)) as push_user_count from zilencer_remotepushdevicetoken
|
||||
group by server_id
|
||||
)
|
||||
select
|
||||
rserver.id,
|
||||
rserver.hostname,
|
||||
rserver.contact_email,
|
||||
max_value,
|
||||
push_user_count,
|
||||
max_end_time
|
||||
from zilencer_remotezulipserver rserver
|
||||
left join icount on icount.server_id = rserver.id
|
||||
left join remote_push_devices on remote_push_devices.server_id = rserver.id
|
||||
order by max_value DESC NULLS LAST, push_user_count DESC NULLS LAST
|
||||
'''
|
||||
|
||||
cols = [
|
||||
'ID',
|
||||
'Hostname',
|
||||
'Contact email',
|
||||
'Analytics users',
|
||||
'Mobile users',
|
||||
'Last update time',
|
||||
]
|
||||
|
||||
pages.append(get_page(query, cols, title,
|
||||
totals_columns=[3, 4]))
|
||||
|
||||
return pages
|
||||
|
||||
@require_server_admin
|
||||
@@ -855,7 +1014,7 @@ def get_activity(request: HttpRequest) -> HttpResponse:
|
||||
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
||||
fields = [
|
||||
'user_profile__full_name',
|
||||
'user_profile__email',
|
||||
'user_profile__delivery_email',
|
||||
'query',
|
||||
'client__name',
|
||||
'count',
|
||||
@@ -867,7 +1026,7 @@ def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
||||
user_profile__is_active=True,
|
||||
user_profile__is_bot=is_bot
|
||||
)
|
||||
records = records.order_by("user_profile__email", "-last_visit")
|
||||
records = records.order_by("user_profile__delivery_email", "-last_visit")
|
||||
records = records.select_related('user_profile', 'client').only(*fields)
|
||||
return records
|
||||
|
||||
@@ -881,7 +1040,7 @@ def get_user_activity_records_for_email(email: str) -> List[QuerySet]:
|
||||
]
|
||||
|
||||
records = UserActivity.objects.filter(
|
||||
user_profile__email=email
|
||||
user_profile__delivery_email=email
|
||||
)
|
||||
records = records.order_by("-last_visit")
|
||||
records = records.select_related('user_profile', 'client').only(*fields)
|
||||
@@ -980,6 +1139,12 @@ def realm_stats_link(realm_str: str) -> mark_safe:
|
||||
stats_link = '<a href="{}"><i class="fa fa-pie-chart"></i></a>'.format(url, realm_str)
|
||||
return mark_safe(stats_link)
|
||||
|
||||
def remote_installation_stats_link(server_id: int, hostname: str) -> mark_safe:
|
||||
url_name = 'analytics.views.stats_for_remote_installation'
|
||||
url = reverse(url_name, kwargs=dict(remote_server_id=server_id))
|
||||
stats_link = '<a href="{}"><i class="fa fa-pie-chart"></i>{}</a>'.format(url, hostname)
|
||||
return mark_safe(stats_link)
|
||||
|
||||
def realm_client_table(user_summaries: Dict[str, Dict[str, Dict[str, Any]]]) -> str:
|
||||
exclude_keys = [
|
||||
'internal',
|
||||
@@ -1055,7 +1220,7 @@ def realm_user_summary_table(all_records: List[QuerySet],
|
||||
user_records = {}
|
||||
|
||||
def by_email(record: QuerySet) -> str:
|
||||
return record.user_profile.email
|
||||
return record.user_profile.delivery_email
|
||||
|
||||
for email, records in itertools.groupby(all_records, by_email):
|
||||
user_records[email] = get_user_activity_summary(list(records))
|
||||
@@ -1126,7 +1291,7 @@ def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
|
||||
except Realm.DoesNotExist:
|
||||
return HttpResponseNotFound("Realm %s does not exist" % (realm_str,))
|
||||
|
||||
admin_emails = {admin.email for admin in admins}
|
||||
admin_emails = {admin.delivery_email for admin in admins}
|
||||
|
||||
for is_bot, page_title in [(False, 'Humans'), (True, 'Bots')]:
|
||||
all_records = list(get_user_activity_records_for_realm(realm_str, is_bot))
|
||||
|
@@ -16,12 +16,11 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
from zerver.lib.utils import generate_random_token
|
||||
from zerver.models import PreregistrationUser, EmailChangeStatus, MultiuseInvite, \
|
||||
UserProfile, Realm
|
||||
from random import SystemRandom
|
||||
import string
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
class ConfirmationKeyException(Exception):
|
||||
WRONG_LENGTH = 1
|
||||
@@ -70,8 +69,11 @@ def create_confirmation_link(obj: ContentType, host: str,
|
||||
confirmation_type: int,
|
||||
url_args: Optional[Dict[str, str]]=None) -> str:
|
||||
key = generate_key()
|
||||
realm = None
|
||||
if hasattr(obj, 'realm'):
|
||||
realm = obj.realm
|
||||
Confirmation.objects.create(content_object=obj, date_sent=timezone_now(), confirmation_key=key,
|
||||
realm=obj.realm, type=confirmation_type)
|
||||
realm=realm, type=confirmation_type)
|
||||
return confirmation_url(key, host, confirmation_type, url_args)
|
||||
|
||||
def confirmation_url(confirmation_key: str, host: str,
|
||||
@@ -99,6 +101,7 @@ class Confirmation(models.Model):
|
||||
SERVER_REGISTRATION = 5
|
||||
MULTIUSE_INVITE = 6
|
||||
REALM_CREATION = 7
|
||||
REALM_REACTIVATION = 8
|
||||
type = models.PositiveSmallIntegerField() # type: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -121,8 +124,18 @@ _properties = {
|
||||
'zerver.views.registration.accounts_home_from_multiuse_invite',
|
||||
validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS),
|
||||
Confirmation.REALM_CREATION: ConfirmationType('check_prereg_key_and_redirect'),
|
||||
Confirmation.REALM_REACTIVATION: ConfirmationType('zerver.views.realm.realm_reactivation'),
|
||||
}
|
||||
|
||||
def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> str:
|
||||
"""
|
||||
Generate a unique link that a logged-out user can visit to unsubscribe from
|
||||
Zulip e-mails without having to first log in.
|
||||
"""
|
||||
return create_confirmation_link(user_profile, user_profile.realm.host,
|
||||
Confirmation.UNSUBSCRIBE,
|
||||
url_args = {'email_type': email_type})
|
||||
|
||||
# Functions related to links generated by the generate_realm_creation_link.py
|
||||
# management command.
|
||||
# Note that being validated here will just allow the user to access the create_realm
|
||||
|
@@ -2,8 +2,6 @@
|
||||
|
||||
# Copyright: (c) 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
__revision__ = '$Id: settings.py 12 2008-11-23 19:38:52Z jarek.zgoda $'
|
||||
|
||||
STATUS_ACTIVE = 1
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from functools import wraps
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
from typing import Any, Callable, Dict, Optional, TypeVar, Tuple
|
||||
from typing import Any, Callable, Dict, Optional, TypeVar, Tuple, cast
|
||||
import ujson
|
||||
|
||||
from django.conf import settings
|
||||
@@ -12,13 +14,12 @@ from django.utils.timezone import now as timezone_now
|
||||
from django.core.signing import Signer
|
||||
import stripe
|
||||
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.logging_util import log_to_file
|
||||
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
|
||||
from zerver.lib.utils import generate_random_token
|
||||
from zerver.lib.actions import do_change_plan_type
|
||||
from zerver.models import Realm, UserProfile, RealmAuditLog
|
||||
from corporate.models import Customer, Plan, Coupon, BillingProcessor
|
||||
from corporate.models import Customer, CustomerPlan, LicenseLedger, \
|
||||
get_active_plan
|
||||
from zproject.settings import get_secret
|
||||
|
||||
STRIPE_PUBLISHABLE_KEY = get_secret('stripe_publishable_key')
|
||||
@@ -32,30 +33,17 @@ billing_logger = logging.getLogger('corporate.stripe')
|
||||
log_to_file(billing_logger, BILLING_LOG_PATH)
|
||||
log_to_file(logging.getLogger('stripe'), BILLING_LOG_PATH)
|
||||
|
||||
## Note: this is no longer accurate, as of when we added coupons
|
||||
# To generate the fixture data in stripe_fixtures.json:
|
||||
# * Set PRINT_STRIPE_FIXTURE_DATA to True
|
||||
# * ./manage.py setup_stripe
|
||||
# * Customer.objects.all().delete()
|
||||
# * Log in as a user, and go to http://localhost:9991/upgrade/
|
||||
# * Click Add card. Enter the following billing details:
|
||||
# Name: Ada Starr, Street: Under the sea, City: Pacific,
|
||||
# Zip: 33333, Country: United States
|
||||
# Card number: 4242424242424242, Expiry: 03/33, CVV: 333
|
||||
# * Click Make payment.
|
||||
# * Copy out the 4 blobs of json from the dev console into stripe_fixtures.json.
|
||||
# The contents of that file are '{\n' + concatenate the 4 json blobs + '\n}'.
|
||||
# Then you can run e.g. `M-x mark-whole-buffer` and `M-x indent-region` in emacs
|
||||
# to prettify the file (and make 4 space indents).
|
||||
# * Copy out the customer id, plan id, and quantity values into
|
||||
# corporate.tests.test_stripe.StripeTest.setUp.
|
||||
# * Set PRINT_STRIPE_FIXTURE_DATA to False
|
||||
PRINT_STRIPE_FIXTURE_DATA = False
|
||||
|
||||
CallableT = TypeVar('CallableT', bound=Callable[..., Any])
|
||||
|
||||
MIN_INVOICED_LICENSES = 30
|
||||
DEFAULT_INVOICE_DAYS_UNTIL_DUE = 30
|
||||
|
||||
def get_seat_count(realm: Realm) -> int:
|
||||
return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False).count()
|
||||
non_guests = UserProfile.objects.filter(
|
||||
realm=realm, is_active=True, is_bot=False, is_guest=False).count()
|
||||
guests = UserProfile.objects.filter(
|
||||
realm=realm, is_active=True, is_bot=False, is_guest=True).count()
|
||||
return max(non_guests, math.ceil(guests / 5))
|
||||
|
||||
def sign_string(string: str) -> Tuple[str, str]:
|
||||
salt = generate_random_token(64)
|
||||
@@ -66,13 +54,78 @@ def unsign_string(signed_string: str, salt: str) -> str:
|
||||
signer = Signer(salt=salt)
|
||||
return signer.unsign(signed_string)
|
||||
|
||||
# Be extremely careful changing this function. Historical billing periods
|
||||
# are not stored anywhere, and are just computed on the fly using this
|
||||
# function. Any change you make here should return the same value (or be
|
||||
# within a few seconds) for basically any value from when the billing system
|
||||
# went online to within a year from now.
|
||||
def add_months(dt: datetime, months: int) -> datetime:
|
||||
assert(months >= 0)
|
||||
# It's fine that the max day in Feb is 28 for leap years.
|
||||
MAX_DAY_FOR_MONTH = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30,
|
||||
7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31}
|
||||
year = dt.year
|
||||
month = dt.month + months
|
||||
while month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
day = min(dt.day, MAX_DAY_FOR_MONTH[month])
|
||||
# datetimes don't support leap seconds, so don't need to worry about those
|
||||
return dt.replace(year=year, month=month, day=day)
|
||||
|
||||
def next_month(billing_cycle_anchor: datetime, dt: datetime) -> datetime:
|
||||
estimated_months = round((dt - billing_cycle_anchor).days * 12. / 365)
|
||||
for months in range(max(estimated_months - 1, 0), estimated_months + 2):
|
||||
proposed_next_month = add_months(billing_cycle_anchor, months)
|
||||
if 20 < (proposed_next_month - dt).days < 40:
|
||||
return proposed_next_month
|
||||
raise AssertionError('Something wrong in next_month calculation with '
|
||||
'billing_cycle_anchor: %s, dt: %s' % (billing_cycle_anchor, dt))
|
||||
|
||||
# TODO take downgrade into account
|
||||
def next_renewal_date(plan: CustomerPlan, event_time: datetime) -> datetime:
|
||||
months_per_period = {
|
||||
CustomerPlan.ANNUAL: 12,
|
||||
CustomerPlan.MONTHLY: 1,
|
||||
}[plan.billing_schedule]
|
||||
periods = 1
|
||||
dt = plan.billing_cycle_anchor
|
||||
while dt <= event_time:
|
||||
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
|
||||
periods += 1
|
||||
return dt
|
||||
|
||||
# TODO take downgrade into account
|
||||
def next_invoice_date(plan: CustomerPlan) -> datetime:
|
||||
months_per_period = {
|
||||
CustomerPlan.ANNUAL: 12,
|
||||
CustomerPlan.MONTHLY: 1,
|
||||
}[plan.billing_schedule]
|
||||
if plan.automanage_licenses:
|
||||
months_per_period = 1
|
||||
periods = 1
|
||||
dt = plan.billing_cycle_anchor
|
||||
while dt <= plan.next_invoice_date:
|
||||
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
|
||||
periods += 1
|
||||
return dt
|
||||
|
||||
def renewal_amount(plan: CustomerPlan, event_time: datetime) -> Optional[int]: # nocoverage: TODO
|
||||
if plan.fixed_price is not None:
|
||||
return plan.fixed_price
|
||||
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
if last_ledger_entry.licenses_at_next_renewal is None:
|
||||
return None
|
||||
assert(plan.price_per_license is not None) # for mypy
|
||||
return plan.price_per_license * last_ledger_entry.licenses_at_next_renewal
|
||||
|
||||
class BillingError(Exception):
|
||||
# error messages
|
||||
CONTACT_SUPPORT = _("Something went wrong. Please contact %s." % (settings.ZULIP_ADMINISTRATOR,))
|
||||
TRY_RELOADING = _("Something went wrong. Please reload the page.")
|
||||
|
||||
# description is used only for tests
|
||||
def __init__(self, description: str, message: str) -> None:
|
||||
def __init__(self, description: str, message: str=CONTACT_SUPPORT) -> None:
|
||||
self.description = description
|
||||
self.message = message
|
||||
|
||||
@@ -89,9 +142,6 @@ def catch_stripe_errors(func: CallableT) -> CallableT:
|
||||
if STRIPE_PUBLISHABLE_KEY is None:
|
||||
raise BillingError('missing stripe config', "Missing Stripe config. "
|
||||
"See https://zulip.readthedocs.io/en/latest/subsystems/billing.html.")
|
||||
if not Plan.objects.exists():
|
||||
raise BillingError('missing plans',
|
||||
"Plan objects not created. Please run ./manage.py setup_stripe")
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
# See https://stripe.com/docs/api/python#error_handling, though
|
||||
@@ -114,58 +164,11 @@ def catch_stripe_errors(func: CallableT) -> CallableT:
|
||||
|
||||
@catch_stripe_errors
|
||||
def stripe_get_customer(stripe_customer_id: str) -> stripe.Customer:
|
||||
stripe_customer = stripe.Customer.retrieve(stripe_customer_id, expand=["default_source"])
|
||||
if PRINT_STRIPE_FIXTURE_DATA:
|
||||
print(''.join(['"customer_with_subscription": ', str(stripe_customer), ','])) # nocoverage
|
||||
return stripe_customer
|
||||
return stripe.Customer.retrieve(stripe_customer_id, expand=["default_source"])
|
||||
|
||||
@catch_stripe_errors
|
||||
def stripe_get_upcoming_invoice(stripe_customer_id: str) -> stripe.Invoice:
|
||||
stripe_invoice = stripe.Invoice.upcoming(customer=stripe_customer_id)
|
||||
if PRINT_STRIPE_FIXTURE_DATA:
|
||||
print(''.join(['"upcoming_invoice": ', str(stripe_invoice), ','])) # nocoverage
|
||||
return stripe_invoice
|
||||
|
||||
@catch_stripe_errors
|
||||
def stripe_get_invoice_preview_for_downgrade(
|
||||
stripe_customer_id: str, stripe_subscription_id: str,
|
||||
stripe_subscriptionitem_id: str) -> stripe.Invoice:
|
||||
return stripe.Invoice.upcoming(
|
||||
customer=stripe_customer_id, subscription=stripe_subscription_id,
|
||||
subscription_items=[{'id': stripe_subscriptionitem_id, 'quantity': 0}])
|
||||
|
||||
def preview_invoice_total_for_downgrade(stripe_customer: stripe.Customer) -> int:
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
if stripe_subscription is None:
|
||||
# Most likely situation is: user A goes to billing page, user B
|
||||
# cancels subscription, user A clicks on "downgrade" or something
|
||||
# else that calls this function.
|
||||
billing_logger.error("Trying to extract subscription item that doesn't exist, for Stripe customer %s"
|
||||
% (stripe_customer.id,))
|
||||
raise BillingError('downgrade without subscription', BillingError.TRY_RELOADING)
|
||||
for item in stripe_subscription['items']:
|
||||
# There should only be one item, but we can't index into stripe_subscription['items']
|
||||
stripe_subscriptionitem_id = item.id
|
||||
return stripe_get_invoice_preview_for_downgrade(
|
||||
stripe_customer.id, stripe_subscription.id, stripe_subscriptionitem_id).total
|
||||
|
||||
# Return type should be Optional[stripe.Subscription], which throws a mypy error.
|
||||
# Will fix once we add type stubs for the Stripe API.
|
||||
def extract_current_subscription(stripe_customer: stripe.Customer) -> Any:
|
||||
if not stripe_customer.subscriptions:
|
||||
return None
|
||||
for stripe_subscription in stripe_customer.subscriptions:
|
||||
if stripe_subscription.status != "canceled":
|
||||
return stripe_subscription
|
||||
return None
|
||||
|
||||
@catch_stripe_errors
|
||||
def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None,
|
||||
coupon: Optional[Coupon]=None) -> stripe.Customer:
|
||||
def do_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
|
||||
realm = user.realm
|
||||
stripe_coupon_id = None
|
||||
if coupon is not None:
|
||||
stripe_coupon_id = coupon.stripe_coupon_id
|
||||
# We could do a better job of handling race conditions here, but if two
|
||||
# people from a realm try to upgrade at exactly the same time, the main
|
||||
# bad thing that will happen is that we will create an extra stripe
|
||||
@@ -174,10 +177,7 @@ def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None,
|
||||
description="%s (%s)" % (realm.string_id, realm.name),
|
||||
email=user.email,
|
||||
metadata={'realm_id': realm.id, 'realm_str': realm.string_id},
|
||||
source=stripe_token,
|
||||
coupon=stripe_coupon_id)
|
||||
if PRINT_STRIPE_FIXTURE_DATA:
|
||||
print(''.join(['"create_customer": ', str(stripe_customer), ','])) # nocoverage
|
||||
source=stripe_token)
|
||||
event_time = timestamp_to_datetime(stripe_customer.created)
|
||||
with transaction.atomic():
|
||||
RealmAuditLog.objects.create(
|
||||
@@ -187,10 +187,11 @@ def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None,
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_CARD_CHANGED,
|
||||
event_time=event_time)
|
||||
Customer.objects.create(realm=realm, stripe_customer_id=stripe_customer.id)
|
||||
customer, created = Customer.objects.update_or_create(realm=realm, defaults={
|
||||
'stripe_customer_id': stripe_customer.id})
|
||||
user.is_billing_admin = True
|
||||
user.save(update_fields=["is_billing_admin"])
|
||||
return stripe_customer
|
||||
return customer
|
||||
|
||||
@catch_stripe_errors
|
||||
def do_replace_payment_source(user: UserProfile, stripe_token: str) -> stripe.Customer:
|
||||
@@ -205,213 +206,262 @@ def do_replace_payment_source(user: UserProfile, stripe_token: str) -> stripe.Cu
|
||||
event_time=timezone_now())
|
||||
return updated_stripe_customer
|
||||
|
||||
@catch_stripe_errors
|
||||
def do_replace_coupon(user: UserProfile, coupon: Coupon) -> stripe.Customer:
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
stripe_customer.coupon = coupon.stripe_coupon_id
|
||||
return stripe.Customer.save(stripe_customer)
|
||||
# event_time should roughly be timezone_now(). Not designed to handle
|
||||
# event_times in the past or future
|
||||
# TODO handle downgrade
|
||||
def add_plan_renewal_to_license_ledger_if_needed(plan: CustomerPlan, event_time: datetime) -> LicenseLedger:
|
||||
last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by('-id').first()
|
||||
last_renewal = LicenseLedger.objects.filter(plan=plan, is_renewal=True) \
|
||||
.order_by('-id').first().event_time
|
||||
plan_renewal_date = next_renewal_date(plan, last_renewal)
|
||||
if plan_renewal_date <= event_time:
|
||||
return LicenseLedger.objects.create(
|
||||
plan=plan, is_renewal=True, event_time=plan_renewal_date,
|
||||
licenses=last_ledger_entry.licenses_at_next_renewal,
|
||||
licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
|
||||
return last_ledger_entry
|
||||
|
||||
# Returns Customer instead of stripe_customer so that we don't make a Stripe
|
||||
# API call if there's nothing to update
|
||||
def update_or_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
|
||||
realm = user.realm
|
||||
customer = Customer.objects.filter(realm=realm).first()
|
||||
if customer is None or customer.stripe_customer_id is None:
|
||||
return do_create_stripe_customer(user, stripe_token=stripe_token)
|
||||
if stripe_token is not None:
|
||||
do_replace_payment_source(user, stripe_token)
|
||||
return customer
|
||||
|
||||
def compute_plan_parameters(
|
||||
automanage_licenses: bool, billing_schedule: int,
|
||||
discount: Optional[Decimal]) -> Tuple[datetime, datetime, datetime, int]:
|
||||
# Everything in Stripe is stored as timestamps with 1 second resolution,
|
||||
# so standardize on 1 second resolution.
|
||||
# TODO talk about leapseconds?
|
||||
billing_cycle_anchor = timezone_now().replace(microsecond=0)
|
||||
if billing_schedule == CustomerPlan.ANNUAL:
|
||||
# TODO use variables to account for Zulip Plus
|
||||
price_per_license = 8000
|
||||
period_end = add_months(billing_cycle_anchor, 12)
|
||||
elif billing_schedule == CustomerPlan.MONTHLY:
|
||||
price_per_license = 800
|
||||
period_end = add_months(billing_cycle_anchor, 1)
|
||||
else:
|
||||
raise AssertionError('Unknown billing_schedule: {}'.format(billing_schedule))
|
||||
if discount is not None:
|
||||
# There are no fractional cents in Stripe, so round down to nearest integer.
|
||||
price_per_license = int(float(price_per_license * (1 - discount / 100)) + .00001)
|
||||
next_invoice_date = period_end
|
||||
if automanage_licenses:
|
||||
next_invoice_date = add_months(billing_cycle_anchor, 1)
|
||||
return billing_cycle_anchor, next_invoice_date, period_end, price_per_license
|
||||
|
||||
# Only used for cloud signups
|
||||
@catch_stripe_errors
|
||||
def do_subscribe_customer_to_plan(user: UserProfile, stripe_customer: stripe.Customer, stripe_plan_id: str,
|
||||
seat_count: int, tax_percent: float) -> None:
|
||||
if extract_current_subscription(stripe_customer) is not None:
|
||||
# Most likely due to two people in the org going to the billing page,
|
||||
# and then both upgrading their plan. We don't send clients
|
||||
# real-time event updates for the billing pages, so this is more
|
||||
# likely than it would be in other parts of the app.
|
||||
billing_logger.error("Stripe customer %s trying to subscribe to %s, "
|
||||
"but has an active subscription" % (stripe_customer.id, stripe_plan_id))
|
||||
def process_initial_upgrade(user: UserProfile, licenses: int, automanage_licenses: bool,
|
||||
billing_schedule: int, stripe_token: Optional[str]) -> None:
|
||||
realm = user.realm
|
||||
customer = update_or_create_stripe_customer(user, stripe_token=stripe_token)
|
||||
if CustomerPlan.objects.filter(customer=customer, status=CustomerPlan.ACTIVE).exists():
|
||||
# Unlikely race condition from two people upgrading (clicking "Make payment")
|
||||
# at exactly the same time. Doesn't fully resolve the race condition, but having
|
||||
# a check here reduces the likelihood.
|
||||
billing_logger.warning(
|
||||
"Customer {} trying to upgrade, but has an active subscription".format(customer))
|
||||
raise BillingError('subscribing with existing subscription', BillingError.TRY_RELOADING)
|
||||
customer = Customer.objects.get(stripe_customer_id=stripe_customer.id)
|
||||
# Note that there is a race condition here, where if two users upgrade at exactly the
|
||||
# same time, they will have two subscriptions, and get charged twice. We could try to
|
||||
# reduce the chance of it with a well-designed idempotency_key, but it's not easy since
|
||||
# we also need to be careful not to block the customer from retrying if their
|
||||
# subscription attempt fails (e.g. due to insufficient funds).
|
||||
|
||||
# Success here implies the stripe_customer was charged: https://stripe.com/docs/billing/lifecycle#active
|
||||
# Otherwise we should expect it to throw a stripe.error.
|
||||
stripe_subscription = stripe.Subscription.create(
|
||||
customer=stripe_customer.id,
|
||||
billing='charge_automatically',
|
||||
items=[{
|
||||
'plan': stripe_plan_id,
|
||||
'quantity': seat_count,
|
||||
}],
|
||||
prorate=True,
|
||||
tax_percent=tax_percent)
|
||||
if PRINT_STRIPE_FIXTURE_DATA:
|
||||
print(''.join(['"create_subscription": ', str(stripe_subscription), ','])) # nocoverage
|
||||
billing_cycle_anchor, next_invoice_date, period_end, price_per_license = compute_plan_parameters(
|
||||
automanage_licenses, billing_schedule, customer.default_discount)
|
||||
# The main design constraint in this function is that if you upgrade with a credit card, and the
|
||||
# charge fails, everything should be rolled back as if nothing had happened. This is because we
|
||||
# expect frequent card failures on initial signup.
|
||||
# Hence, if we're going to charge a card, do it at the beginning, even if we later may have to
|
||||
# adjust the number of licenses.
|
||||
charge_automatically = stripe_token is not None
|
||||
if charge_automatically:
|
||||
stripe_charge = stripe.Charge.create(
|
||||
amount=price_per_license * licenses,
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description="Upgrade to Zulip Standard, ${} x {}".format(price_per_license/100, licenses),
|
||||
receipt_email=user.email,
|
||||
statement_descriptor='Zulip Standard')
|
||||
# Not setting a period start and end, but maybe we should? Unclear what will make things
|
||||
# most similar to the renewal case from an accounting perspective.
|
||||
stripe.InvoiceItem.create(
|
||||
amount=price_per_license * licenses * -1,
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description="Payment (Card ending in {})".format(cast(stripe.Card, stripe_charge.source).last4),
|
||||
discountable=False)
|
||||
|
||||
# TODO: The correctness of this relies on user creation, deactivation, etc being
|
||||
# in a transaction.atomic() with the relevant RealmAuditLog entries
|
||||
with transaction.atomic():
|
||||
customer.has_billing_relationship = True
|
||||
customer.save(update_fields=['has_billing_relationship'])
|
||||
customer.realm.has_seat_based_plan = True
|
||||
customer.realm.save(update_fields=['has_seat_based_plan'])
|
||||
# billed_licenses can greater than licenses if users are added between the start of
|
||||
# this function (process_initial_upgrade) and now
|
||||
billed_licenses = max(get_seat_count(realm), licenses)
|
||||
plan_params = {
|
||||
'automanage_licenses': automanage_licenses,
|
||||
'charge_automatically': charge_automatically,
|
||||
'price_per_license': price_per_license,
|
||||
'discount': customer.default_discount,
|
||||
'billing_cycle_anchor': billing_cycle_anchor,
|
||||
'billing_schedule': billing_schedule,
|
||||
'tier': CustomerPlan.STANDARD}
|
||||
plan = CustomerPlan.objects.create(
|
||||
customer=customer,
|
||||
next_invoice_date=next_invoice_date,
|
||||
**plan_params)
|
||||
ledger_entry = LicenseLedger.objects.create(
|
||||
plan=plan,
|
||||
is_renewal=True,
|
||||
event_time=billing_cycle_anchor,
|
||||
licenses=billed_licenses,
|
||||
licenses_at_next_renewal=billed_licenses)
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.save(update_fields=['invoiced_through'])
|
||||
RealmAuditLog.objects.create(
|
||||
realm=customer.realm,
|
||||
acting_user=user,
|
||||
event_type=RealmAuditLog.STRIPE_PLAN_CHANGED,
|
||||
event_time=timestamp_to_datetime(stripe_subscription.created),
|
||||
extra_data=ujson.dumps({'plan': stripe_plan_id, 'quantity': seat_count}))
|
||||
realm=realm, acting_user=user, event_time=billing_cycle_anchor,
|
||||
event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED,
|
||||
extra_data=ujson.dumps(plan_params))
|
||||
stripe.InvoiceItem.create(
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description='Zulip Standard',
|
||||
discountable=False,
|
||||
period = {'start': datetime_to_timestamp(billing_cycle_anchor),
|
||||
'end': datetime_to_timestamp(period_end)},
|
||||
quantity=billed_licenses,
|
||||
unit_amount=price_per_license)
|
||||
|
||||
current_seat_count = get_seat_count(customer.realm)
|
||||
if seat_count != current_seat_count:
|
||||
RealmAuditLog.objects.create(
|
||||
realm=customer.realm,
|
||||
event_type=RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET,
|
||||
event_time=timestamp_to_datetime(stripe_subscription.created),
|
||||
requires_billing_update=True,
|
||||
extra_data=ujson.dumps({'quantity': current_seat_count}))
|
||||
if charge_automatically:
|
||||
billing_method = 'charge_automatically'
|
||||
days_until_due = None
|
||||
else:
|
||||
billing_method = 'send_invoice'
|
||||
days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE
|
||||
stripe_invoice = stripe.Invoice.create(
|
||||
auto_advance=True,
|
||||
billing=billing_method,
|
||||
customer=customer.stripe_customer_id,
|
||||
days_until_due=days_until_due,
|
||||
statement_descriptor='Zulip Standard')
|
||||
stripe.Invoice.finalize_invoice(stripe_invoice)
|
||||
|
||||
def process_initial_upgrade(user: UserProfile, plan: Plan, seat_count: int, stripe_token: str) -> None:
|
||||
customer = Customer.objects.filter(realm=user.realm).first()
|
||||
from zerver.lib.actions import do_change_plan_type
|
||||
do_change_plan_type(realm, Realm.STANDARD)
|
||||
|
||||
def update_license_ledger_for_automanaged_plan(realm: Realm, plan: CustomerPlan,
|
||||
event_time: datetime) -> None:
|
||||
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
# todo: handle downgrade, where licenses_at_next_renewal should be 0
|
||||
licenses_at_next_renewal = get_seat_count(realm)
|
||||
licenses = max(licenses_at_next_renewal, last_ledger_entry.licenses)
|
||||
LicenseLedger.objects.create(
|
||||
plan=plan, event_time=event_time, licenses=licenses,
|
||||
licenses_at_next_renewal=licenses_at_next_renewal)
|
||||
|
||||
def update_license_ledger_if_needed(realm: Realm, event_time: datetime) -> None:
|
||||
customer = Customer.objects.filter(realm=realm).first()
|
||||
if customer is None:
|
||||
stripe_customer = do_create_customer(user, stripe_token=stripe_token)
|
||||
else:
|
||||
stripe_customer = do_replace_payment_source(user, stripe_token)
|
||||
do_subscribe_customer_to_plan(
|
||||
user=user,
|
||||
stripe_customer=stripe_customer,
|
||||
stripe_plan_id=plan.stripe_plan_id,
|
||||
seat_count=seat_count,
|
||||
# TODO: billing address details are passed to us in the request;
|
||||
# use that to calculate taxes.
|
||||
tax_percent=0)
|
||||
do_change_plan_type(user, Realm.STANDARD)
|
||||
return
|
||||
plan = get_active_plan(customer)
|
||||
if plan is None:
|
||||
return
|
||||
if not plan.automanage_licenses:
|
||||
return
|
||||
update_license_ledger_for_automanaged_plan(realm, plan, event_time)
|
||||
|
||||
def attach_discount_to_realm(user: UserProfile, percent_off: int) -> None:
|
||||
coupon = Coupon.objects.get(percent_off=percent_off)
|
||||
customer = Customer.objects.filter(realm=user.realm).first()
|
||||
if customer is None:
|
||||
do_create_customer(user, coupon=coupon)
|
||||
else:
|
||||
do_replace_coupon(user, coupon)
|
||||
|
||||
@catch_stripe_errors
|
||||
def process_downgrade(user: UserProfile) -> None:
|
||||
stripe_customer = stripe_get_customer(
|
||||
Customer.objects.filter(realm=user.realm).first().stripe_customer_id)
|
||||
subscription_balance = preview_invoice_total_for_downgrade(stripe_customer)
|
||||
# If subscription_balance > 0, they owe us money. This is likely due to
|
||||
# people they added in the last day, so we can just forgive it.
|
||||
# Stripe automatically forgives it when we delete the subscription, so nothing we need to do there.
|
||||
if subscription_balance < 0:
|
||||
stripe_customer.account_balance = stripe_customer.account_balance + subscription_balance
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
# Wish these two could be transaction.atomic
|
||||
stripe_subscription = stripe_subscription.delete()
|
||||
stripe.Customer.save(stripe_customer)
|
||||
with transaction.atomic():
|
||||
user.realm.has_seat_based_plan = False
|
||||
user.realm.save(update_fields=['has_seat_based_plan'])
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm,
|
||||
acting_user=user,
|
||||
event_type=RealmAuditLog.STRIPE_PLAN_CHANGED,
|
||||
event_time=timestamp_to_datetime(stripe_subscription.canceled_at),
|
||||
extra_data=ujson.dumps({'plan': None, 'quantity': stripe_subscription.quantity}))
|
||||
# Doing this last, since it results in user-visible confirmation (via
|
||||
# product changes) that the downgrade succeeded.
|
||||
# Keeping it out of the transaction.atomic block because it will
|
||||
# eventually have a lot of stuff going on.
|
||||
do_change_plan_type(user, Realm.LIMITED)
|
||||
|
||||
## Process RealmAuditLog
|
||||
|
||||
def do_set_subscription_quantity(
|
||||
customer: Customer, timestamp: int, idempotency_key: str, quantity: int) -> None:
|
||||
stripe_customer = stripe_get_customer(customer.stripe_customer_id)
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
stripe_subscription.quantity = quantity
|
||||
stripe_subscription.proration_date = timestamp
|
||||
stripe_subscription.save(idempotency_key=idempotency_key)
|
||||
|
||||
def do_adjust_subscription_quantity(
|
||||
customer: Customer, timestamp: int, idempotency_key: str, delta: int) -> None:
|
||||
stripe_customer = stripe_get_customer(customer.stripe_customer_id)
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
stripe_subscription.quantity = stripe_subscription.quantity + delta
|
||||
stripe_subscription.proration_date = timestamp
|
||||
stripe_subscription.save(idempotency_key=idempotency_key)
|
||||
|
||||
def increment_subscription_quantity(
|
||||
customer: Customer, timestamp: int, idempotency_key: str) -> None:
|
||||
return do_adjust_subscription_quantity(customer, timestamp, idempotency_key, 1)
|
||||
|
||||
def decrement_subscription_quantity(
|
||||
customer: Customer, timestamp: int, idempotency_key: str) -> None:
|
||||
return do_adjust_subscription_quantity(customer, timestamp, idempotency_key, -1)
|
||||
|
||||
@catch_stripe_errors
|
||||
def process_billing_log_entry(processor: BillingProcessor, log_row: RealmAuditLog) -> None:
|
||||
processor.state = BillingProcessor.STARTED
|
||||
processor.log_row = log_row
|
||||
processor.save()
|
||||
|
||||
customer = Customer.objects.get(realm=log_row.realm)
|
||||
timestamp = datetime_to_timestamp(log_row.event_time)
|
||||
idempotency_key = 'process_billing_log_entry:%s' % (log_row.id,)
|
||||
extra_args = {} # type: Dict[str, Any]
|
||||
if log_row.extra_data is not None:
|
||||
extra_args = ujson.loads(log_row.extra_data)
|
||||
processing_functions = {
|
||||
RealmAuditLog.STRIPE_PLAN_QUANTITY_RESET: do_set_subscription_quantity,
|
||||
RealmAuditLog.USER_CREATED: increment_subscription_quantity,
|
||||
RealmAuditLog.USER_ACTIVATED: increment_subscription_quantity,
|
||||
RealmAuditLog.USER_DEACTIVATED: decrement_subscription_quantity,
|
||||
RealmAuditLog.USER_REACTIVATED: increment_subscription_quantity,
|
||||
} # type: Dict[str, Callable[..., None]]
|
||||
processing_functions[log_row.event_type](customer, timestamp, idempotency_key, **extra_args)
|
||||
|
||||
processor.state = BillingProcessor.DONE
|
||||
processor.save()
|
||||
|
||||
def get_next_billing_log_entry(processor: BillingProcessor) -> Optional[RealmAuditLog]:
|
||||
if processor.state == BillingProcessor.STARTED:
|
||||
return processor.log_row
|
||||
assert processor.state != BillingProcessor.STALLED
|
||||
if processor.state not in [BillingProcessor.DONE, BillingProcessor.SKIPPED]:
|
||||
raise BillingError(
|
||||
'unknown processor state',
|
||||
"Check for typos, since this value is sometimes set by hand: %s" % (processor.state,))
|
||||
|
||||
if processor.realm is None:
|
||||
realms_with_processors = BillingProcessor.objects.exclude(
|
||||
realm=None).values_list('realm', flat=True)
|
||||
query = RealmAuditLog.objects.exclude(realm__in=realms_with_processors)
|
||||
else:
|
||||
global_processor = BillingProcessor.objects.get(realm=None)
|
||||
query = RealmAuditLog.objects.filter(
|
||||
realm=processor.realm, id__lt=global_processor.log_row.id)
|
||||
return query.filter(id__gt=processor.log_row.id,
|
||||
requires_billing_update=True).order_by('id').first()
|
||||
|
||||
def run_billing_processor_one_step(processor: BillingProcessor) -> bool:
|
||||
# Returns True if a row was processed, or if processing was attempted
|
||||
log_row = get_next_billing_log_entry(processor)
|
||||
if log_row is None:
|
||||
if processor.realm is not None:
|
||||
processor.delete()
|
||||
return False
|
||||
try:
|
||||
process_billing_log_entry(processor, log_row)
|
||||
return True
|
||||
except Exception as e:
|
||||
# Possible errors include processing subscription quantity entries
|
||||
# after downgrade, since the downgrade code doesn't check that
|
||||
# billing processor is up to date
|
||||
billing_logger.error("Error on log_row.realm=%s, event_type=%s, log_row.id=%s, "
|
||||
"processor.id=%s, processor.realm=%s" % (
|
||||
processor.log_row.realm.string_id, processor.log_row.event_type,
|
||||
processor.log_row.id, processor.id, processor.realm))
|
||||
if isinstance(e, StripeCardError):
|
||||
if processor.realm is None:
|
||||
BillingProcessor.objects.create(log_row=processor.log_row,
|
||||
realm=processor.log_row.realm,
|
||||
state=BillingProcessor.STALLED)
|
||||
processor.state = BillingProcessor.SKIPPED
|
||||
def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
|
||||
if plan.invoicing_status == CustomerPlan.STARTED:
|
||||
raise NotImplementedError('Plan with invoicing_status==STARTED needs manual resolution.')
|
||||
add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
assert(plan.invoiced_through is not None)
|
||||
licenses_base = plan.invoiced_through.licenses
|
||||
invoice_item_created = False
|
||||
for ledger_entry in LicenseLedger.objects.filter(plan=plan, id__gt=plan.invoiced_through.id,
|
||||
event_time__lte=event_time).order_by('id'):
|
||||
price_args = {} # type: Dict[str, int]
|
||||
if ledger_entry.is_renewal:
|
||||
if plan.fixed_price is not None:
|
||||
price_args = {'amount': plan.fixed_price}
|
||||
else:
|
||||
processor.state = BillingProcessor.STALLED
|
||||
processor.save()
|
||||
return True
|
||||
raise
|
||||
assert(plan.price_per_license is not None) # needed for mypy
|
||||
price_args = {'unit_amount': plan.price_per_license,
|
||||
'quantity': ledger_entry.licenses}
|
||||
description = "Zulip Standard - renewal"
|
||||
elif ledger_entry.licenses != licenses_base:
|
||||
assert(plan.price_per_license)
|
||||
last_renewal = LicenseLedger.objects.filter(
|
||||
plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time) \
|
||||
.order_by('-id').first().event_time
|
||||
period_end = next_renewal_date(plan, ledger_entry.event_time)
|
||||
proration_fraction = (period_end - ledger_entry.event_time) / (period_end - last_renewal)
|
||||
price_args = {'unit_amount': int(plan.price_per_license * proration_fraction + .5),
|
||||
'quantity': ledger_entry.licenses - licenses_base}
|
||||
description = "Additional license ({} - {})".format(
|
||||
ledger_entry.event_time.strftime('%b %-d, %Y'), period_end.strftime('%b %-d, %Y'))
|
||||
|
||||
if price_args:
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.invoicing_status = CustomerPlan.STARTED
|
||||
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
|
||||
idempotency_key = 'ledger_entry:{}'.format(ledger_entry.id) # type: Optional[str]
|
||||
if settings.TEST_SUITE:
|
||||
idempotency_key = None
|
||||
stripe.InvoiceItem.create(
|
||||
currency='usd',
|
||||
customer=plan.customer.stripe_customer_id,
|
||||
description=description,
|
||||
discountable=False,
|
||||
period = {'start': datetime_to_timestamp(ledger_entry.event_time),
|
||||
'end': datetime_to_timestamp(next_renewal_date(plan, ledger_entry.event_time))},
|
||||
idempotency_key=idempotency_key,
|
||||
**price_args)
|
||||
invoice_item_created = True
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.invoicing_status = CustomerPlan.DONE
|
||||
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
|
||||
licenses_base = ledger_entry.licenses
|
||||
|
||||
if invoice_item_created:
|
||||
if plan.charge_automatically:
|
||||
billing_method = 'charge_automatically'
|
||||
days_until_due = None
|
||||
else:
|
||||
billing_method = 'send_invoice'
|
||||
days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE
|
||||
stripe_invoice = stripe.Invoice.create(
|
||||
auto_advance=True,
|
||||
billing=billing_method,
|
||||
customer=plan.customer.stripe_customer_id,
|
||||
days_until_due=days_until_due,
|
||||
statement_descriptor='Zulip Standard')
|
||||
stripe.Invoice.finalize_invoice(stripe_invoice)
|
||||
|
||||
plan.next_invoice_date = next_invoice_date(plan)
|
||||
plan.save(update_fields=['next_invoice_date'])
|
||||
|
||||
def invoice_plans_as_needed(event_time: datetime) -> None:
|
||||
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
|
||||
invoice_plan(plan, event_time)
|
||||
|
||||
def attach_discount_to_realm(realm: Realm, discount: Decimal) -> None:
|
||||
Customer.objects.update_or_create(realm=realm, defaults={'default_discount': discount})
|
||||
|
||||
def process_downgrade(user: UserProfile) -> None: # nocoverage
|
||||
pass
|
||||
|
||||
def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverage
|
||||
annual_revenue = {}
|
||||
for plan in CustomerPlan.objects.filter(
|
||||
status=CustomerPlan.ACTIVE).select_related('customer__realm'):
|
||||
# TODO: figure out what to do for plans that don't automatically
|
||||
# renew, but which probably will renew
|
||||
renewal_cents = renewal_amount(plan, timezone_now()) or 0
|
||||
if plan.billing_schedule == CustomerPlan.MONTHLY:
|
||||
renewal_cents *= 12
|
||||
# TODO: Decimal stuff
|
||||
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
|
||||
return annual_revenue
|
||||
|
@@ -1,47 +0,0 @@
|
||||
"""\
|
||||
Run BillingProcessors.
|
||||
|
||||
This management command is run via supervisor. Do not run on multiple
|
||||
machines, as the code has not been made robust to race conditions from doing
|
||||
so. (Alternatively, you can set `BILLING_PROCESSOR_ENABLED=False` on all but
|
||||
one machine to make the command have no effect.)
|
||||
"""
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from zerver.lib.context_managers import lockfile
|
||||
from zerver.lib.management import sleep_forever
|
||||
from corporate.lib.stripe import StripeConnectionError, \
|
||||
run_billing_processor_one_step
|
||||
from corporate.models import BillingProcessor
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """Run BillingProcessors, to sync billing-relevant updates into Stripe.
|
||||
|
||||
Run this command under supervisor.
|
||||
|
||||
Usage: ./manage.py process_billing_updates
|
||||
"""
|
||||
|
||||
def handle(self, *args: Any, **options: Any) -> None:
|
||||
if not settings.BILLING_PROCESSOR_ENABLED:
|
||||
sleep_forever()
|
||||
|
||||
with lockfile("/tmp/zulip_billing_processor.lockfile"):
|
||||
while True:
|
||||
for processor in BillingProcessor.objects.exclude(
|
||||
state=BillingProcessor.STALLED):
|
||||
try:
|
||||
entry_processed = run_billing_processor_one_step(processor)
|
||||
except StripeConnectionError:
|
||||
time.sleep(5*60)
|
||||
# Less load on the db during times of activity
|
||||
# and more responsiveness when the load is low
|
||||
if entry_processed:
|
||||
time.sleep(10)
|
||||
else:
|
||||
time.sleep(2)
|
@@ -1,55 +0,0 @@
|
||||
from zerver.lib.management import ZulipBaseCommand
|
||||
from corporate.models import Plan, Coupon, Customer
|
||||
from zproject.settings import get_secret
|
||||
|
||||
from typing import Any
|
||||
|
||||
import stripe
|
||||
stripe.api_key = get_secret('stripe_secret_key')
|
||||
|
||||
class Command(ZulipBaseCommand):
|
||||
help = """Script to add the appropriate products and plans to Stripe."""
|
||||
|
||||
def handle(self, *args: Any, **options: Any) -> None:
|
||||
Customer.objects.all().delete()
|
||||
Plan.objects.all().delete()
|
||||
Coupon.objects.all().delete()
|
||||
|
||||
# Zulip Cloud offerings
|
||||
product = stripe.Product.create(
|
||||
name="Zulip Cloud Standard",
|
||||
type='service',
|
||||
statement_descriptor="Zulip Cloud Standard",
|
||||
unit_label="user")
|
||||
|
||||
plan = stripe.Plan.create(
|
||||
currency='usd',
|
||||
interval='month',
|
||||
product=product.id,
|
||||
amount=800,
|
||||
billing_scheme='per_unit',
|
||||
nickname=Plan.CLOUD_MONTHLY,
|
||||
usage_type='licensed')
|
||||
Plan.objects.create(nickname=Plan.CLOUD_MONTHLY, stripe_plan_id=plan.id)
|
||||
|
||||
plan = stripe.Plan.create(
|
||||
currency='usd',
|
||||
interval='year',
|
||||
product=product.id,
|
||||
amount=8000,
|
||||
billing_scheme='per_unit',
|
||||
nickname=Plan.CLOUD_ANNUAL,
|
||||
usage_type='licensed')
|
||||
Plan.objects.create(nickname=Plan.CLOUD_ANNUAL, stripe_plan_id=plan.id)
|
||||
|
||||
coupon = stripe.Coupon.create(
|
||||
duration='forever',
|
||||
name='25% discount',
|
||||
percent_off=25)
|
||||
Coupon.objects.create(percent_off=25, stripe_coupon_id=coupon.id)
|
||||
|
||||
coupon = stripe.Coupon.create(
|
||||
duration='forever',
|
||||
name='85% discount',
|
||||
percent_off=85)
|
||||
Coupon.objects.create(percent_off=85, stripe_coupon_id=coupon.id)
|
20
corporate/migrations/0002_customer_default_discount.py
Normal file
20
corporate/migrations/0002_customer_default_discount.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-12 20:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customer',
|
||||
name='default_discount',
|
||||
field=models.DecimalField(decimal_places=4, max_digits=7, null=True),
|
||||
),
|
||||
]
|
35
corporate/migrations/0003_customerplan.py
Normal file
35
corporate/migrations/0003_customerplan.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-22 21:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0002_customer_default_discount'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomerPlan',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('licenses', models.IntegerField()),
|
||||
('automanage_licenses', models.BooleanField(default=False)),
|
||||
('charge_automatically', models.BooleanField(default=False)),
|
||||
('price_per_license', models.IntegerField(null=True)),
|
||||
('fixed_price', models.IntegerField(null=True)),
|
||||
('discount', models.DecimalField(decimal_places=4, max_digits=6, null=True)),
|
||||
('billing_cycle_anchor', models.DateTimeField()),
|
||||
('billing_schedule', models.SmallIntegerField()),
|
||||
('billed_through', models.DateTimeField()),
|
||||
('next_billing_date', models.DateTimeField(db_index=True)),
|
||||
('tier', models.SmallIntegerField()),
|
||||
('status', models.SmallIntegerField(default=1)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='corporate.Customer')),
|
||||
],
|
||||
),
|
||||
]
|
27
corporate/migrations/0004_licenseledger.py
Normal file
27
corporate/migrations/0004_licenseledger.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-19 05:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0003_customerplan'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LicenseLedger',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_renewal', models.BooleanField(default=False)),
|
||||
('event_time', models.DateTimeField()),
|
||||
('licenses', models.IntegerField()),
|
||||
('licenses_at_next_renewal', models.IntegerField(null=True)),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='corporate.CustomerPlan')),
|
||||
],
|
||||
),
|
||||
]
|
35
corporate/migrations/0005_customerplan_invoicing.py
Normal file
35
corporate/migrations/0005_customerplan_invoicing.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-28 13:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0004_licenseledger'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='customerplan',
|
||||
old_name='next_billing_date',
|
||||
new_name='next_invoice_date',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customerplan',
|
||||
name='billed_through',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerplan',
|
||||
name='invoiced_through',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='corporate.LicenseLedger'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerplan',
|
||||
name='invoicing_status',
|
||||
field=models.SmallIntegerField(default=1),
|
||||
),
|
||||
]
|
20
corporate/migrations/0006_nullable_stripe_customer_id.py
Normal file
20
corporate/migrations/0006_nullable_stripe_customer_id.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-29 01:46
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0005_customerplan_invoicing'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customer',
|
||||
name='stripe_customer_id',
|
||||
field=models.CharField(max_length=255, null=True, unique=True),
|
||||
),
|
||||
]
|
40
corporate/migrations/0007_remove_deprecated_fields.py
Normal file
40
corporate/migrations/0007_remove_deprecated_fields.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-31 22:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0006_nullable_stripe_customer_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='billingprocessor',
|
||||
name='log_row',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='billingprocessor',
|
||||
name='realm',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Coupon',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Plan',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customer',
|
||||
name='has_billing_relationship',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customerplan',
|
||||
name='licenses',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='BillingProcessor',
|
||||
),
|
||||
]
|
@@ -1,46 +1,70 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import CASCADE
|
||||
|
||||
from zerver.models import Realm, RealmAuditLog
|
||||
from zerver.models import Realm
|
||||
|
||||
class Customer(models.Model):
|
||||
realm = models.OneToOneField(Realm, on_delete=models.CASCADE) # type: Realm
|
||||
stripe_customer_id = models.CharField(max_length=255, unique=True) # type: str
|
||||
# Becomes True the first time a payment successfully goes through, and never
|
||||
# goes back to being False
|
||||
has_billing_relationship = models.BooleanField(default=False) # type: bool
|
||||
realm = models.OneToOneField(Realm, on_delete=CASCADE) # type: Realm
|
||||
stripe_customer_id = models.CharField(max_length=255, null=True, unique=True) # type: str
|
||||
# A percentage, like 85.
|
||||
default_discount = models.DecimalField(decimal_places=4, max_digits=7, null=True) # type: Optional[Decimal]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "<Customer %s %s>" % (self.realm, self.stripe_customer_id)
|
||||
|
||||
class Plan(models.Model):
|
||||
# The two possible values for nickname
|
||||
CLOUD_MONTHLY = 'monthly'
|
||||
CLOUD_ANNUAL = 'annual'
|
||||
nickname = models.CharField(max_length=40, unique=True) # type: str
|
||||
class CustomerPlan(models.Model):
|
||||
customer = models.ForeignKey(Customer, on_delete=CASCADE) # type: Customer
|
||||
automanage_licenses = models.BooleanField(default=False) # type: bool
|
||||
charge_automatically = models.BooleanField(default=False) # type: bool
|
||||
|
||||
stripe_plan_id = models.CharField(max_length=255, unique=True) # type: str
|
||||
# Both of these are in cents. Exactly one of price_per_license or
|
||||
# fixed_price should be set. fixed_price is only for manual deals, and
|
||||
# can't be set via the self-serve billing system.
|
||||
price_per_license = models.IntegerField(null=True) # type: Optional[int]
|
||||
fixed_price = models.IntegerField(null=True) # type: Optional[int]
|
||||
|
||||
class Coupon(models.Model):
|
||||
percent_off = models.SmallIntegerField(unique=True) # type: int
|
||||
stripe_coupon_id = models.CharField(max_length=255, unique=True) # type: str
|
||||
# Discount that was applied. For display purposes only.
|
||||
discount = models.DecimalField(decimal_places=4, max_digits=6, null=True) # type: Optional[Decimal]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '<Coupon: %s %s %s>' % (self.percent_off, self.stripe_coupon_id, self.id)
|
||||
billing_cycle_anchor = models.DateTimeField() # type: datetime.datetime
|
||||
ANNUAL = 1
|
||||
MONTHLY = 2
|
||||
billing_schedule = models.SmallIntegerField() # type: int
|
||||
|
||||
class BillingProcessor(models.Model):
|
||||
log_row = models.ForeignKey(RealmAuditLog, on_delete=models.CASCADE) # RealmAuditLog
|
||||
# Exactly one processor, the global processor, has realm=None.
|
||||
realm = models.OneToOneField(Realm, null=True, on_delete=models.CASCADE) # type: Realm
|
||||
next_invoice_date = models.DateTimeField(db_index=True) # type: datetime.datetime
|
||||
invoiced_through = models.ForeignKey(
|
||||
'LicenseLedger', null=True, on_delete=CASCADE, related_name='+') # type: Optional[LicenseLedger]
|
||||
DONE = 1
|
||||
STARTED = 2
|
||||
invoicing_status = models.SmallIntegerField(default=DONE) # type: int
|
||||
|
||||
DONE = 'done'
|
||||
STARTED = 'started'
|
||||
SKIPPED = 'skipped' # global processor only
|
||||
STALLED = 'stalled' # realm processors only
|
||||
state = models.CharField(max_length=20) # type: str
|
||||
STANDARD = 1
|
||||
PLUS = 2 # not available through self-serve signup
|
||||
ENTERPRISE = 10
|
||||
tier = models.SmallIntegerField() # type: int
|
||||
|
||||
last_modified = models.DateTimeField(auto_now=True) # type: datetime.datetime
|
||||
ACTIVE = 1
|
||||
ENDED = 2
|
||||
NEVER_STARTED = 3
|
||||
# You can only have 1 active subscription at a time
|
||||
status = models.SmallIntegerField(default=ACTIVE) # type: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '<BillingProcessor: %s %s %s>' % (self.realm, self.log_row, self.id)
|
||||
# TODO maybe override setattr to ensure billing_cycle_anchor, etc are immutable
|
||||
|
||||
def get_active_plan(customer: Customer) -> Optional[CustomerPlan]:
|
||||
return CustomerPlan.objects.filter(customer=customer, status=CustomerPlan.ACTIVE).first()
|
||||
|
||||
class LicenseLedger(models.Model):
|
||||
plan = models.ForeignKey(CustomerPlan, on_delete=CASCADE) # type: CustomerPlan
|
||||
# Also True for the initial upgrade.
|
||||
is_renewal = models.BooleanField(default=False) # type: bool
|
||||
event_time = models.DateTimeField() # type: datetime.datetime
|
||||
licenses = models.IntegerField() # type: int
|
||||
# None means the plan does not automatically renew.
|
||||
# 0 means the plan has been explicitly downgraded.
|
||||
# This cannot be None if plan.automanage_licenses.
|
||||
licenses_at_next_renewal = models.IntegerField(null=True) # type: Optional[int]
|
||||
|
@@ -1,411 +0,0 @@
|
||||
{
|
||||
"create_customer": {
|
||||
"account_balance": 0,
|
||||
"created": 1529990750,
|
||||
"currency": null,
|
||||
"default_source": "card_1Ch9gVGh0CmXqmnwv94RombT",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": {
|
||||
"coupon": {
|
||||
"amount_off": null,
|
||||
"created": 1535002820,
|
||||
"currency": null,
|
||||
"duration": "forever",
|
||||
"duration_in_months": null,
|
||||
"id": "rncBblSZ",
|
||||
"livemode": false,
|
||||
"max_redemptions": null,
|
||||
"metadata": {},
|
||||
"name": "85% discount",
|
||||
"object": "coupon",
|
||||
"percent_off": 85.0,
|
||||
"redeem_by": null,
|
||||
"times_redeemed": 1,
|
||||
"valid": true
|
||||
},
|
||||
"customer": "cus_DT7pd3yW0w8lF1",
|
||||
"end": null,
|
||||
"object": "discount",
|
||||
"start": 1535004909,
|
||||
"subscription": null
|
||||
},
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_D7OT2jf5YAtZQL",
|
||||
"invoice_prefix": "23ABC45",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": "FL",
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1Ch9gVGh0CmXqmnwv94RombT",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_D7OT2jf5YAtZQL/sources"
|
||||
},
|
||||
"subscriptions": {}
|
||||
},
|
||||
"create_subscription": {
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1529990751,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1529990751,
|
||||
"current_period_end": 1561526751,
|
||||
"current_period_start": 1529990751,
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_D7OTT8FZbOPxah",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1529990751,
|
||||
"id": "si_D7OTEItF5ZLN2R",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1529987890,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_D7NhmicJvX2edE",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_D7OTT8FZbOPxah"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_D7OTT8FZbOPxah"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1529987890,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_D7NhmicJvX2edE",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1529990751,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
},
|
||||
"customer_with_subscription": {
|
||||
"account_balance": 0,
|
||||
"created": 1529990750,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": "FL",
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1Ch9gVGh0CmXqmnwv94RombT",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_D7OT2jf5YAtZQL",
|
||||
"invoice_prefix": "23ABC45",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": "FL",
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1Ch9gVGh0CmXqmnwv94RombT",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_D7OT2jf5YAtZQL/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1529990751,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1529990751,
|
||||
"current_period_end": 1561526751,
|
||||
"current_period_start": 1529990751,
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_D7OTT8FZbOPxah",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1529990751,
|
||||
"id": "si_D7OTEItF5ZLN2R",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1529987890,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_D7NhmicJvX2edE",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_D7OTT8FZbOPxah"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_D7OTT8FZbOPxah"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1529987890,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_D7NhmicJvX2edE",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1529990751,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_D7OT2jf5YAtZQL/subscriptions"
|
||||
}
|
||||
},
|
||||
"upcoming_invoice": {
|
||||
"amount_due": 64000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 64000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "upcoming",
|
||||
"charge": null,
|
||||
"closed": false,
|
||||
"currency": "usd",
|
||||
"customer": "cus_D7OT2jf5YAtZQL",
|
||||
"date": 1561526751,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"forgiven": false,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "8 user \u00d7 Zulip Cloud Standard (at $80.00 / year)",
|
||||
"discountable": true,
|
||||
"id": "sub_D7OTT8FZbOPxah",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1593149151,
|
||||
"start": 1561526751
|
||||
},
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1529987890,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_D7NhmicJvX2edE",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"subscription_item": "si_D7OTEItF5ZLN2R",
|
||||
"type": "subscription"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/upcoming/lines?customer=cus_D7OT2jf5YAtZQL"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1561530351,
|
||||
"number": "23ABC45-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1561526751,
|
||||
"period_start": 1529990751,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": null,
|
||||
"subscription": "sub_D7OTT8FZbOPxah",
|
||||
"subtotal": 64000,
|
||||
"tax": 0,
|
||||
"tax_percent": 0.0,
|
||||
"total": 64000,
|
||||
"webhooks_delivered_at": null
|
||||
},
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"amount": 36000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000002",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $60.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000002/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/charges"
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000002",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $60.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
},
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/charges"
|
||||
}
|
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539882356,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_1DMewaGh0CmXqmnw659QMDX7",
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_DoHVyfEZQ0tNj4",
|
||||
"invoice_prefix": "448916B",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
@@ -29,14 +33,14 @@
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHVyfEZQ0tNj4",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMewaGh0CmXqmnw659QMDX7",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
@@ -47,14 +51,14 @@
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHVyfEZQ0tNj4/sources"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_DoHVyfEZQ0tNj4/subscriptions"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": "card_NORMALIZED00000000000002",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
@@ -0,0 +1,191 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -7200
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 1200
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -36000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 6000
|
||||
}
|
@@ -14,18 +14,18 @@
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMewaGh0CmXqmnw659QMDX7",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "107.202.144.213",
|
||||
"created": 1539882356,
|
||||
"id": "tok_1DMewaGh0CmXqmnwguSDP9TQ",
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
@@ -14,18 +14,18 @@
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMRsvGh0CmXqmnwRFN7yRRD",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "107.202.144.213",
|
||||
"created": 1539832157,
|
||||
"id": "tok_1DMRsvGh0CmXqmnwO80yZKru",
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1540918391,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_1DR0SsGh0CmXqmnwLRYxxJTu",
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_Dsm0jbfb5Vxhmv",
|
||||
"invoice_prefix": "705151A",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
@@ -29,14 +33,14 @@
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_Dsm0jbfb5Vxhmv",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DR0SsGh0CmXqmnwLRYxxJTu",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
@@ -47,14 +51,14 @@
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_Dsm0jbfb5Vxhmv/sources"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_Dsm0jbfb5Vxhmv/subscriptions"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -48000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
@@ -14,18 +14,18 @@
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DR0SsGh0CmXqmnwLRYxxJTu",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "107.202.144.213",
|
||||
"created": 1540918394,
|
||||
"id": "tok_1DR0SsGh0CmXqmnwBW7Lj2FR",
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
@@ -1,168 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539881153,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_DoHBcS2dBGOP9t",
|
||||
"invoice_prefix": "3B3F5D6",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1539881154,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1539881154,
|
||||
"current_period_end": 1571417154,
|
||||
"current_period_start": 1539881154,
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_DoHBD49xn11qGo",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1539881154,
|
||||
"id": "si_DoHB9flY9e7zrZ",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_DoHBD49xn11qGo"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_DoHBD49xn11qGo"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1539881154,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -1,168 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539881153,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_DoHBcS2dBGOP9t",
|
||||
"invoice_prefix": "3B3F5D6",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1539881154,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1539881154,
|
||||
"current_period_end": 1571417154,
|
||||
"current_period_start": 1539881154,
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_DoHBD49xn11qGo",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1539881154,
|
||||
"id": "si_DoHB9flY9e7zrZ",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_DoHBD49xn11qGo"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_DoHBD49xn11qGo"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1539881154,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"amount_due": 64000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 64000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "upcoming",
|
||||
"charge": null,
|
||||
"closed": false,
|
||||
"currency": "usd",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"date": 1571417154,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": 0,
|
||||
"forgiven": false,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "8 user \u00d7 Zulip Cloud Standard (at $80.00 / year)",
|
||||
"discountable": true,
|
||||
"id": "sli_31180ead97e161",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1603039554,
|
||||
"start": 1571417154
|
||||
},
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": "sub_DoHBD49xn11qGo",
|
||||
"subscription_item": "si_DoHB9flY9e7zrZ",
|
||||
"type": "subscription"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/upcoming/lines?customer=cus_DoHBcS2dBGOP9t"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1571420754,
|
||||
"number": "3B3F5D6-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1571417154,
|
||||
"period_start": 1539881154,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": null,
|
||||
"subscription": "sub_DoHBD49xn11qGo",
|
||||
"subtotal": 64000,
|
||||
"tax": 0,
|
||||
"tax_percent": 0.0,
|
||||
"total": 64000,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"amount_due": 64000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 64000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "upcoming",
|
||||
"charge": null,
|
||||
"closed": false,
|
||||
"currency": "usd",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"date": 1571417154,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": 0,
|
||||
"forgiven": false,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "8 user \u00d7 Zulip Cloud Standard (at $80.00 / year)",
|
||||
"discountable": true,
|
||||
"id": "sli_31180ead97e161",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1603039554,
|
||||
"start": 1571417154
|
||||
},
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": "sub_DoHBD49xn11qGo",
|
||||
"subscription_item": "si_DoHB9flY9e7zrZ",
|
||||
"type": "subscription"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/upcoming/lines?customer=cus_DoHBcS2dBGOP9t"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1571420754,
|
||||
"number": "3B3F5D6-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1571417154,
|
||||
"period_start": 1539881154,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": null,
|
||||
"subscription": "sub_DoHBD49xn11qGo",
|
||||
"subtotal": 64000,
|
||||
"tax": 0,
|
||||
"tax_percent": 0.0,
|
||||
"total": 64000,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1539881154,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1539881154,
|
||||
"current_period_end": 1571417154,
|
||||
"current_period_start": 1539881154,
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_DoHBD49xn11qGo",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1539881154,
|
||||
"id": "si_DoHB9flY9e7zrZ",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_DoHBD49xn11qGo"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_DoHBD49xn11qGo"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1539881154,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": null,
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": 100
|
||||
}
|
@@ -1,168 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539832157,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_Do40UO0WhJ4ZIl",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMRsvGh0CmXqmnwRFN7yRRD",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_Do40UO0WhJ4ZIl",
|
||||
"invoice_prefix": "7783290",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_Do40UO0WhJ4ZIl",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMRsvGh0CmXqmnwRFN7yRRD",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_Do40UO0WhJ4ZIl/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1539832158,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1539832158,
|
||||
"current_period_end": 1571368158,
|
||||
"current_period_start": 1539832158,
|
||||
"customer": "cus_Do40UO0WhJ4ZIl",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_Do40s9CYZB3Ib8",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1539832159,
|
||||
"id": "si_Do40INfOLVpONR",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_Do40s9CYZB3Ib8"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_Do40s9CYZB3Ib8"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1539832158,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_Do40UO0WhJ4ZIl/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"application_fee_percent": null,
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1539832158,
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": null,
|
||||
"created": 1539832158,
|
||||
"current_period_end": 1571368158,
|
||||
"current_period_start": 1539832158,
|
||||
"customer": "cus_Do40UO0WhJ4ZIl",
|
||||
"days_until_due": null,
|
||||
"discount": null,
|
||||
"ended_at": null,
|
||||
"id": "sub_Do40s9CYZB3Ib8",
|
||||
"items": {
|
||||
"data": [
|
||||
{
|
||||
"created": 1539832159,
|
||||
"id": "si_Do40INfOLVpONR",
|
||||
"metadata": {},
|
||||
"object": "subscription_item",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"subscription": "sub_Do40s9CYZB3Ib8"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_Do40s9CYZB3Ib8"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "subscription",
|
||||
"plan": {
|
||||
"active": true,
|
||||
"aggregate_usage": null,
|
||||
"amount": 8000,
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1539831971,
|
||||
"currency": "usd",
|
||||
"id": "plan_Do3xCvbzO89OsR",
|
||||
"interval": "year",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "annual",
|
||||
"object": "plan",
|
||||
"product": "prod_Do3x494SetTDpx",
|
||||
"tiers": null,
|
||||
"tiers_mode": null,
|
||||
"transform_usage": null,
|
||||
"trial_period_days": null,
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 8,
|
||||
"start": 1539832158,
|
||||
"status": "active",
|
||||
"tax_percent": 0.0,
|
||||
"trial_end": null,
|
||||
"trial_start": null
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539881153,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_DoHBcS2dBGOP9t",
|
||||
"invoice_prefix": "3B3F5D6",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
@@ -29,14 +33,14 @@
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_DoHBcS2dBGOP9t",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
@@ -47,14 +51,14 @@
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/sources"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_DoHBcS2dBGOP9t/subscriptions"
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -48000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"unit_amount": 5814
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": 7255
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"amount": 64000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_1DxdeSGh0CmXqmnw9I7ziL6D",
|
||||
"captured": true,
|
||||
"created": 1548695524,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 8",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_1DxdeSGh0CmXqmnwsw80Rn8n",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 46,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_1BWYgHGh0CmXqmnw/ch_1DxdeSGh0CmXqmnwsw80Rn8n/rcpt_EQUda9O5I5cYpJL0yxiRbxnPbWZ415D",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_1DxdeSGh0CmXqmnwsw80Rn8n/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1539832157,
|
||||
"created": 1548695523,
|
||||
"currency": null,
|
||||
"default_source": "card_1DMRsvGh0CmXqmnwRFN7yRRD",
|
||||
"default_source": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_Do40UO0WhJ4ZIl",
|
||||
"invoice_prefix": "7783290",
|
||||
"id": "cus_EQUd48LphR5ahk",
|
||||
"invoice_prefix": "9F53235",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
@@ -29,14 +33,14 @@
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_Do40UO0WhJ4ZIl",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMRsvGh0CmXqmnwRFN7yRRD",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
@@ -47,14 +51,14 @@
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_Do40UO0WhJ4ZIl/sources"
|
||||
"url": "/v1/customers/cus_EQUd48LphR5ahk/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_Do40UO0WhJ4ZIl/subscriptions"
|
||||
"url": "/v1/customers/cus_EQUd48LphR5ahk/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695526,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1548699126,
|
||||
"number": "9F53235-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1548695526,
|
||||
"period_start": 1548695526,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695526,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1551287527,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1548695527,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE",
|
||||
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "9F53235-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1548695526,
|
||||
"period_start": 1548695526,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1548695526
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695525,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -64000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695525,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695527,
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeVGh0CmXqmnwf6cLlEA7",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"unit_amount": 5814
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DMedAGh0CmXqmnwDLwrAV1v",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
@@ -24,8 +24,8 @@
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "107.202.144.213",
|
||||
"created": 1539881152,
|
||||
"id": "tok_1DMedAGh0CmXqmnwduQE6C3S",
|
||||
"created": 1548695523,
|
||||
"id": "tok_1DxdeRGh0CmXqmnw5X6CeYnC",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user