@fantastic.dev/better-stripe 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -1
  3. package/dist/client/billing/checkout.d.ts +3 -43
  4. package/dist/client/billing/checkout.d.ts.map +1 -1
  5. package/dist/client/billing/checkout.js +5 -260
  6. package/dist/client/billing/checkout.js.map +1 -1
  7. package/dist/client/billing/checkoutRequest.d.ts +129 -0
  8. package/dist/client/billing/checkoutRequest.d.ts.map +1 -0
  9. package/dist/client/billing/checkoutRequest.js +263 -0
  10. package/dist/client/billing/checkoutRequest.js.map +1 -0
  11. package/dist/client/billing/preparedCheckout.d.ts +22 -0
  12. package/dist/client/billing/preparedCheckout.d.ts.map +1 -0
  13. package/dist/client/billing/preparedCheckout.js +86 -0
  14. package/dist/client/billing/preparedCheckout.js.map +1 -0
  15. package/dist/client/checkoutRequests.d.ts +80 -0
  16. package/dist/client/checkoutRequests.d.ts.map +1 -0
  17. package/dist/client/checkoutRequests.js +28 -0
  18. package/dist/client/checkoutRequests.js.map +1 -0
  19. package/dist/client/connect/payoutAdoptionTestHelpers.d.ts +464 -0
  20. package/dist/client/connect/payoutAdoptionTestHelpers.d.ts.map +1 -1
  21. package/dist/client/connect/payoutCollection.d.ts +21 -0
  22. package/dist/client/connect/payoutCollection.d.ts.map +1 -0
  23. package/dist/client/connect/payoutCollection.js +47 -0
  24. package/dist/client/connect/payoutCollection.js.map +1 -0
  25. package/dist/client/connect/payoutExecutionTestHelpers.d.ts +1805 -0
  26. package/dist/client/connect/payoutExecutionTestHelpers.d.ts.map +1 -0
  27. package/dist/client/connect/payoutExecutionTestHelpers.js +194 -0
  28. package/dist/client/connect/payoutExecutionTestHelpers.js.map +1 -0
  29. package/dist/client/connect/payoutSyncTestHelpers.d.ts +232 -0
  30. package/dist/client/connect/payoutSyncTestHelpers.d.ts.map +1 -1
  31. package/dist/client/connect/payoutWorker.d.ts +48 -0
  32. package/dist/client/connect/payoutWorker.d.ts.map +1 -0
  33. package/dist/client/connect/payoutWorker.js +78 -0
  34. package/dist/client/connect/payoutWorker.js.map +1 -0
  35. package/dist/client/index.d.ts +4 -14
  36. package/dist/client/index.d.ts.map +1 -1
  37. package/dist/client/index.js +6 -13
  38. package/dist/client/index.js.map +1 -1
  39. package/dist/client/payoutPolicy.d.ts +67 -0
  40. package/dist/client/payoutPolicy.d.ts.map +1 -1
  41. package/dist/client/payoutPolicy.js +10 -0
  42. package/dist/client/payoutPolicy.js.map +1 -1
  43. package/dist/client/readContracts.d.ts +14 -1
  44. package/dist/client/readContracts.d.ts.map +1 -1
  45. package/dist/client/readContracts.js +14 -0
  46. package/dist/client/readContracts.js.map +1 -1
  47. package/dist/client/setup.test.d.ts +18 -0
  48. package/dist/client/setup.test.d.ts.map +1 -0
  49. package/dist/client/setup.test.js +33 -0
  50. package/dist/client/setup.test.js.map +1 -0
  51. package/dist/client/types/triggers.d.ts +1 -1
  52. package/dist/client/types/triggers.d.ts.map +1 -1
  53. package/dist/client/webhooks/helpers.d.ts.map +1 -1
  54. package/dist/client/webhooks/helpers.js +1 -0
  55. package/dist/client/webhooks/helpers.js.map +1 -1
  56. package/dist/client/webhooks/payoutReconciliation.d.ts +8 -0
  57. package/dist/client/webhooks/payoutReconciliation.d.ts.map +1 -0
  58. package/dist/client/webhooks/payoutReconciliation.js +36 -0
  59. package/dist/client/webhooks/payoutReconciliation.js.map +1 -0
  60. package/dist/client/webhooks/payoutReconciliationTestHelpers.d.ts +117 -0
  61. package/dist/client/webhooks/payoutReconciliationTestHelpers.d.ts.map +1 -0
  62. package/dist/client/webhooks/payoutReconciliationTestHelpers.js +105 -0
  63. package/dist/client/webhooks/payoutReconciliationTestHelpers.js.map +1 -0
  64. package/dist/client/webhooks/processors.d.ts.map +1 -1
  65. package/dist/client/webhooks/processors.js +1 -20
  66. package/dist/client/webhooks/processors.js.map +1 -1
  67. package/dist/component/_generated/component.d.ts +293 -0
  68. package/dist/component/_generated/component.d.ts.map +1 -1
  69. package/dist/component/core/mutations.d.ts.map +1 -1
  70. package/dist/component/core/mutations.js +2 -0
  71. package/dist/component/core/mutations.js.map +1 -1
  72. package/dist/component/core/resetSafety.d.ts +4 -0
  73. package/dist/component/core/resetSafety.d.ts.map +1 -0
  74. package/dist/component/core/resetSafety.js +54 -0
  75. package/dist/component/core/resetSafety.js.map +1 -0
  76. package/dist/component/earnings/triggers.d.ts +418 -0
  77. package/dist/component/earnings/triggers.d.ts.map +1 -1
  78. package/dist/component/earnings/triggers.js +2 -0
  79. package/dist/component/earnings/triggers.js.map +1 -1
  80. package/dist/component/payoutPolicy/schema.d.ts +2 -0
  81. package/dist/component/payoutPolicy/schema.d.ts.map +1 -1
  82. package/dist/component/payoutPolicy/schema.js +3 -1
  83. package/dist/component/payoutPolicy/schema.js.map +1 -1
  84. package/dist/component/payoutRelease/mutations.js +1 -1
  85. package/dist/component/payoutRelease/schema.d.ts +1 -0
  86. package/dist/component/payoutRelease/schema.d.ts.map +1 -1
  87. package/dist/component/payoutRelease/schema.js +1 -0
  88. package/dist/component/payoutRelease/schema.js.map +1 -1
  89. package/dist/component/payoutSnapshots/localState.d.ts +89 -0
  90. package/dist/component/payoutSnapshots/localState.d.ts.map +1 -0
  91. package/dist/component/payoutSnapshots/localState.js +186 -0
  92. package/dist/component/payoutSnapshots/localState.js.map +1 -0
  93. package/dist/component/payoutSnapshots/mutations.d.ts +108 -0
  94. package/dist/component/payoutSnapshots/mutations.d.ts.map +1 -0
  95. package/dist/component/payoutSnapshots/mutations.js +263 -0
  96. package/dist/component/payoutSnapshots/mutations.js.map +1 -0
  97. package/dist/component/payoutSnapshots/revisions.d.ts +12 -0
  98. package/dist/component/payoutSnapshots/revisions.d.ts.map +1 -0
  99. package/dist/component/payoutSnapshots/revisions.js +173 -0
  100. package/dist/component/payoutSnapshots/revisions.js.map +1 -0
  101. package/dist/component/payoutSnapshots/schema.d.ts +285 -0
  102. package/dist/component/payoutSnapshots/schema.d.ts.map +1 -0
  103. package/dist/component/payoutSnapshots/schema.js +108 -0
  104. package/dist/component/payoutSnapshots/schema.js.map +1 -0
  105. package/dist/component/payoutSnapshots/scope.d.ts +7 -0
  106. package/dist/component/payoutSnapshots/scope.d.ts.map +1 -0
  107. package/dist/component/payoutSnapshots/scope.js +8 -0
  108. package/dist/component/payoutSnapshots/scope.js.map +1 -0
  109. package/dist/component/payoutWorker/helpers.d.ts +30 -0
  110. package/dist/component/payoutWorker/helpers.d.ts.map +1 -0
  111. package/dist/component/payoutWorker/helpers.js +21 -0
  112. package/dist/component/payoutWorker/helpers.js.map +1 -0
  113. package/dist/component/payoutWorker/mutations.d.ts +68 -0
  114. package/dist/component/payoutWorker/mutations.d.ts.map +1 -0
  115. package/dist/component/payoutWorker/mutations.js +161 -0
  116. package/dist/component/payoutWorker/mutations.js.map +1 -0
  117. package/dist/component/payoutWorker/queries.d.ts +123 -0
  118. package/dist/component/payoutWorker/queries.d.ts.map +1 -0
  119. package/dist/component/payoutWorker/queries.js +61 -0
  120. package/dist/component/payoutWorker/queries.js.map +1 -0
  121. package/dist/component/payoutWorker/schema.d.ts +91 -0
  122. package/dist/component/payoutWorker/schema.d.ts.map +1 -0
  123. package/dist/component/payoutWorker/schema.js +27 -0
  124. package/dist/component/payoutWorker/schema.js.map +1 -0
  125. package/dist/component/schema.d.ts +232 -0
  126. package/dist/component/schema.js +10 -0
  127. package/dist/component/schema.js.map +1 -1
  128. package/docs/read-contracts.md +69 -5
  129. package/docs/settlement.md +128 -1
  130. package/package.json +1 -1
  131. package/src/client/billing/checkout.ts +11 -359
  132. package/src/client/billing/checkoutRequest.ts +362 -0
  133. package/src/client/billing/prepared-checkout.test.ts +430 -0
  134. package/src/client/billing/preparedCheckout.ts +143 -0
  135. package/src/client/checkoutRequests.ts +40 -0
  136. package/src/client/connect/payoutCollection.test.ts +74 -0
  137. package/src/client/connect/payoutCollection.ts +82 -0
  138. package/src/client/connect/payoutExecution.test.ts +1 -211
  139. package/src/client/connect/payoutExecutionTestHelpers.ts +213 -0
  140. package/src/client/connect/payoutWorker.test.ts +145 -0
  141. package/src/client/connect/payoutWorker.ts +144 -0
  142. package/src/client/index.ts +13 -27
  143. package/src/client/payoutPolicy.ts +26 -1
  144. package/src/client/readContracts.ts +28 -0
  145. package/src/client/types/triggers.ts +1 -0
  146. package/src/client/webhooks/helpers.ts +1 -0
  147. package/src/client/webhooks/payout-canceled.integration.test.ts +14 -1
  148. package/src/client/webhooks/payout-reconciliation.integration.test.ts +168 -0
  149. package/src/client/webhooks/payoutReconciliation.ts +72 -0
  150. package/src/client/webhooks/payoutReconciliationTestHelpers.ts +135 -0
  151. package/src/client/webhooks/processors.test.ts +44 -29
  152. package/src/client/webhooks/processors.ts +1 -27
  153. package/src/component/_generated/api.ts +16 -0
  154. package/src/component/_generated/component.ts +361 -0
  155. package/src/component/core/mutations.ts +2 -0
  156. package/src/component/core/resetSafety.ts +67 -0
  157. package/src/component/earnings/triggers.ts +2 -0
  158. package/src/component/payout-reset-safety.test.ts +148 -0
  159. package/src/component/payout-revisions.test.ts +125 -0
  160. package/src/component/payout-snapshots.test.ts +395 -0
  161. package/src/component/payout-worker.test.ts +165 -0
  162. package/src/component/payoutPolicy/schema.ts +3 -1
  163. package/src/component/payoutRelease/mutations.ts +1 -1
  164. package/src/component/payoutRelease/schema.ts +1 -0
  165. package/src/component/payoutSnapshots/localState.ts +238 -0
  166. package/src/component/payoutSnapshots/mutations.ts +275 -0
  167. package/src/component/payoutSnapshots/revisions.ts +247 -0
  168. package/src/component/payoutSnapshots/schema.ts +125 -0
  169. package/src/component/payoutSnapshots/scope.ts +7 -0
  170. package/src/component/payoutWorker/helpers.ts +30 -0
  171. package/src/component/payoutWorker/mutations.ts +181 -0
  172. package/src/component/payoutWorker/queries.ts +64 -0
  173. package/src/component/payoutWorker/schema.ts +26 -0
  174. package/src/component/public.test.ts +2 -2
  175. package/src/component/schema.ts +18 -0
