@fantastic.dev/better-stripe 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/MIGRATION.md +12 -0
- package/README.md +46 -2
- package/dist/client/billing/checkout.d.ts +1 -0
- package/dist/client/billing/checkout.d.ts.map +1 -1
- package/dist/client/billing/checkout.js +5 -1
- package/dist/client/billing/checkout.js.map +1 -1
- package/dist/client/billing/providerReads.d.ts +18 -0
- package/dist/client/billing/providerReads.d.ts.map +1 -0
- package/dist/client/billing/providerReads.js +15 -0
- package/dist/client/billing/providerReads.js.map +1 -0
- package/dist/client/billing/subscriptions.d.ts +1 -1
- package/dist/client/billing/subscriptions.d.ts.map +1 -1
- package/dist/client/billing/subscriptions.js.map +1 -1
- package/dist/client/connect/payoutSnapshot.d.ts +28 -0
- package/dist/client/connect/payoutSnapshot.d.ts.map +1 -0
- package/dist/client/connect/payoutSnapshot.js +81 -0
- package/dist/client/connect/payoutSnapshot.js.map +1 -0
- package/dist/client/connect/transfers.d.ts.map +1 -1
- package/dist/client/connect/transfers.js +13 -32
- package/dist/client/connect/transfers.js.map +1 -1
- package/dist/client/core/accounts.d.ts +12 -23
- package/dist/client/core/accounts.d.ts.map +1 -1
- package/dist/client/core/accounts.js +5 -1
- package/dist/client/core/accounts.js.map +1 -1
- package/dist/client/core/idempotency.d.ts +5 -0
- package/dist/client/core/idempotency.d.ts.map +1 -0
- package/dist/client/core/idempotency.js +10 -0
- package/dist/client/core/idempotency.js.map +1 -0
- package/dist/client/index.d.ts +31 -25
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +29 -52
- package/dist/client/index.js.map +1 -1
- package/dist/client/settlement/allocation.d.ts +30 -0
- package/dist/client/settlement/allocation.d.ts.map +1 -0
- package/dist/client/settlement/allocation.js +71 -0
- package/dist/client/settlement/allocation.js.map +1 -0
- package/dist/client/settlement/commissions.d.ts +27 -0
- package/dist/client/settlement/commissions.d.ts.map +1 -0
- package/dist/client/settlement/commissions.js +69 -0
- package/dist/client/settlement/commissions.js.map +1 -0
- package/dist/client/settlement/execution.d.ts +27 -0
- package/dist/client/settlement/execution.d.ts.map +1 -0
- package/dist/client/settlement/execution.js +70 -0
- package/dist/client/settlement/execution.js.map +1 -0
- package/dist/client/settlement/reconciliation.d.ts +10 -0
- package/dist/client/settlement/reconciliation.d.ts.map +1 -0
- package/dist/client/settlement/reconciliation.js +73 -0
- package/dist/client/settlement/reconciliation.js.map +1 -0
- package/dist/client/settlement/settleSale.d.ts +21 -0
- package/dist/client/settlement/settleSale.d.ts.map +1 -0
- package/dist/client/settlement/settleSale.js +27 -0
- package/dist/client/settlement/settleSale.js.map +1 -0
- package/dist/client/settlement/verifiedSale.d.ts +25 -0
- package/dist/client/settlement/verifiedSale.d.ts.map +1 -0
- package/dist/client/settlement/verifiedSale.js +100 -0
- package/dist/client/settlement/verifiedSale.js.map +1 -0
- package/dist/client/webhooks/asyncHooks.d.ts +5 -0
- package/dist/client/webhooks/asyncHooks.d.ts.map +1 -0
- package/dist/client/webhooks/asyncHooks.js +20 -0
- package/dist/client/webhooks/asyncHooks.js.map +1 -0
- package/dist/component/_generated/component.d.ts +115 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/connect/queries.d.ts +30 -1
- package/dist/component/connect/queries.d.ts.map +1 -1
- package/dist/component/connect/queries.js +19 -2
- package/dist/component/connect/queries.js.map +1 -1
- package/dist/component/schema.d.ts +67 -0
- package/dist/component/schema.js +4 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/settlement/mutations.d.ts +47 -0
- package/dist/component/settlement/mutations.d.ts.map +1 -0
- package/dist/component/settlement/mutations.js +104 -0
- package/dist/component/settlement/mutations.js.map +1 -0
- package/dist/component/settlement/queries.d.ts +29 -0
- package/dist/component/settlement/queries.d.ts.map +1 -0
- package/dist/component/settlement/queries.js +35 -0
- package/dist/component/settlement/queries.js.map +1 -0
- package/dist/component/settlement/schema.d.ts +99 -0
- package/dist/component/settlement/schema.d.ts.map +1 -0
- package/dist/component/settlement/schema.js +35 -0
- package/dist/component/settlement/schema.js.map +1 -0
- package/dist/react/workflows/AffiliatePayoutEditor.d.ts +41 -0
- package/dist/react/workflows/AffiliatePayoutEditor.d.ts.map +1 -0
- package/dist/react/workflows/AffiliatePayoutEditor.js +80 -0
- package/dist/react/workflows/AffiliatePayoutEditor.js.map +1 -0
- package/dist/react/workflows/AffiliateRateEditor.d.ts +26 -0
- package/dist/react/workflows/AffiliateRateEditor.d.ts.map +1 -0
- package/dist/react/workflows/AffiliateRateEditor.js +49 -0
- package/dist/react/workflows/AffiliateRateEditor.js.map +1 -0
- package/dist/react/workflows/Checkout.d.ts +3 -1
- package/dist/react/workflows/Checkout.d.ts.map +1 -1
- package/dist/react/workflows/Checkout.js +3 -2
- package/dist/react/workflows/Checkout.js.map +1 -1
- package/dist/react/workflows/CheckoutForms.d.ts +2 -0
- package/dist/react/workflows/CheckoutForms.d.ts.map +1 -1
- package/dist/react/workflows/CheckoutForms.js +22 -16
- package/dist/react/workflows/CheckoutForms.js.map +1 -1
- package/dist/react/workflows/IntegrationSwitch.js +2 -2
- package/dist/react/workflows/IntegrationSwitch.js.map +1 -1
- package/dist/react/workflows/Pagination.d.ts +32 -0
- package/dist/react/workflows/Pagination.d.ts.map +1 -0
- package/dist/react/workflows/Pagination.js +49 -0
- package/dist/react/workflows/Pagination.js.map +1 -0
- package/dist/react/workflows/Payouts.d.ts +3 -18
- package/dist/react/workflows/Payouts.d.ts.map +1 -1
- package/dist/react/workflows/Payouts.js +2 -2
- package/dist/react/workflows/Payouts.js.map +1 -1
- package/dist/react/workflows/PlanPricingEditor.d.ts +35 -0
- package/dist/react/workflows/PlanPricingEditor.d.ts.map +1 -0
- package/dist/react/workflows/PlanPricingEditor.js +64 -0
- package/dist/react/workflows/PlanPricingEditor.js.map +1 -0
- package/dist/react/workflows/index.d.ts +8 -0
- package/dist/react/workflows/index.d.ts.map +1 -1
- package/dist/react/workflows/index.js +4 -0
- package/dist/react/workflows/index.js.map +1 -1
- package/docs/react-workflows-audit.md +19 -0
- package/docs/react-workflows.md +94 -3
- package/docs/settlement.md +158 -0
- package/package.json +6 -4
- package/src/client/billing/checkout-idempotency.test.ts +82 -0
- package/src/client/billing/checkout.ts +6 -1
- package/src/client/billing/providerReads.test.ts +60 -0
- package/src/client/billing/providerReads.ts +30 -0
- package/src/client/billing/subscriptions.ts +1 -8
- package/src/client/connect/payoutSnapshot.test.ts +91 -0
- package/src/client/connect/payoutSnapshot.ts +130 -0
- package/src/client/connect/transfers.test.ts +15 -4
- package/src/client/connect/transfers.ts +13 -42
- package/src/client/core/account-idempotency.test.ts +78 -0
- package/src/client/core/accounts.ts +18 -25
- package/src/client/core/idempotency.test.ts +10 -0
- package/src/client/core/idempotency.ts +11 -0
- package/src/client/index.ts +84 -103
- package/src/client/settlement/allocation.test.ts +98 -0
- package/src/client/settlement/allocation.ts +110 -0
- package/src/client/settlement/commissions.test.ts +103 -0
- package/src/client/settlement/commissions.ts +125 -0
- package/src/client/settlement/execution.test.ts +186 -0
- package/src/client/settlement/execution.ts +134 -0
- package/src/client/settlement/reconciliation.ts +119 -0
- package/src/client/settlement/settleSale.ts +59 -0
- package/src/client/settlement/verifiedSale.test.ts +139 -0
- package/src/client/settlement/verifiedSale.ts +159 -0
- package/src/client/webhooks/asyncHooks.ts +38 -0
- package/src/client/webhooks/processors.test.ts +9 -9
- package/src/component/_generated/api.ts +4 -0
- package/src/component/_generated/component.ts +141 -0
- package/src/component/connect/queries.ts +26 -2
- package/src/component/earnings-totals.test.ts +13 -0
- package/src/component/schema.ts +8 -0
- package/src/component/settlement/mutations.ts +119 -0
- package/src/component/settlement/queries.ts +38 -0
- package/src/component/settlement/schema.ts +45 -0
- package/src/component/settlement-claims.test.ts +77 -0
- package/src/component/transfers.test.ts +48 -0
- package/src/react/components/EmbeddedDisputes.render.test.tsx +2 -2
- package/src/react/index.payment-element.test.ts +22 -37
- package/src/react/workflows/AffiliatePayoutEditor.test.tsx +90 -0
- package/src/react/workflows/AffiliatePayoutEditor.tsx +201 -0
- package/src/react/workflows/AffiliateRateEditor.test.tsx +36 -0
- package/src/react/workflows/AffiliateRateEditor.tsx +92 -0
- package/src/react/workflows/Checkout.test.tsx +30 -0
- package/src/react/workflows/Checkout.tsx +10 -3
- package/src/react/workflows/CheckoutForms.test.tsx +40 -0
- package/src/react/workflows/CheckoutForms.tsx +37 -16
- package/src/react/workflows/IntegrationSwitch.test.tsx +25 -0
- package/src/react/workflows/IntegrationSwitch.tsx +2 -2
- package/src/react/workflows/Pagination.test.tsx +53 -0
- package/src/react/workflows/Pagination.tsx +93 -0
- package/src/react/workflows/Payouts.test.tsx +33 -0
- package/src/react/workflows/Payouts.tsx +21 -15
- package/src/react/workflows/PlanPricingEditor.test.tsx +79 -0
- package/src/react/workflows/PlanPricingEditor.tsx +156 -0
- package/src/react/workflows/index.ts +22 -0
package/src/client/index.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
CheckoutSessionReadOptions,
|
|
3
|
+
SubscriptionReadOptions,
|
|
4
|
+
CouponReadOptions,
|
|
5
|
+
} from "./billing/providerReads.js";
|
|
6
|
+
import * as providerReads from "./billing/providerReads.js";
|
|
7
|
+
export type { CreateAccountOptions } from "./core/accounts.js";
|
|
8
|
+
import { buildAsyncHookRoutes } from "./webhooks/asyncHooks.js";
|
|
9
|
+
import * as saleContextImpl from "./settlement/verifiedSale.js";
|
|
10
|
+
import * as settlementImpl from "./settlement/settleSale.js";
|
|
11
|
+
import * as commissionImpl from "./settlement/commissions.js";
|
|
12
|
+
import * as payoutSnapshotImpl from "./connect/payoutSnapshot.js";
|
|
13
|
+
export { allocateSale } from "./settlement/allocation.js";
|
|
14
|
+
export type {
|
|
15
|
+
SaleAllocation,
|
|
16
|
+
SaleAllocationOptions,
|
|
17
|
+
} from "./settlement/allocation.js";
|
|
18
|
+
export type {
|
|
19
|
+
VerifiedSale,
|
|
20
|
+
VerifiedSaleOptions,
|
|
21
|
+
} from "./settlement/verifiedSale.js";
|
|
22
|
+
export type { SettleSaleOptions } from "./settlement/settleSale.js";
|
|
23
|
+
export type { SettlementResult } from "./settlement/execution.js";
|
|
24
|
+
export type {
|
|
25
|
+
CommissionTerm,
|
|
26
|
+
CommissionSelectionOptions,
|
|
27
|
+
CommissionSelection,
|
|
28
|
+
CommissionDecision,
|
|
29
|
+
} from "./settlement/commissions.js";
|
|
30
|
+
export type {
|
|
31
|
+
RecipientPayoutSnapshot,
|
|
32
|
+
RecipientPayoutRow,
|
|
33
|
+
PayoutSnapshotOptions,
|
|
34
|
+
} from "./connect/payoutSnapshot.js";
|
|
1
35
|
import { internalActionGeneric, internalMutationGeneric } from "convex/server";
|
|
2
36
|
import { v } from "convex/values";
|
|
3
37
|
import Stripe from "stripe";
|
|
@@ -146,9 +180,7 @@ function clonePlatformFee(config: PlatformFeeConfig): PlatformFeeConfig {
|
|
|
146
180
|
};
|
|
147
181
|
}
|
|
148
182
|
|
|
149
|
-
// =============================================================================
|
|
150
183
|
// BETTER STRIPE CLIENT
|
|
151
|
-
// =============================================================================
|
|
152
184
|
|
|
153
185
|
export class BetterStripe {
|
|
154
186
|
public component: Component;
|
|
@@ -212,9 +244,7 @@ export class BetterStripe {
|
|
|
212
244
|
return getStripeClient(this.apiKey, apiVersion);
|
|
213
245
|
}
|
|
214
246
|
|
|
215
|
-
// ============================================================================
|
|
216
247
|
// Static defaults (preserved for backwards compatibility)
|
|
217
|
-
// ============================================================================
|
|
218
248
|
|
|
219
249
|
static DEFAULT_ACCOUNT_CONFIGURATION =
|
|
220
250
|
accountsImpl.DEFAULT_ACCOUNT_CONFIGURATION;
|
|
@@ -222,21 +252,9 @@ export class BetterStripe {
|
|
|
222
252
|
static DEFAULT_CUSTOMER_CONFIGURATION =
|
|
223
253
|
accountsImpl.DEFAULT_CUSTOMER_CONFIGURATION;
|
|
224
254
|
|
|
225
|
-
// ============================================================================
|
|
226
255
|
// ACCOUNT METHODS
|
|
227
|
-
// ============================================================================
|
|
228
256
|
|
|
229
|
-
async createAccount(
|
|
230
|
-
ctx: RunCtx,
|
|
231
|
-
opts: {
|
|
232
|
-
userId: string;
|
|
233
|
-
email?: string;
|
|
234
|
-
name?: string;
|
|
235
|
-
country?: string;
|
|
236
|
-
orgId?: string;
|
|
237
|
-
metadata?: Record<string, string>;
|
|
238
|
-
},
|
|
239
|
-
) {
|
|
257
|
+
async createAccount(ctx: RunCtx, opts: accountsImpl.CreateAccountOptions) {
|
|
240
258
|
return accountsImpl.createAccount(this.stripe(), this.component, ctx, opts);
|
|
241
259
|
}
|
|
242
260
|
|
|
@@ -276,14 +294,7 @@ export class BetterStripe {
|
|
|
276
294
|
|
|
277
295
|
async getOrCreateAccount(
|
|
278
296
|
ctx: RunCtx,
|
|
279
|
-
opts:
|
|
280
|
-
userId: string;
|
|
281
|
-
email?: string;
|
|
282
|
-
name?: string;
|
|
283
|
-
country?: string;
|
|
284
|
-
orgId?: string;
|
|
285
|
-
metadata?: Record<string, string>;
|
|
286
|
-
},
|
|
297
|
+
opts: accountsImpl.CreateAccountOptions,
|
|
287
298
|
) {
|
|
288
299
|
return accountsImpl.getOrCreateAccount(
|
|
289
300
|
this.stripe(),
|
|
@@ -453,14 +464,7 @@ export class BetterStripe {
|
|
|
453
464
|
*/
|
|
454
465
|
async ensureCustomerAccount(
|
|
455
466
|
ctx: RunCtx,
|
|
456
|
-
opts:
|
|
457
|
-
userId: string;
|
|
458
|
-
email?: string;
|
|
459
|
-
name?: string;
|
|
460
|
-
country?: string;
|
|
461
|
-
orgId?: string;
|
|
462
|
-
metadata?: Record<string, string>;
|
|
463
|
-
},
|
|
467
|
+
opts: accountsImpl.CreateAccountOptions,
|
|
464
468
|
) {
|
|
465
469
|
return accountsImpl.ensureCustomerAccount(
|
|
466
470
|
this.stripe(),
|
|
@@ -470,9 +474,7 @@ export class BetterStripe {
|
|
|
470
474
|
);
|
|
471
475
|
}
|
|
472
476
|
|
|
473
|
-
// ============================================================================
|
|
474
477
|
// PRODUCT METHODS
|
|
475
|
-
// ============================================================================
|
|
476
478
|
|
|
477
479
|
async createProduct(
|
|
478
480
|
ctx: RunCtx,
|
|
@@ -559,9 +561,7 @@ export class BetterStripe {
|
|
|
559
561
|
return productsImpl.deleteProduct(this.stripe(), ctx, opts);
|
|
560
562
|
}
|
|
561
563
|
|
|
562
|
-
// ============================================================================
|
|
563
564
|
// PRICE METHODS
|
|
564
|
-
// ============================================================================
|
|
565
565
|
|
|
566
566
|
async createPrice(
|
|
567
567
|
ctx: RunCtx,
|
|
@@ -649,13 +649,12 @@ export class BetterStripe {
|
|
|
649
649
|
return pricesImpl.listStripePrices(this.stripe(), ctx, opts);
|
|
650
650
|
}
|
|
651
651
|
|
|
652
|
-
// ============================================================================
|
|
653
652
|
// CHECKOUT METHODS
|
|
654
|
-
// ============================================================================
|
|
655
653
|
|
|
656
654
|
async createCheckoutSession(
|
|
657
655
|
ctx: RunCtx,
|
|
658
656
|
opts: {
|
|
657
|
+
idempotencyKey?: string;
|
|
659
658
|
userId: string;
|
|
660
659
|
orgId?: string;
|
|
661
660
|
stripePriceId: string;
|
|
@@ -744,9 +743,7 @@ export class BetterStripe {
|
|
|
744
743
|
return checkoutImpl.updateCheckoutSession(this.stripe(), ctx, opts);
|
|
745
744
|
}
|
|
746
745
|
|
|
747
|
-
// ============================================================================
|
|
748
746
|
// SUBSCRIPTION METHODS
|
|
749
|
-
// ============================================================================
|
|
750
747
|
|
|
751
748
|
/**
|
|
752
749
|
* Create a subscription directly for a V2 buyer (`customer_account`), off the
|
|
@@ -943,14 +940,7 @@ export class BetterStripe {
|
|
|
943
940
|
accountId?: string;
|
|
944
941
|
userId: string;
|
|
945
942
|
orgId?: string;
|
|
946
|
-
status:
|
|
947
|
-
| "active"
|
|
948
|
-
| "trialing"
|
|
949
|
-
| "past_due"
|
|
950
|
-
| "canceled"
|
|
951
|
-
| "incomplete"
|
|
952
|
-
| "unpaid"
|
|
953
|
-
| "paused";
|
|
943
|
+
status: SubscriptionStatus;
|
|
954
944
|
priceId?: string;
|
|
955
945
|
quantity?: number;
|
|
956
946
|
currentPeriodStart?: string;
|
|
@@ -970,9 +960,7 @@ export class BetterStripe {
|
|
|
970
960
|
return subscriptionsImpl.getTrialStatus(this.component, ctx, opts);
|
|
971
961
|
}
|
|
972
962
|
|
|
973
|
-
// ============================================================================
|
|
974
963
|
// INVOICE METHODS
|
|
975
|
-
// ============================================================================
|
|
976
964
|
|
|
977
965
|
async listInvoices(
|
|
978
966
|
ctx: RunCtx,
|
|
@@ -1039,9 +1027,7 @@ export class BetterStripe {
|
|
|
1039
1027
|
return invoicesImpl.getInvoiceFromStripe(this.stripe(), ctx, opts);
|
|
1040
1028
|
}
|
|
1041
1029
|
|
|
1042
|
-
// ============================================================================
|
|
1043
1030
|
// PAYMENT METHOD METHODS
|
|
1044
|
-
// ============================================================================
|
|
1045
1031
|
|
|
1046
1032
|
async listPaymentMethods(
|
|
1047
1033
|
ctx: RunCtx,
|
|
@@ -1072,9 +1058,7 @@ export class BetterStripe {
|
|
|
1072
1058
|
return paymentMethodsImpl.setDefaultPaymentMethod(this.stripe(), ctx, opts);
|
|
1073
1059
|
}
|
|
1074
1060
|
|
|
1075
|
-
// ============================================================================
|
|
1076
1061
|
// ACCOUNT LINK METHODS
|
|
1077
|
-
// ============================================================================
|
|
1078
1062
|
|
|
1079
1063
|
async createAccountLink(
|
|
1080
1064
|
ctx: RunCtx,
|
|
@@ -1150,9 +1134,7 @@ export class BetterStripe {
|
|
|
1150
1134
|
return accountLinksImpl.getCountrySpecs(this.stripe(), ctx, opts);
|
|
1151
1135
|
}
|
|
1152
1136
|
|
|
1153
|
-
// ============================================================================
|
|
1154
1137
|
// PAYOUT METHODS
|
|
1155
|
-
// ============================================================================
|
|
1156
1138
|
|
|
1157
1139
|
async createPayout(
|
|
1158
1140
|
ctx: RunCtx,
|
|
@@ -1253,6 +1235,50 @@ export class BetterStripe {
|
|
|
1253
1235
|
* (`{ available, pending, currency }`, minor units) per currency — each entry
|
|
1254
1236
|
* feeds the headless `PayoutSchedule` component's `balance` prop directly.
|
|
1255
1237
|
*/
|
|
1238
|
+
async getCheckoutSessionFromStripe(
|
|
1239
|
+
_ctx: RunCtx,
|
|
1240
|
+
opts: providerReads.CheckoutSessionReadOptions,
|
|
1241
|
+
) {
|
|
1242
|
+
return providerReads.getCheckoutSessionFromStripe(this.stripe(), opts);
|
|
1243
|
+
}
|
|
1244
|
+
async getSubscriptionFromStripe(
|
|
1245
|
+
_ctx: RunCtx,
|
|
1246
|
+
opts: providerReads.SubscriptionReadOptions,
|
|
1247
|
+
) {
|
|
1248
|
+
return providerReads.getSubscriptionFromStripe(this.stripe(), opts);
|
|
1249
|
+
}
|
|
1250
|
+
async getCouponFromStripe(
|
|
1251
|
+
_ctx: RunCtx,
|
|
1252
|
+
opts: providerReads.CouponReadOptions,
|
|
1253
|
+
) {
|
|
1254
|
+
return providerReads.getCouponFromStripe(this.stripe(), opts);
|
|
1255
|
+
}
|
|
1256
|
+
async getVerifiedSale(
|
|
1257
|
+
_ctx: RunCtx,
|
|
1258
|
+
opts: saleContextImpl.VerifiedSaleOptions,
|
|
1259
|
+
) {
|
|
1260
|
+
return saleContextImpl.getVerifiedSale(this.stripe(), opts);
|
|
1261
|
+
}
|
|
1262
|
+
async settleSale(ctx: RunCtx, opts: settlementImpl.SettleSaleOptions) {
|
|
1263
|
+
return settlementImpl.settleSale(this.stripe(), this.component, ctx, opts);
|
|
1264
|
+
}
|
|
1265
|
+
async selectCommissionInvoice(
|
|
1266
|
+
ctx: RunCtx,
|
|
1267
|
+
opts: commissionImpl.CommissionSelectionOptions,
|
|
1268
|
+
) {
|
|
1269
|
+
return commissionImpl.selectCommissionInvoice(
|
|
1270
|
+
this.stripe(),
|
|
1271
|
+
this.component,
|
|
1272
|
+
ctx,
|
|
1273
|
+
opts,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
async getPayoutSnapshot(
|
|
1277
|
+
_ctx: RunCtx,
|
|
1278
|
+
opts: payoutSnapshotImpl.PayoutSnapshotOptions,
|
|
1279
|
+
) {
|
|
1280
|
+
return payoutSnapshotImpl.getPayoutSnapshot(this.stripe(), opts);
|
|
1281
|
+
}
|
|
1256
1282
|
async getAccountBalance(_ctx: RunCtx, opts: { stripeAccountId: string }) {
|
|
1257
1283
|
return payoutsImpl.getAccountBalance(this.stripe(), opts);
|
|
1258
1284
|
}
|
|
@@ -1268,9 +1294,7 @@ export class BetterStripe {
|
|
|
1268
1294
|
return payoutsImpl.listPayouts(this.component, ctx, opts);
|
|
1269
1295
|
}
|
|
1270
1296
|
|
|
1271
|
-
// ============================================================================
|
|
1272
1297
|
// REFUND METHODS
|
|
1273
|
-
// ============================================================================
|
|
1274
1298
|
|
|
1275
1299
|
/**
|
|
1276
1300
|
* Issue a refund (BTS-34). `refundApplicationFee` and `reverseTransfer`
|
|
@@ -1325,9 +1349,7 @@ export class BetterStripe {
|
|
|
1325
1349
|
return refundsImpl.listRefunds(this.component, ctx, opts);
|
|
1326
1350
|
}
|
|
1327
1351
|
|
|
1328
|
-
// ============================================================================
|
|
1329
1352
|
// DISPUTE METHODS
|
|
1330
|
-
// ============================================================================
|
|
1331
1353
|
|
|
1332
1354
|
async getDisputeByStripeId(
|
|
1333
1355
|
ctx: RunCtx,
|
|
@@ -1397,9 +1419,7 @@ export class BetterStripe {
|
|
|
1397
1419
|
return disputesImpl.closeDispute(this.stripe(), this.component, ctx, opts);
|
|
1398
1420
|
}
|
|
1399
1421
|
|
|
1400
|
-
// ============================================================================
|
|
1401
1422
|
// WEBHOOK ENDPOINT MANAGEMENT
|
|
1402
|
-
// ============================================================================
|
|
1403
1423
|
|
|
1404
1424
|
async listWebhookEndpoints(ctx: RunCtx, opts?: { limit?: number }) {
|
|
1405
1425
|
return webhookEndpointsImpl.listWebhookEndpoints(this.stripe(), ctx, opts);
|
|
@@ -1439,9 +1459,7 @@ export class BetterStripe {
|
|
|
1439
1459
|
return webhookEndpointsImpl.setupEventDestination(this.stripe(), ctx, opts);
|
|
1440
1460
|
}
|
|
1441
1461
|
|
|
1442
|
-
// ============================================================================
|
|
1443
1462
|
// WEBHOOK EVENT QUERIES
|
|
1444
|
-
// ============================================================================
|
|
1445
1463
|
|
|
1446
1464
|
async listWebhookEvents(
|
|
1447
1465
|
ctx: RunCtx,
|
|
@@ -1462,17 +1480,13 @@ export class BetterStripe {
|
|
|
1462
1480
|
}>;
|
|
1463
1481
|
}
|
|
1464
1482
|
|
|
1465
|
-
// ============================================================================
|
|
1466
1483
|
// DATA MANAGEMENT
|
|
1467
|
-
// ============================================================================
|
|
1468
1484
|
|
|
1469
1485
|
async clearAll(ctx: RunCtx): Promise<{ cleared: number; tables: string[] }> {
|
|
1470
1486
|
return dataImpl.clearAll(this.component, ctx);
|
|
1471
1487
|
}
|
|
1472
1488
|
|
|
1473
|
-
// ============================================================================
|
|
1474
1489
|
// SYNC METHODS
|
|
1475
|
-
// ============================================================================
|
|
1476
1490
|
|
|
1477
1491
|
async syncAllAccounts(ctx: RunCtx) {
|
|
1478
1492
|
return accountsImpl.syncAllAccounts(this.stripe(), this.component, ctx);
|
|
@@ -1490,9 +1504,7 @@ export class BetterStripe {
|
|
|
1490
1504
|
);
|
|
1491
1505
|
}
|
|
1492
1506
|
|
|
1493
|
-
// ============================================================================
|
|
1494
1507
|
// WEBHOOK HANDLER EXPORT (BetterAuth-style)
|
|
1495
|
-
// ============================================================================
|
|
1496
1508
|
|
|
1497
1509
|
/**
|
|
1498
1510
|
* Build the two webhook handler function definitions for the app to export
|
|
@@ -1811,38 +1823,7 @@ export class BetterStripe {
|
|
|
1811
1823
|
),
|
|
1812
1824
|
};
|
|
1813
1825
|
|
|
1814
|
-
|
|
1815
|
-
const ASYNC: Record<
|
|
1816
|
-
AsyncHookName,
|
|
1817
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined
|
|
1818
|
-
> = {
|
|
1819
|
-
afterAccountUpdated: hooks?.onAccountUpdated as
|
|
1820
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1821
|
-
afterCheckoutCompleted: hooks?.onCheckoutCompleted as
|
|
1822
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1823
|
-
afterSubscriptionUpdated: hooks?.onSubscriptionUpdated as
|
|
1824
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1825
|
-
afterSubscriptionCanceled: hooks?.onSubscriptionCanceled as
|
|
1826
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1827
|
-
afterTrialEnding: hooks?.onTrialEnding as
|
|
1828
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1829
|
-
afterInvoicePaid: hooks?.onInvoicePaid as
|
|
1830
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1831
|
-
afterInvoicePaymentFailed: hooks?.onInvoicePaymentFailed as
|
|
1832
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1833
|
-
afterPaymentSucceeded: hooks?.onPaymentSucceeded as
|
|
1834
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1835
|
-
afterPaymentFailed: hooks?.onPaymentFailed as
|
|
1836
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1837
|
-
afterPayoutCompleted: hooks?.onPayoutCompleted as
|
|
1838
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1839
|
-
afterRefundCreated: hooks?.onRefundCreated as
|
|
1840
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1841
|
-
afterDisputeCreated: hooks?.onDisputeCreated as
|
|
1842
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1843
|
-
afterDisputeClosed: hooks?.onDisputeClosed as
|
|
1844
|
-
((ctx: AsyncHookCtx, doc: unknown) => Promise<void>) | undefined,
|
|
1845
|
-
};
|
|
1826
|
+
const ASYNC = buildAsyncHookRoutes(hooks);
|
|
1846
1827
|
|
|
1847
1828
|
const syncWebhook = internalMutationGeneric({
|
|
1848
1829
|
args: { dispatcher: v.string(), data: v.any() },
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { expect, it } from "vitest";
|
|
2
|
+
import { allocateSale } from "./allocation.js";
|
|
3
|
+
it("allocates processing and plan fees before affiliate and collaborators", () => {
|
|
4
|
+
const result = allocateSale({
|
|
5
|
+
gross: 10000,
|
|
6
|
+
processingFee: 320,
|
|
7
|
+
feeBasisPoints: 900,
|
|
8
|
+
creatorAccountId: "acct_creator",
|
|
9
|
+
affiliate: { destinationAccountId: "acct_affiliate", basisPoints: 2000 },
|
|
10
|
+
collaborators: [{ destinationAccountId: "acct_other", basisPoints: 2500 }],
|
|
11
|
+
});
|
|
12
|
+
expect(result).toMatchObject({
|
|
13
|
+
sellingFee: 900,
|
|
14
|
+
platformNet: 580,
|
|
15
|
+
affiliateNet: 1820,
|
|
16
|
+
creatorNet: 5460,
|
|
17
|
+
collaboratorNet: 1820,
|
|
18
|
+
});
|
|
19
|
+
expect(
|
|
20
|
+
result.transfers.reduce((n, t) => n + t.amount, 0) + result.sellingFee,
|
|
21
|
+
).toBe(result.gross);
|
|
22
|
+
});
|
|
23
|
+
it("caps fixed commissions and applies the processing fee floor", () => {
|
|
24
|
+
expect(
|
|
25
|
+
allocateSale({
|
|
26
|
+
gross: 10000,
|
|
27
|
+
processingFee: 700,
|
|
28
|
+
feeBasisPoints: 490,
|
|
29
|
+
creatorAccountId: "acct_creator",
|
|
30
|
+
affiliate: { destinationAccountId: "acct_a", fixedAmount: 99999 },
|
|
31
|
+
}),
|
|
32
|
+
).toMatchObject({ affiliateNet: 9300, creatorNet: 0, platformNet: 0 });
|
|
33
|
+
expect(
|
|
34
|
+
allocateSale({
|
|
35
|
+
gross: 12900,
|
|
36
|
+
processingFee: 404,
|
|
37
|
+
feeBasisPoints: 490,
|
|
38
|
+
creatorAccountId: "acct_c",
|
|
39
|
+
affiliate: { destinationAccountId: "acct_a", basisPoints: 2000 },
|
|
40
|
+
}),
|
|
41
|
+
).toMatchObject({ affiliateNet: 2453, creatorNet: 9815 });
|
|
42
|
+
});
|
|
43
|
+
it("retains rounding residue for the creator and rejects invalid or duplicate shares", () => {
|
|
44
|
+
const opts = {
|
|
45
|
+
gross: 101,
|
|
46
|
+
processingFee: 0,
|
|
47
|
+
creatorAccountId: "acct_c",
|
|
48
|
+
collaborators: [
|
|
49
|
+
{ destinationAccountId: "acct_a", basisPoints: 3333 },
|
|
50
|
+
{ destinationAccountId: "acct_b", basisPoints: 3333 },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
expect(allocateSale(opts)).toMatchObject({
|
|
54
|
+
creatorNet: 35,
|
|
55
|
+
collaboratorNet: 66,
|
|
56
|
+
affiliateNet: 0,
|
|
57
|
+
});
|
|
58
|
+
expect(() =>
|
|
59
|
+
allocateSale({
|
|
60
|
+
...opts,
|
|
61
|
+
collaborators: [{ destinationAccountId: "acct_a", basisPoints: 10001 }],
|
|
62
|
+
}),
|
|
63
|
+
).toThrow();
|
|
64
|
+
expect(() =>
|
|
65
|
+
allocateSale({
|
|
66
|
+
...opts,
|
|
67
|
+
collaborators: [opts.collaborators[0], opts.collaborators[0]],
|
|
68
|
+
}),
|
|
69
|
+
).toThrow();
|
|
70
|
+
expect(() => allocateSale({ ...opts, gross: 1.5 })).toThrow();
|
|
71
|
+
expect(() =>
|
|
72
|
+
allocateSale({
|
|
73
|
+
...opts,
|
|
74
|
+
affiliate: {
|
|
75
|
+
destinationAccountId: "acct_z",
|
|
76
|
+
fixedAmount: 1,
|
|
77
|
+
basisPoints: 1,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
).toThrow();
|
|
81
|
+
});
|
|
82
|
+
it("supports zero-dollar previews and zero affiliate shares", () => {
|
|
83
|
+
expect(
|
|
84
|
+
allocateSale({
|
|
85
|
+
gross: 0,
|
|
86
|
+
processingFee: 0,
|
|
87
|
+
creatorAccountId: "acct_creator",
|
|
88
|
+
}),
|
|
89
|
+
).toMatchObject({ gross: 0, creatorNet: 0, affiliateNet: 0, platformNet: 0 });
|
|
90
|
+
expect(
|
|
91
|
+
allocateSale({
|
|
92
|
+
gross: 100,
|
|
93
|
+
processingFee: 3,
|
|
94
|
+
creatorAccountId: "acct_creator",
|
|
95
|
+
affiliate: { destinationAccountId: "acct_a", basisPoints: 0 },
|
|
96
|
+
}),
|
|
97
|
+
).toMatchObject({ creatorNet: 97, affiliateNet: 0 });
|
|
98
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { SplitTransfer } from "../core/fees.js";
|
|
2
|
+
export type SaleAllocationOptions = {
|
|
3
|
+
gross: number;
|
|
4
|
+
processingFee: number;
|
|
5
|
+
/** Inclusive sale fee, with actual Stripe processing as its floor. */
|
|
6
|
+
feeBasisPoints?: number | null;
|
|
7
|
+
creatorAccountId: string;
|
|
8
|
+
affiliate?: {
|
|
9
|
+
destinationAccountId: string;
|
|
10
|
+
basisPoints?: number;
|
|
11
|
+
fixedAmount?: number;
|
|
12
|
+
};
|
|
13
|
+
collaborators?: { destinationAccountId: string; basisPoints: number }[];
|
|
14
|
+
};
|
|
15
|
+
export type SaleAllocation = {
|
|
16
|
+
gross: number;
|
|
17
|
+
processingFee: number;
|
|
18
|
+
sellingFee: number;
|
|
19
|
+
platformNet: number;
|
|
20
|
+
affiliateNet: number;
|
|
21
|
+
creatorNet: number;
|
|
22
|
+
collaboratorNet: number;
|
|
23
|
+
transfers: SplitTransfer[];
|
|
24
|
+
};
|
|
25
|
+
const basisPoints = (n: number) => {
|
|
26
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 10000)
|
|
27
|
+
throw new Error("Share must be an integer from 0 to 10000 basis points.");
|
|
28
|
+
return n;
|
|
29
|
+
};
|
|
30
|
+
const units = (n: number) => {
|
|
31
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Amounts must be non-negative safe integer currency units.",
|
|
34
|
+
);
|
|
35
|
+
return n;
|
|
36
|
+
};
|
|
37
|
+
const share = (amount: number, rate: number) =>
|
|
38
|
+
Number((BigInt(amount) * BigInt(basisPoints(rate))) / 10000n);
|
|
39
|
+
/** Pure fee-first allocation. Fixed amounts are minor units of the sale currency. */
|
|
40
|
+
export function allocateSale(opts: SaleAllocationOptions): SaleAllocation {
|
|
41
|
+
const gross = units(opts.gross),
|
|
42
|
+
processingFee = units(opts.processingFee);
|
|
43
|
+
if (processingFee > gross || !opts.creatorAccountId)
|
|
44
|
+
throw new Error("Invalid gross amount, processing fee or creator account.");
|
|
45
|
+
const rate = basisPoints(opts.feeBasisPoints ?? 0);
|
|
46
|
+
const sellingFee = Math.max(
|
|
47
|
+
processingFee,
|
|
48
|
+
Number((BigInt(gross) * BigInt(rate) + 5000n) / 10000n),
|
|
49
|
+
);
|
|
50
|
+
const net = gross - sellingFee;
|
|
51
|
+
const affiliate = opts.affiliate;
|
|
52
|
+
if (
|
|
53
|
+
affiliate &&
|
|
54
|
+
(!affiliate.destinationAccountId ||
|
|
55
|
+
(affiliate.fixedAmount !== undefined &&
|
|
56
|
+
affiliate.basisPoints !== undefined))
|
|
57
|
+
)
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Choose one affiliate amount or percentage and a destination.",
|
|
60
|
+
);
|
|
61
|
+
const affiliateNet = affiliate
|
|
62
|
+
? Math.min(
|
|
63
|
+
net,
|
|
64
|
+
affiliate.fixedAmount !== undefined
|
|
65
|
+
? units(affiliate.fixedAmount)
|
|
66
|
+
: share(net, affiliate.basisPoints ?? 0),
|
|
67
|
+
)
|
|
68
|
+
: 0;
|
|
69
|
+
const creatorRemainder = net - affiliateNet;
|
|
70
|
+
const collaborators = opts.collaborators ?? [];
|
|
71
|
+
if (
|
|
72
|
+
collaborators.reduce((sum, r) => sum + basisPoints(r.basisPoints), 0) >
|
|
73
|
+
10000
|
|
74
|
+
)
|
|
75
|
+
throw new Error("Collaborator shares exceed the creator remainder.");
|
|
76
|
+
const seen = new Set<string>();
|
|
77
|
+
const transfers: SplitTransfer[] = collaborators.map((r) => {
|
|
78
|
+
if (!r.destinationAccountId || seen.has(r.destinationAccountId))
|
|
79
|
+
throw new Error("Collaborator destinations must be nonempty and unique.");
|
|
80
|
+
seen.add(r.destinationAccountId);
|
|
81
|
+
return {
|
|
82
|
+
destinationAccountId: r.destinationAccountId,
|
|
83
|
+
role: "other",
|
|
84
|
+
amount: share(creatorRemainder, r.basisPoints),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
const collaboratorNet = transfers.reduce((sum, r) => sum + r.amount, 0);
|
|
88
|
+
const creatorNet = creatorRemainder - collaboratorNet;
|
|
89
|
+
transfers.unshift({
|
|
90
|
+
destinationAccountId: opts.creatorAccountId,
|
|
91
|
+
role: "store",
|
|
92
|
+
amount: creatorNet,
|
|
93
|
+
});
|
|
94
|
+
if (affiliate)
|
|
95
|
+
transfers.push({
|
|
96
|
+
destinationAccountId: affiliate.destinationAccountId,
|
|
97
|
+
role: "affiliate",
|
|
98
|
+
amount: affiliateNet,
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
gross,
|
|
102
|
+
processingFee,
|
|
103
|
+
sellingFee,
|
|
104
|
+
platformNet: sellingFee - processingFee,
|
|
105
|
+
affiliateNet,
|
|
106
|
+
creatorNet,
|
|
107
|
+
collaboratorNet,
|
|
108
|
+
transfers,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
import { expect, it, vi } from "vitest";
|
|
3
|
+
import type { Component, RunCtx } from "../helpers.js";
|
|
4
|
+
import { selectCommissionInvoice } from "./commissions.js";
|
|
5
|
+
const ref = (path: string) => ({
|
|
6
|
+
[Symbol.for("toReferencePath")]: `betterStripe/${path}`,
|
|
7
|
+
});
|
|
8
|
+
const component = {
|
|
9
|
+
settlement: {
|
|
10
|
+
commissions: {
|
|
11
|
+
get: ref("settlement/queries/getCommissionTerm"),
|
|
12
|
+
claim: ref("settlement/mutations/claimCommissionInvoice"),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
} as unknown as Component;
|
|
16
|
+
it("finds the first paid nonzero invoice across provider pages and ignores trial invoices", async () => {
|
|
17
|
+
const list = vi
|
|
18
|
+
.fn()
|
|
19
|
+
.mockResolvedValueOnce({
|
|
20
|
+
data: [
|
|
21
|
+
{
|
|
22
|
+
id: "in_later",
|
|
23
|
+
amount_paid: 100,
|
|
24
|
+
status_transitions: { paid_at: 20 },
|
|
25
|
+
created: 20,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "in_trial",
|
|
29
|
+
amount_paid: 0,
|
|
30
|
+
status_transitions: { paid_at: 1 },
|
|
31
|
+
created: 1,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
has_more: true,
|
|
35
|
+
})
|
|
36
|
+
.mockResolvedValueOnce({
|
|
37
|
+
data: [
|
|
38
|
+
{
|
|
39
|
+
id: "in_first",
|
|
40
|
+
amount_paid: 100,
|
|
41
|
+
status_transitions: { paid_at: 10 },
|
|
42
|
+
created: 2,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
has_more: false,
|
|
46
|
+
});
|
|
47
|
+
const raw = {
|
|
48
|
+
invoices: {
|
|
49
|
+
list,
|
|
50
|
+
retrieve: vi.fn().mockResolvedValue({
|
|
51
|
+
id: "in_later",
|
|
52
|
+
status: "paid",
|
|
53
|
+
amount_paid: 100,
|
|
54
|
+
parent: { subscription_details: { subscription: "sub_1" } },
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const runMutation = vi.fn(async (_ref, args) => ({
|
|
59
|
+
...args,
|
|
60
|
+
eligible: false,
|
|
61
|
+
replay: false,
|
|
62
|
+
claimKey: "key",
|
|
63
|
+
}));
|
|
64
|
+
const ctx = {
|
|
65
|
+
runQuery: vi.fn().mockResolvedValue(null),
|
|
66
|
+
runMutation,
|
|
67
|
+
} as unknown as RunCtx;
|
|
68
|
+
await selectCommissionInvoice(raw as unknown as Stripe, component, ctx, {
|
|
69
|
+
subscriptionId: "sub_1",
|
|
70
|
+
invoiceId: "in_later",
|
|
71
|
+
affiliateAccountId: "acct_a",
|
|
72
|
+
term: "one_time",
|
|
73
|
+
});
|
|
74
|
+
expect(runMutation).toHaveBeenCalledWith(
|
|
75
|
+
expect.anything(),
|
|
76
|
+
expect.objectContaining({
|
|
77
|
+
firstPaidInvoiceId: "in_first",
|
|
78
|
+
term: "one_time",
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
expect(list).toHaveBeenCalledTimes(2);
|
|
82
|
+
});
|
|
83
|
+
it("does not consume a failed or zero-dollar invoice", async () => {
|
|
84
|
+
const raw = {
|
|
85
|
+
invoices: {
|
|
86
|
+
retrieve: vi.fn().mockResolvedValue({
|
|
87
|
+
id: "in_trial",
|
|
88
|
+
status: "paid",
|
|
89
|
+
amount_paid: 0,
|
|
90
|
+
parent: { subscription_details: { subscription: "sub_1" } },
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
const mutation = vi.fn(),
|
|
95
|
+
ctx = { runQuery: vi.fn(), runMutation: mutation } as unknown as RunCtx;
|
|
96
|
+
expect(
|
|
97
|
+
await selectCommissionInvoice(raw as unknown as Stripe, component, ctx, {
|
|
98
|
+
subscriptionId: "sub_1",
|
|
99
|
+
invoiceId: "in_trial",
|
|
100
|
+
}),
|
|
101
|
+
).toMatchObject({ status: "not_payable", eligible: false });
|
|
102
|
+
expect(mutation).not.toHaveBeenCalled();
|
|
103
|
+
});
|