@@ -3683,6 +3683,161 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
3683
3683
  >;
3684
3684
  };
3685
3685
  };
3686
+ payoutSnapshots: {
3687
+ mutations: {
3688
+ advance: FunctionReference<
3689
+ "mutation",
3690
+ "internal",
3691
+ {
3692
+ accountId: string;
3693
+ batchSize?: number;
3694
+ expectedStep: number;
3695
+ operationKey: string;
3696
+ ownerKey: string;
3697
+ },
3698
+ {
3699
+ _creationTime: number;
3700
+ _id: string;
3701
+ accountId: string;
3702
+ allocationCount: number;
3703
+ allocationCursor?: string;
3704
+ claimCount: number;
3705
+ claimCursor?: string;
3706
+ deferredPositiveChanges: boolean;
3707
+ expectedRevision: number;
3708
+ expiresAt: number;
3709
+ feeCount: number;
3710
+ feeCursor?: string;
3711
+ initialRevision: number;
3712
+ initialRiskRevision: number;
3713
+ observedRevision: number;
3714
+ operationKey: string;
3715
+ ownerKey: string;
3716
+ phase:
3717
+ | "sources"
3718
+ | "allocations"
3719
+ | "fees"
3720
+ | "claims"
3721
+ | "repair"
3722
+ | "check"
3723
+ | "local_ready"
3724
+ | "invalidated"
3725
+ | "discarding";
3726
+ policyMode?: string;
3727
+ policyOperationKey?: string;
3728
+ providerChanged: boolean;
3729
+ readOnly: boolean;
3730
+ reason?: string;
3731
+ riskRevision: number;
3732
+ sourceCount: number;
3733
+ sourceCursor?: string;
3734
+ sourceFence: number;
3735
+ step: number;
3736
+ },
3737
+ Name
3738
+ >;
3739
+ begin: FunctionReference<
3740
+ "mutation",
3741
+ "internal",
3742
+ {
3743
+ accountId: string;
3744
+ expectedRevision: number;
3745
+ operationKey: string;
3746
+ ownerKey: string;
3747
+ },
3748
+ {
3749
+ _creationTime: number;
3750
+ _id: string;
3751
+ accountId: string;
3752
+ allocationCount: number;
3753
+ allocationCursor?: string;
3754
+ claimCount: number;
3755
+ claimCursor?: string;
3756
+ deferredPositiveChanges: boolean;
3757
+ expectedRevision: number;
3758
+ expiresAt: number;
3759
+ feeCount: number;
3760
+ feeCursor?: string;
3761
+ initialRevision: number;
3762
+ initialRiskRevision: number;
3763
+ observedRevision: number;
3764
+ operationKey: string;
3765
+ ownerKey: string;
3766
+ phase:
3767
+ | "sources"
3768
+ | "allocations"
3769
+ | "fees"
3770
+ | "claims"
3771
+ | "repair"
3772
+ | "check"
3773
+ | "local_ready"
3774
+ | "invalidated"
3775
+ | "discarding";
3776
+ policyMode?: string;
3777
+ policyOperationKey?: string;
3778
+ providerChanged: boolean;
3779
+ readOnly: boolean;
3780
+ reason?: string;
3781
+ riskRevision: number;
3782
+ sourceCount: number;
3783
+ sourceCursor?: string;
3784
+ sourceFence: number;
3785
+ step: number;
3786
+ },
3787
+ Name
3788
+ >;
3789
+ discard: FunctionReference<
3790
+ "mutation",
3791
+ "internal",
3792
+ {
3793
+ accountId: string;
3794
+ batchSize?: number;
3795
+ operationKey: string;
3796
+ ownerKey: string;
3797
+ },
3798
+ {
3799
+ _creationTime: number;
3800
+ _id: string;
3801
+ accountId: string;
3802
+ allocationCount: number;
3803
+ allocationCursor?: string;
3804
+ claimCount: number;
3805
+ claimCursor?: string;
3806
+ deferredPositiveChanges: boolean;
3807
+ expectedRevision: number;
3808
+ expiresAt: number;
3809
+ feeCount: number;
3810
+ feeCursor?: string;
3811
+ initialRevision: number;
3812
+ initialRiskRevision: number;
3813
+ observedRevision: number;
3814
+ operationKey: string;
3815
+ ownerKey: string;
3816
+ phase:
3817
+ | "sources"
3818
+ | "allocations"
3819
+ | "fees"
3820
+ | "claims"
3821
+ | "repair"
3822
+ | "check"
3823
+ | "local_ready"
3824
+ | "invalidated"
3825
+ | "discarding";
3826
+ policyMode?: string;
3827
+ policyOperationKey?: string;
3828
+ providerChanged: boolean;
3829
+ readOnly: boolean;
3830
+ reason?: string;
3831
+ riskRevision: number;
3832
+ sourceCount: number;
3833
+ sourceCursor?: string;
3834
+ sourceFence: number;
3835
+ step: number;
3836
+ } | null,
3837
+ Name
3838
+ >;
3839
+ };
3840
+ };
3686
3841
  payoutSync: {
3687
3842
  mutations: {
3688
3843
  begin: FunctionReference<
@@ -3765,6 +3920,212 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
3765
3920
  >;
3766
3921
  };
3767
3922
  };
3923
+ payoutWorker: {
3924
+ mutations: {
3925
+ begin: FunctionReference<
3926
+ "mutation",
3927
+ "internal",
3928
+ {
3929
+ accountId: string;
3930
+ cadenceMs: number;
3931
+ currency: string;
3932
+ ownerKey: string;
3933
+ },
3934
+ | { nextAttemptAt?: number; reason: string; status: "held" }
3935
+ | {
3936
+ job: {
3937
+ _creationTime: number;
3938
+ _id: string;
3939
+ accountId: string;
3940
+ cadenceMs: number;
3941
+ completedGeneration?: number;
3942
+ completedOperationKey?: string;
3943
+ currency: string;
3944
+ generation: number;
3945
+ hasOperation: boolean;
3946
+ lastAttemptAt?: number;
3947
+ lastReason?: string;
3948
+ leaseUntil?: number;
3949
+ nextAttemptAt: number;
3950
+ operationKey?: string;
3951
+ ownerKey: string;
3952
+ sequence: number;
3953
+ };
3954
+ reconcileOnly: boolean;
3955
+ status: "run";
3956
+ },
3957
+ Name
3958
+ >;
3959
+ finish: FunctionReference<
3960
+ "mutation",
3961
+ "internal",
3962
+ {
3963
+ accountId: string;
3964
+ currency: string;
3965
+ generation: number;
3966
+ operationKey: string;
3967
+ ownerKey: string;
3968
+ reason: string;
3969
+ },
3970
+ {
3971
+ _creationTime: number;
3972
+ _id: string;
3973
+ accountId: string;
3974
+ cadenceMs: number;
3975
+ completedGeneration?: number;
3976
+ completedOperationKey?: string;
3977
+ currency: string;
3978
+ generation: number;
3979
+ hasOperation: boolean;
3980
+ lastAttemptAt?: number;
3981
+ lastReason?: string;
3982
+ leaseUntil?: number;
3983
+ nextAttemptAt: number;
3984
+ operationKey?: string;
3985
+ ownerKey: string;
3986
+ sequence: number;
3987
+ },
3988
+ Name
3989
+ >;
3990
+ };
3991
+ queries: {
3992
+ changing: FunctionReference<
3993
+ "query",
3994
+ "internal",
3995
+ {
3996
+ paginationOpts: {
3997
+ cursor: string | null;
3998
+ endCursor?: string | null;
3999
+ id?: number;
4000
+ maximumBytesRead?: number;
4001
+ maximumRowsRead?: number;
4002
+ numItems: number;
4003
+ };
4004
+ },
4005
+ {
4006
+ continueCursor: string;
4007
+ isDone: boolean;
4008
+ page: Array<{
4009
+ _creationTime: number;
4010
+ _id: string;
4011
+ accountId: string;
4012
+ country: string;
4013
+ holdDeadline?: number;
4014
+ mode: "stripe_managed" | "custom";
4015
+ operationKey: string;
4016
+ ownerKey: string;
4017
+ previousSchedule?: {
4018
+ interval: "daily" | "weekly" | "monthly" | "manual";
4019
+ monthlyDays?: Array<number>;
4020
+ weeklyDays?: Array<string>;
4021
+ };
4022
+ revision: number;
4023
+ status: "changing" | "ready";
4024
+ thresholds: Array<{ amount: number; currency: string }>;
4025
+ }>;
4026
+ },
4027
+ Name
4028
+ >;
4029
+ get: FunctionReference<
4030
+ "query",
4031
+ "internal",
4032
+ { accountId: string; currency: string; ownerKey: string },
4033
+ {
4034
+ _creationTime: number;
4035
+ _id: string;
4036
+ accountId: string;
4037
+ cadenceMs: number;
4038
+ completedGeneration?: number;
4039
+ completedOperationKey?: string;
4040
+ currency: string;
4041
+ generation: number;
4042
+ hasOperation: boolean;
4043
+ lastAttemptAt?: number;
4044
+ lastReason?: string;
4045
+ leaseUntil?: number;
4046
+ nextAttemptAt: number;
4047
+ operationKey?: string;
4048
+ ownerKey: string;
4049
+ sequence: number;
4050
+ } | null,
4051
+ Name
4052
+ >;
4053
+ policies: FunctionReference<
4054
+ "query",
4055
+ "internal",
4056
+ {
4057
+ paginationOpts: {
4058
+ cursor: string | null;
4059
+ endCursor?: string | null;
4060
+ id?: number;
4061
+ maximumBytesRead?: number;
4062
+ maximumRowsRead?: number;
4063
+ numItems: number;
4064
+ };
4065
+ },
4066
+ {
4067
+ continueCursor: string;
4068
+ isDone: boolean;
4069
+ page: Array<{
4070
+ _creationTime: number;
4071
+ _id: string;
4072
+ accountId: string;
4073
+ country: string;
4074
+ holdDeadline?: number;
4075
+ mode: "stripe_managed" | "custom";
4076
+ operationKey: string;
4077
+ ownerKey: string;
4078
+ previousSchedule?: {
4079
+ interval: "daily" | "weekly" | "monthly" | "manual";
4080
+ monthlyDays?: Array<number>;
4081
+ weeklyDays?: Array<string>;
4082
+ };
4083
+ revision: number;
4084
+ status: "changing" | "ready";
4085
+ thresholds: Array<{ amount: number; currency: string }>;
4086
+ }>;
4087
+ },
4088
+ Name
4089
+ >;
4090
+ recovering: FunctionReference<
4091
+ "query",
4092
+ "internal",
4093
+ {
4094
+ paginationOpts: {
4095
+ cursor: string | null;
4096
+ endCursor?: string | null;
4097
+ id?: number;
4098
+ maximumBytesRead?: number;
4099
+ maximumRowsRead?: number;
4100
+ numItems: number;
4101
+ };
4102
+ },
4103
+ {
4104
+ continueCursor: string;
4105
+ isDone: boolean;
4106
+ page: Array<{
4107
+ _creationTime: number;
4108
+ _id: string;
4109
+ accountId: string;
4110
+ cadenceMs: number;
4111
+ completedGeneration?: number;
4112
+ completedOperationKey?: string;
4113
+ currency: string;
4114
+ generation: number;
4115
+ hasOperation: boolean;
4116
+ lastAttemptAt?: number;
4117
+ lastReason?: string;
4118
+ leaseUntil?: number;
4119
+ nextAttemptAt: number;
4120
+ operationKey?: string;
4121
+ ownerKey: string;
4122
+ sequence: number;
4123
+ }>;
4124
+ },
4125
+ Name
4126
+ >;
4127
+ };
4128
+ };
3768
4129
  products: {
3769
4130
  mutations: {
3770
4131
  upsertPrice: FunctionReference<
@@ -1,3 +1,4 @@
1
+ import { assertPayoutResetSafe } from "./resetSafety.js";
1
2
  import { v } from "convex/values";
2
3
 
3
4
  import type { TableNames } from "../_generated/dataModel";
@@ -155,6 +156,7 @@ export const clearAllTables = mutation({
155
156
  tables: v.array(v.string()),
156
157
  }),
157
158
  handler: async (ctx) => {
159
+ await assertPayoutResetSafe(ctx);
158
160
  // Derive table names from the schema definition so the clear list can never
159
161
  // drift out of sync when new tables are added.
160
162
  const tables = Object.keys(schema.tables) as TableNames[];
@@ -0,0 +1,67 @@
1
+ import type { MutationCtx } from "../_generated/server.js";
2
+ /** A database reset cannot erase the only restoration or unresolved-payout record. */
3
+ export async function assertPayoutResetSafe(ctx: MutationCtx) {
4
+ const [bound, custom, changing, reserved, submitted, pending, transit] =
5
+ await Promise.all([
6
+ ctx.db
7
+ .query("payoutSnapshots")
8
+ .withIndex("by_read_only", (q) => q.eq("readOnly", false))
9
+ .first(),
10
+ ctx.db
11
+ .query("payoutPolicies")
12
+ .withIndex("by_mode_status", (q) => q.eq("mode", "custom"))
13
+ .first(),
14
+ ctx.db
15
+ .query("payoutPolicies")
16
+ .withIndex("by_mode_status", (q) =>
17
+ q.eq("mode", "stripe_managed").eq("status", "changing"),
18
+ )
19
+ .first(),
20
+ ctx.db
21
+ .query("payoutReleaseClaims")
22
+ .withIndex("by_status", (q) => q.eq("status", "reserved"))
23
+ .first(),
24
+ ctx.db
25
+ .query("payoutReleaseClaims")
26
+ .withIndex("by_status", (q) => q.eq("status", "submitted"))
27
+ .first(),
28
+ ctx.db
29
+ .query("payouts")
30
+ .withIndex("by_status", (q) => q.eq("status", "pending"))
31
+ .first(),
32
+ ctx.db
33
+ .query("payouts")
34
+ .withIndex("by_status", (q) => q.eq("status", "in_transit"))
35
+ .first(),
36
+ ]);
37
+ if (bound)
38
+ throw new Error(
39
+ "A financially bound payout collection must be resolved before reset.",
40
+ );
41
+ if (custom || changing)
42
+ throw new Error(
43
+ "Restore and verify payout schedules before reset; restoration records cannot be erased.",
44
+ );
45
+ if (reserved || submitted)
46
+ throw new Error(
47
+ "Resolve payout claims before reset; unresolved reservations cannot be erased.",
48
+ );
49
+ if (pending || transit)
50
+ throw new Error("Verify in-flight provider payouts before reset.");
51
+ const affiliateOperations = await Promise.all(
52
+ (["pending", "unknown"] as const).flatMap((state) => [
53
+ ctx.db
54
+ .query("affiliatePayoutReleases")
55
+ .withIndex("by_state", (q) => q.eq("state", state))
56
+ .first(),
57
+ ctx.db
58
+ .query("affiliateRecoveryOperations")
59
+ .withIndex("by_state", (q) => q.eq("state", state))
60
+ .first(),
61
+ ]),
62
+ );
63
+ if (affiliateOperations.some(Boolean))
64
+ throw new Error(
65
+ "Resolve pending or unknown affiliate operations before reset.",
66
+ );
67
+ }
@@ -1,3 +1,4 @@
1
+ import { registerPayoutRevisions } from "../payoutSnapshots/revisions.js";
1
2
  import { Triggers } from "convex-helpers/server/triggers";
2
3
  import {
3
4
  customCtx,
@@ -93,4 +94,5 @@ triggers.register("payouts", async (ctx, change) => {
93
94
  await ctx.innerDb.patch("payouts", change.id, { totalsRecorded: true });
94
95
  }
95
96
  });
97
+ registerPayoutRevisions(triggers);
96
98
  export const mutation = customMutation(rawMutation, customCtx(triggers.wrapDB));
@@ -0,0 +1,148 @@
1
+ // @vitest-environment edge-runtime
2
+ import { convexTest } from "convex-test";
3
+ import { makeFunctionReference } from "convex/server";
4
+ import { expect, it } from "vitest";
5
+ import schema from "./schema.js";
6
+ const modules = import.meta.glob("./**/*.*s");
7
+ const reset = makeFunctionReference<"mutation">(
8
+ "core/mutations:clearAllTables",
9
+ );
10
+ const scope = { accountId: "acct_one", ownerKey: "owner" };
11
+ it.each(["custom", "changing"])(
12
+ "retains restoration records for %s payout policy",
13
+ async (kind) => {
14
+ const t = convexTest(schema, modules);
15
+ await t.run((ctx) =>
16
+ ctx.db.insert("payoutPolicies", {
17
+ ...scope,
18
+ revision: 0,
19
+ mode: kind === "custom" ? "custom" : "stripe_managed",
20
+ status: kind === "changing" ? "changing" : "ready",
21
+ operationKey: "control",
22
+ country: "US",
23
+ thresholds: [],
24
+ previousSchedule: { interval: "weekly", weeklyDays: ["monday"] },
25
+ holdDeadline: Date.now() + 86400000,
26
+ }),
27
+ );
28
+ await expect(t.mutation(reset, {})).rejects.toThrow(/payout.*schedule/i);
29
+ expect(
30
+ await t.run((ctx) => ctx.db.query("payoutPolicies").collect()),
31
+ ).toHaveLength(1);
32
+ },
33
+ );
34
+ it.each(["reserved", "submitted"])(
35
+ "retains an unresolved %s payout claim",
36
+ async (status) => {
37
+ const t = convexTest(schema, modules);
38
+ await t.run((ctx) =>
39
+ ctx.db.insert("payoutReleaseClaims", {
40
+ ...scope,
41
+ operationKey: "money",
42
+ expectedRevision: 0,
43
+ currency: "usd",
44
+ amount: 1000,
45
+ status: status as "reserved" | "submitted",
46
+ allocations: [{ stripeTransferId: "tr_one", amount: 1000 }],
47
+ observations: [],
48
+ attemptedAt: Date.now(),
49
+ }),
50
+ );
51
+ await expect(t.mutation(reset, {})).rejects.toThrow(/payout.*claim/i);
52
+ expect(
53
+ await t.run((ctx) => ctx.db.query("payoutReleaseClaims").collect()),
54
+ ).toHaveLength(1);
55
+ },
56
+ );
57
+ it("retains known in-flight provider payouts", async () => {
58
+ const t = convexTest(schema, modules);
59
+ await t.mutation(
60
+ makeFunctionReference<"mutation">("connect/mutations:upsertPayout"),
61
+ {
62
+ stripePayoutId: "po_one",
63
+ accountId: scope.accountId,
64
+ currency: "usd",
65
+ amount: 1000,
66
+ status: "pending",
67
+ },
68
+ );
69
+ await expect(t.mutation(reset, {})).rejects.toThrow(/in.flight.*payout/i);
70
+ expect(await t.run((ctx) => ctx.db.query("payouts").collect())).toHaveLength(
71
+ 1,
72
+ );
73
+ });
74
+ it("cannot bypass a bound collection through wholesale reset", async () => {
75
+ const t = convexTest(schema, modules);
76
+ const h = await t.mutation(
77
+ makeFunctionReference<"mutation">("payoutSnapshots/mutations:begin"),
78
+ { ...scope, operationKey: "collection", expectedRevision: -1 },
79
+ );
80
+ await t.run((ctx) =>
81
+ ctx.db.patch("payoutSnapshots", h._id, { readOnly: false }),
82
+ );
83
+ await expect(t.mutation(reset, {})).rejects.toThrow(/bound.*collection/i);
84
+ expect(
85
+ await t.run((ctx) => ctx.db.query("payoutSnapshots").collect()),
86
+ ).toHaveLength(1);
87
+ });
88
+ it.each(["pending", "unknown"] as const)(
89
+ "retains %s direct affiliate payout attempts",
90
+ async (state) => {
91
+ const t = convexTest(schema, modules);
92
+ await t.run((ctx) =>
93
+ ctx.db.insert("affiliatePayoutReleases", {
94
+ affiliateAccountId: scope.accountId,
95
+ currency: "usd",
96
+ operationKey: "direct-affiliate",
97
+ amount: 1000,
98
+ minimumAmount: 1,
99
+ createdAt: Date.now(),
100
+ state,
101
+ }),
102
+ );
103
+ await expect(t.mutation(reset, {})).rejects.toThrow(
104
+ /affiliate.*operation/i,
105
+ );
106
+ expect(
107
+ await t.run((ctx) => ctx.db.query("affiliatePayoutReleases").collect()),
108
+ ).toHaveLength(1);
109
+ },
110
+ );
111
+ it.each(["pending", "unknown"] as const)(
112
+ "retains %s affiliate recovery attempts",
113
+ async (state) => {
114
+ const t = convexTest(schema, modules);
115
+ await t.run(async (ctx) => {
116
+ const obligationId = await ctx.db.insert("affiliateObligations", {
117
+ sourceChargeId: "ch_one",
118
+ affiliateAccountId: scope.accountId,
119
+ currency: "usd",
120
+ chargeAmount: 1000,
121
+ originalCommission: 100,
122
+ refundedPrincipal: 1000,
123
+ disputedPrincipal: 0,
124
+ lostPrincipal: 0,
125
+ finalizedAmount: 100,
126
+ provisionalAmount: 0,
127
+ recoveredAmount: 0,
128
+ needsReconciliation: false,
129
+ });
130
+ await ctx.db.insert("affiliateRecoveryOperations", {
131
+ obligationId,
132
+ kind: "recovery",
133
+ amount: 100,
134
+ state,
135
+ attempted: true,
136
+ createdAt: Date.now(),
137
+ });
138
+ });
139
+ await expect(t.mutation(reset, {})).rejects.toThrow(
140
+ /affiliate.*operation/i,
141
+ );
142
+ expect(
143
+ await t.run((ctx) =>
144
+ ctx.db.query("affiliateRecoveryOperations").collect(),
145
+ ),
146
+ ).toHaveLength(1);
147
+ },
148
+ );