@stacksjs/payments 0.75.268 → 0.75.270

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.
@@ -1,5 +1,13 @@
1
1
  import type { UserModel } from '@stacksjs/orm';
2
2
  import type Stripe from 'stripe';
3
+ /**
4
+ * Validate that a redirect target URL is on the configured app origin.
5
+ * Without this, an attacker who could influence params (e.g. via a
6
+ * forged form) could route the post-payment redirect to a phishing
7
+ * site impersonating the brand. We compare origins (scheme+host+port)
8
+ * so subdomains and paths are still flexible within the trust boundary.
9
+ */
10
+ export declare function assertSameOriginUrl(url: string | undefined, label: string): void;
3
11
  export declare const manageCheckout: Checkout;
4
12
  export declare interface Checkout {
5
13
  create: (user: UserModel, params: Stripe.Checkout.SessionCreateParams) => Promise<Stripe.Response<Stripe.Checkout.Session>>
@@ -1 +1 @@
1
- import{config}from"@stacksjs/config";import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";function assertSameOriginUrl(url,label){if(!url)return;let parsed;try{parsed=new URL(url)}catch{throw Error(`[payments/checkout] ${label} is not a valid URL: ${url}`)}const appUrl=config?.app?.url;if(!appUrl)return;let appOrigin;try{appOrigin=new URL(appUrl.startsWith("http")?appUrl:`https://${appUrl}`).origin}catch{return}if(parsed.origin!==appOrigin)throw Error(`[payments/checkout] ${label} origin (${parsed.origin}) does not match app origin (${appOrigin})`)}export const manageCheckout=(()=>{async function create(user,params){const customerId=params.customer||user.stripe_id;if(!customerId)throw Error("User has no Stripe customer");assertSameOriginUrl(params.success_url,"success_url");assertSameOriginUrl(params.cancel_url,"cancel_url");const mergedParams={...{customer:customerId,mode:"payment",success_url:params.success_url,cancel_url:params.cancel_url},...params};return await stripe.checkout.sessions.create(mergedParams,{idempotencyKey:freshIdempotencyKey("checkout.session.create",user.id)})}return{create}})();
1
+ import{config}from"@stacksjs/config";import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";export function assertSameOriginUrl(url,label){if(!url)return;let parsed;try{parsed=new URL(url)}catch{throw Error(`[payments/checkout] ${label} is not a valid URL: ${url}`)}const appUrl=config?.app?.url;if(!appUrl)return;let appOrigin;try{appOrigin=new URL(appUrl.startsWith("http")?appUrl:`https://${appUrl}`).origin}catch{return}if(parsed.origin!==appOrigin)throw Error(`[payments/checkout] ${label} origin (${parsed.origin}) does not match app origin (${appOrigin})`)}export const manageCheckout=(()=>{async function create(user,params){const customerId=params.customer||user.stripe_id;if(!customerId)throw Error("User has no Stripe customer");assertSameOriginUrl(params.success_url,"success_url");assertSameOriginUrl(params.cancel_url,"cancel_url");const mergedParams={...{customer:customerId,mode:"payment",success_url:params.success_url,cancel_url:params.cancel_url},...params};return await stripe.checkout.sessions.create(mergedParams,{idempotencyKey:freshIdempotencyKey("checkout.session.create",user.id)})}return{create}})();
@@ -7,6 +7,8 @@ import type Stripe from 'stripe';
7
7
  * parameters give another.
8
8
  */
9
9
  export declare function connectedAccountIdempotencyKey(scope: 'connect.account.create' | 'connect.account.create_test', userId: number | string, params: Stripe.AccountCreateParams, reference?: string): string;
10
+ /** The defaults' controller unless the caller chose a `type` or a `controller` of its own. */
11
+ export declare function connectedAccountDefaults(params: Partial<Stripe.AccountCreateParams>): Partial<Stripe.AccountCreateParams>;
10
12
  /**
11
13
  * Connected-account ids are `acct_...`. Checking the shape here turns a
12
14
  * confusing Stripe 404 (which reads as "no such account" even when the caller
@@ -14,6 +16,19 @@ export declare function connectedAccountIdempotencyKey(scope: 'connect.account.c
14
16
  * names the argument.
15
17
  */
16
18
  export declare function requireAccountId(accountId: string, caller: string): void;
19
+ /**
20
+ * How a new connected account is controlled when the caller does not say:
21
+ * Stripe's Managed Risk. Stripe manages the account's credit and fraud risk
22
+ * and carries unrecoverable negative balances, the merchant gets the full
23
+ * Stripe Dashboard and pays Stripe's fees on its own charges, and Stripe
24
+ * collects what onboarding needs. It is what Stripe recommends for SaaS
25
+ * platforms and the only setup a platform that has not taken on loss
26
+ * liability may create: Stripe refuses the legacy `type: 'express'` and
27
+ * `type: 'custom'` there ("your platform as the losses collector"). Sell on
28
+ * such an account with direct charges (`connect/direct.ts`). A platform that
29
+ * did accept loss liability passes `type` or its own `controller`.
30
+ */
31
+ export declare const MANAGED_RISK_CONTROLLER: Stripe.AccountCreateParams.Controller;
17
32
  export declare const manageConnectedAccount: ManageConnectedAccount;
18
33
  /**
19
34
  * A connected account's readiness, reduced to the questions a marketplace
@@ -1 +1 @@
1
- import{stripe}from"../drivers/stripe";import{idempotencyFingerprint,stacksIdempotencyKey}from"../idempotency";export function connectedAccountIdempotencyKey(scope,userId,params,reference){return stacksIdempotencyKey(scope,userId,reference,idempotencyFingerprint(params))}export const manageConnectedAccount=(()=>{async function createConnectedAccount(user,params={},options={}){const merged={...{type:"express",email:user.email??void 0,metadata:{stacks_user_id:String(user.id)}},...params};return await stripe.accounts.create(merged,{idempotencyKey:connectedAccountIdempotencyKey("connect.account.create",user.id,merged,options.reference)})}async function createTestConnectedAccount(user,params={},options={}){const merged={...{type:"express",email:user.email??void 0,business_type:"individual",capabilities:{card_payments:{requested:!0},transfers:{requested:!0}},business_profile:{url:"https://example.com",mcc:"5812"},metadata:{stacks_user_id:String(user.id),stacks_test_account:"true"}},...params};return await stripe.accounts.create(merged,{idempotencyKey:connectedAccountIdempotencyKey("connect.account.create_test",user.id,merged,options.reference)})}async function accountOnboardingLink(accountId,options){requireAccountId(accountId,"accountOnboardingLink");if(!options?.refreshUrl||!options?.returnUrl)throw Error("[payments/connect] accountOnboardingLink requires refreshUrl and returnUrl");return await stripe.accountLinks.create({account:accountId,refresh_url:options.refreshUrl,return_url:options.returnUrl,type:options.type??"account_onboarding",...options.collect?{collection_options:{fields:options.collect}}:{}})}async function retrieveConnectedAccount(accountId){requireAccountId(accountId,"retrieveConnectedAccount");return await stripe.accounts.retrieve(accountId)}async function accountStatus(accountId){const account=await retrieveConnectedAccount(accountId),requirements=account.requirements;return{chargesEnabled:account.charges_enabled===!0,payoutsEnabled:account.payouts_enabled===!0,detailsSubmitted:account.details_submitted===!0,currentlyDue:requirements?.currently_due??[],pastDue:requirements?.past_due??[],disabledReason:requirements?.disabled_reason??null,raw:account}}return{createConnectedAccount,createTestConnectedAccount,accountOnboardingLink,accountStatus,retrieveConnectedAccount}})();export function requireAccountId(accountId,caller){if(!accountId||typeof accountId!=="string")throw Error(`[payments/connect] ${caller} requires a connected account id`);if(!accountId.startsWith("acct_"))throw Error(`[payments/connect] ${caller} expects a connected account id (acct_...), got "${accountId}"`)}
1
+ import{stripe}from"../drivers/stripe";import{idempotencyFingerprint,stacksIdempotencyKey}from"../idempotency";export function connectedAccountIdempotencyKey(scope,userId,params,reference){return stacksIdempotencyKey(scope,userId,reference,idempotencyFingerprint(params))}export const MANAGED_RISK_CONTROLLER={stripe_dashboard:{type:"full"},fees:{payer:"account"},losses:{payments:"stripe"},requirement_collection:"stripe"};export function connectedAccountDefaults(params){return params.type||params.controller?{}:{controller:MANAGED_RISK_CONTROLLER}}export const manageConnectedAccount=(()=>{async function createConnectedAccount(user,params={},options={}){const merged={...{...connectedAccountDefaults(params),email:user.email??void 0,metadata:{stacks_user_id:String(user.id)}},...params};return await stripe.accounts.create(merged,{idempotencyKey:connectedAccountIdempotencyKey("connect.account.create",user.id,merged,options.reference)})}async function createTestConnectedAccount(user,params={},options={}){const merged={...{...connectedAccountDefaults(params),email:user.email??void 0,business_type:"individual",capabilities:{card_payments:{requested:!0},transfers:{requested:!0}},business_profile:{url:"https://example.com",mcc:"5812"},metadata:{stacks_user_id:String(user.id),stacks_test_account:"true"}},...params};return await stripe.accounts.create(merged,{idempotencyKey:connectedAccountIdempotencyKey("connect.account.create_test",user.id,merged,options.reference)})}async function accountOnboardingLink(accountId,options){requireAccountId(accountId,"accountOnboardingLink");if(!options?.refreshUrl||!options?.returnUrl)throw Error("[payments/connect] accountOnboardingLink requires refreshUrl and returnUrl");return await stripe.accountLinks.create({account:accountId,refresh_url:options.refreshUrl,return_url:options.returnUrl,type:options.type??"account_onboarding",...options.collect?{collection_options:{fields:options.collect}}:{}})}async function retrieveConnectedAccount(accountId){requireAccountId(accountId,"retrieveConnectedAccount");return await stripe.accounts.retrieve(accountId)}async function accountStatus(accountId){const account=await retrieveConnectedAccount(accountId),requirements=account.requirements;return{chargesEnabled:account.charges_enabled===!0,payoutsEnabled:account.payouts_enabled===!0,detailsSubmitted:account.details_submitted===!0,currentlyDue:requirements?.currently_due??[],pastDue:requirements?.past_due??[],disabledReason:requirements?.disabled_reason??null,raw:account}}return{createConnectedAccount,createTestConnectedAccount,accountOnboardingLink,accountStatus,retrieveConnectedAccount}})();export function requireAccountId(accountId,caller){if(!accountId||typeof accountId!=="string")throw Error(`[payments/connect] ${caller} requires a connected account id`);if(!accountId.startsWith("acct_"))throw Error(`[payments/connect] ${caller} expects a connected account id (acct_...), got "${accountId}"`)}
@@ -0,0 +1,111 @@
1
+ import type { DestinationOptions } from './destination';
2
+ import type { InlineRecurringPrice } from './subscription';
3
+ import type { UserModel } from '@stacksjs/orm';
4
+ import type Stripe from 'stripe';
5
+ /**
6
+ * Request options that make a Stripe call as the connected account (the
7
+ * `Stripe-Account` header), merged with any others (an idempotency key).
8
+ *
9
+ * @example
10
+ * await stripe.checkout.sessions.retrieve(sessionId, asAccount(gym.stripe_account_id))
11
+ */
12
+ export declare function asAccount(accountId: string, extra?: Stripe.RequestOptions): Stripe.RequestOptions;
13
+ /** The fields of a PaymentIntent for `amount` minor units charged directly, to spread into its params. */
14
+ export declare function directPaymentIntentParams(amount: number, options?: DirectFeeOptions): DirectPaymentFields;
15
+ /**
16
+ * `payment_intent_data` for a Checkout Session in `mode: 'payment'`, created
17
+ * on the connected account, whose line items total `amount` minor units.
18
+ * `extra` carries the PaymentIntent's other fields (metadata, a description);
19
+ * any destination fields in it are dropped, since a direct charge has none.
20
+ */
21
+ export declare function directCheckoutPaymentData(amount: number, options?: DirectFeeOptions, extra?: Stripe.Checkout.SessionCreateParams.PaymentIntentData): Stripe.Checkout.SessionCreateParams.PaymentIntentData;
22
+ /**
23
+ * `subscription_data` for a Checkout Session in `mode: 'subscription'`,
24
+ * created on the connected account: every invoice pays the platform
25
+ * `feePercent`. `extra` carries the subscription's other fields.
26
+ */
27
+ export declare function directCheckoutSubscriptionData(options?: Pick<DirectFeeOptions, 'feePercent' | 'feeCents'>, extra?: Stripe.Checkout.SessionCreateParams.SubscriptionData): Stripe.Checkout.SessionCreateParams.SubscriptionData;
28
+ /** The fields of a subscription created directly on the account (`stripe.subscriptions.create`). */
29
+ export declare function directSubscriptionParams(options?: Pick<DirectFeeOptions, 'feePercent' | 'feeCents'>): DirectSubscriptionFields;
30
+ /**
31
+ * The idempotency key for creating `user`'s customer on `accountId`: the same
32
+ * for every attempt with the same details, so two requests racing to create
33
+ * one make one, while a changed name or email is a new request rather than a
34
+ * reused key Stripe would refuse.
35
+ */
36
+ export declare function connectedCustomerIdempotencyKey(accountId: string, userId: number | string, params?: Stripe.CustomerCreateParams): string;
37
+ /**
38
+ * The user's customer on a connected account, created on first use. A
39
+ * customer belongs to one account, so the platform's own customer (the user's
40
+ * `stripe_id`) cannot pay a direct charge; each merchant the user buys from
41
+ * holds its own. Found again by the user's id in the customer's metadata,
42
+ * looked up by email first (immediately consistent) and by metadata search
43
+ * when the email has changed, and created with a deterministic idempotency
44
+ * key, so nothing on the app's side has to remember it.
45
+ */
46
+ export declare function connectedCustomer(user: Pick<UserModel, 'id' | 'email' | 'name'> & { email?: string | null, name?: string | null }, accountId: string, client?: CustomerClient): Promise<Stripe.Customer>;
47
+ /**
48
+ * A Checkout Session on the connected account, as `manageCheckout.create`
49
+ * makes one on the platform: the user's customer there unless `params` names
50
+ * one, return URLs held to the app's origin, and a fresh idempotency key so a
51
+ * retried request does not open a second session.
52
+ *
53
+ * @example
54
+ * await directCheckout(user, gym.stripe_account_id, {
55
+ * mode: 'payment', line_items,
56
+ * payment_intent_data: directCheckoutPaymentData(checkoutLineItemsTotal(line_items), { feeCents }),
57
+ * success_url, cancel_url,
58
+ * })
59
+ */
60
+ export declare function directCheckout(user: UserModel, accountId: string, params: Stripe.Checkout.SessionCreateParams, client?: Pick<Stripe, 'customers' | 'checkout'>): Promise<Stripe.Response<Stripe.Checkout.Session>>;
61
+ /** Subscriptions billed on the merchant's account, the platform paid its fee from each invoice. */
62
+ export declare const manageDirectSubscription: ManageDirectSubscription;
63
+ export declare interface DirectSubscriptionCheckoutOptions {
64
+ account: string
65
+ price: string | InlineRecurringPrice
66
+ quantity?: number
67
+ trialDays?: number
68
+ platformFeePercent?: number
69
+ metadata?: Record<string, string>
70
+ clientReferenceId?: string
71
+ customer?: string
72
+ successUrl: string
73
+ cancelUrl: string
74
+ sessionParams?: Partial<Stripe.Checkout.SessionCreateParams>
75
+ }
76
+ export declare interface ManageDirectSubscription {
77
+ subscriptionData: (options?: Pick<DirectSubscriptionCheckoutOptions, 'trialDays' | 'platformFeePercent' | 'metadata'>) => Stripe.Checkout.SessionCreateParams.SubscriptionData
78
+ checkout: (user: UserModel, options: DirectSubscriptionCheckoutOptions, client?: Pick<Stripe, 'customers' | 'checkout'>) => Promise<Stripe.Response<Stripe.Checkout.Session>>
79
+ }
80
+ /**
81
+ * Direct charges: the sale is made on the merchant's own connected account,
82
+ * and the platform takes its cut as an application fee.
83
+ *
84
+ * This is the funds flow Stripe's Managed Risk is for (Stripe manages the
85
+ * merchant's credit and fraud risk and carries unrecoverable negative
86
+ * balances), and the one Stripe now requires of a platform that has not taken
87
+ * on loss liability itself: destination charges and separate transfers need
88
+ * the platform to be the losses collector.
89
+ *
90
+ * Everything that belongs to the sale lives on the connected account: the
91
+ * customer, the Checkout Session, the PaymentIntent, the subscription and its
92
+ * invoices, saved payment methods and mandates, and the refund. A call acts as
93
+ * the account with Stripe's `Stripe-Account` header, `asAccount(id)` as the
94
+ * request options. Stripe's processing fees are the merchant's; the platform
95
+ * receives the application fee whole.
96
+ *
97
+ * | Where the money is taken | Fields | Request options |
98
+ * |-----------------------------------|--------------------------------------------|---------------------|
99
+ * | Checkout, `mode: 'payment'` | `payment_intent_data.application_fee_amount` | `asAccount(id)` |
100
+ * | Checkout, `mode: 'subscription'` | `subscription_data.application_fee_percent` | `asAccount(id)` |
101
+ * | `stripe.subscriptions.create` | `application_fee_percent` | `asAccount(id)` |
102
+ * | `stripe.paymentIntents.create` | `application_fee_amount` | `asAccount(id)` |
103
+ */
104
+ /** The platform's cut of a direct charge: a percent or an amount, as for destination charges. */
105
+ export type DirectFeeOptions = Pick<DestinationOptions, 'feePercent' | 'feeCents' | 'rounding'>;
106
+ /** The Connect fields of a one-off direct charge. */
107
+ export type DirectPaymentFields = Pick<Stripe.PaymentIntentCreateParams, 'application_fee_amount'>;
108
+ /** The Connect fields of a subscription created directly on the account. */
109
+ export type DirectSubscriptionFields = Pick<Stripe.SubscriptionCreateParams, 'application_fee_percent'>;
110
+ /** The Stripe customer calls a connected customer needs. */
111
+ declare type CustomerClient = Pick<Stripe, 'customers'>;
@@ -0,0 +1 @@
1
+ import{stripe}from"../drivers/stripe";import{assertSameOriginUrl}from"../billable/checkout";import{freshIdempotencyKey,idempotencyFingerprint,stacksIdempotencyKey}from"../idempotency";import{requireAccountId}from"./account";import{destinationFeeCents,destinationFeePercent}from"./destination";import{manageDestinationSubscription}from"./subscription";export function asAccount(accountId,extra={}){requireAccountId(accountId,"asAccount");return{...extra,stripeAccount:accountId}}export function directPaymentIntentParams(amount,options={}){const fee=destinationFeeCents(amount,options);return fee>0?{application_fee_amount:fee}:{}}export function directCheckoutPaymentData(amount,options={},extra={}){const rest={...extra};delete rest.application_fee_amount;delete rest.transfer_data;delete rest.on_behalf_of;delete rest.transfer_group;return{...rest,...directPaymentIntentParams(amount,options)}}export function directCheckoutSubscriptionData(options={},extra={}){const percent=destinationFeePercent(options),rest={...extra};delete rest.application_fee_percent;delete rest.transfer_data;delete rest.on_behalf_of;return{...rest,...percent!=null?{application_fee_percent:percent}:{}}}export function directSubscriptionParams(options={}){const percent=destinationFeePercent(options);return percent!=null?{application_fee_percent:percent}:{}}export function connectedCustomerIdempotencyKey(accountId,userId,params={}){return stacksIdempotencyKey("connect.customer.create",userId,accountId,idempotencyFingerprint(params))}export async function connectedCustomer(user,accountId,client=stripe){requireAccountId(accountId,"connectedCustomer");const userId=String(user.id),options=asAccount(accountId),own=(customer)=>!(("deleted"in customer)&&customer.deleted)&&customer.metadata?.stacks_user_id===userId;if(user.email){const found=(await client.customers.list({email:user.email,limit:100},options)).data.find(own);if(found)return found}try{const found=(await client.customers.search({query:`metadata['stacks_user_id']:'${userId.replace(/'/g,"")}'`,limit:1},options)).data.find(own);if(found)return found}catch{}const params={...user.email?{email:user.email}:{},...user.name?{name:user.name}:{},metadata:{stacks_user_id:userId}};return await client.customers.create(params,asAccount(accountId,{idempotencyKey:connectedCustomerIdempotencyKey(accountId,userId,params)}))}export async function directCheckout(user,accountId,params,client=stripe){requireAccountId(accountId,"directCheckout");assertSameOriginUrl(params.success_url,"success_url");assertSameOriginUrl(params.cancel_url,"cancel_url");const customer=params.customer??(params.customer_email?void 0:(await connectedCustomer(user,accountId,client)).id);return await client.checkout.sessions.create({mode:"payment",...params,...customer?{customer}:{}},asAccount(accountId,{idempotencyKey:freshIdempotencyKey("connect.checkout.create",user.id,accountId)}))}export const manageDirectSubscription=(()=>{function subscriptionData(options={}){if(options.trialDays!=null&&(!Number.isInteger(options.trialDays)||options.trialDays<1||options.trialDays>730))throw Error(`[payments/connect] trialDays must be a whole number from 1 to 730, got ${options.trialDays}`);const fee=manageDestinationSubscription.feePercentFor({platformFeePercent:options.platformFeePercent});return{...fee!=null?{application_fee_percent:fee}:{},...options.trialDays?{trial_period_days:options.trialDays}:{},...options.metadata?{metadata:options.metadata}:{}}}async function checkout(user,options,client=stripe){requireAccountId(options.account,"manageDirectSubscription.checkout");const customer=options.customer??(await connectedCustomer(user,options.account,client)).id;return await directCheckout(user,options.account,{mode:"subscription",customer,line_items:[manageDestinationSubscription.lineItem(options.price,options.quantity)],subscription_data:subscriptionData(options),success_url:options.successUrl,cancel_url:options.cancelUrl,...options.clientReferenceId?{client_reference_id:options.clientReferenceId}:{},...options.metadata?{metadata:options.metadata}:{},...options.sessionParams},client)}return{subscriptionData,checkout}})();
@@ -1,3 +1,4 @@
1
+ import { asAccount, connectedCustomer, directCheckout, directCheckoutPaymentData, directCheckoutSubscriptionData, directPaymentIntentParams, directSubscriptionParams, manageDirectSubscription } from './direct';
1
2
  import { checkoutLineItemsTotal, destinationCheckoutPaymentData, destinationCheckoutSubscriptionData, destinationFeeCents, destinationFeePercent, destinationPaymentIntentParams, destinationSubscriptionParams, splitFee } from './destination';
2
3
  import { connectRefund, connectRefundParams, isDestinationCharge } from './refund';
3
4
  import { manageConnectedAccount } from './account';
@@ -25,6 +26,14 @@ export declare const connect: {
25
26
  subscriptionParams: typeof destinationSubscriptionParams;
26
27
  lineItemsTotal: typeof checkoutLineItemsTotal;
27
28
  splitFee: typeof splitFee;
29
+ asAccount: typeof asAccount;
30
+ customer: typeof connectedCustomer;
31
+ directCheckout: typeof directCheckout;
32
+ directPaymentIntentParams: typeof directPaymentIntentParams;
33
+ directCheckoutPaymentData: typeof directCheckoutPaymentData;
34
+ directCheckoutSubscriptionData: typeof directCheckoutSubscriptionData;
35
+ directSubscriptionParams: typeof directSubscriptionParams;
36
+ directSubscriptionCheckout: typeof manageDirectSubscription.checkout;
28
37
  refund: typeof connectRefund;
29
38
  refundParams: typeof connectRefundParams;
30
39
  isDestinationCharge: typeof isDestinationCharge;
@@ -46,6 +55,7 @@ export declare const connect: {
46
55
  account: typeof manageConnectedAccount;
47
56
  charge: typeof manageDestinationCharge;
48
57
  subscription: typeof manageDestinationSubscription;
58
+ directSubscription: typeof manageDirectSubscription;
49
59
  transfer: typeof manageTransfer;
50
60
  payout: typeof managePayout
51
61
  };
@@ -63,6 +73,7 @@ export declare const connect: {
63
73
  export * from './account';
64
74
  export * from './charge';
65
75
  export * from './destination';
76
+ export * from './direct';
66
77
  export * from './payout';
67
78
  export * from './refund';
68
79
  export * from './subscription';
@@ -1 +1 @@
1
- export*from"./account";export*from"./charge";export*from"./destination";export*from"./payout";export*from"./refund";export*from"./subscription";export*from"./transfer";export*from"./webhook";import{manageConnectedAccount}from"./account";import{manageDestinationCharge}from"./charge";import{checkoutLineItemsTotal,destinationCheckoutPaymentData,destinationCheckoutSubscriptionData,destinationFeeCents,destinationFeePercent,destinationPaymentIntentParams,destinationSubscriptionParams,splitFee}from"./destination";import{managePayout}from"./payout";import{connectRefund,connectRefundParams,isDestinationCharge}from"./refund";import{manageDestinationSubscription}from"./subscription";import{manageTransfer}from"./transfer";import{manageConnectWebhook}from"./webhook";export const connect={createAccount:manageConnectedAccount.createConnectedAccount,createTestAccount:manageConnectedAccount.createTestConnectedAccount,onboardingLink:manageConnectedAccount.accountOnboardingLink,accountStatus:manageConnectedAccount.accountStatus,retrieveAccount:manageConnectedAccount.retrieveConnectedAccount,destinationCharge:manageDestinationCharge.destinationCharge,applicationFeeFor:manageDestinationCharge.applicationFeeFor,feeCents:destinationFeeCents,feePercent:destinationFeePercent,paymentIntentParams:destinationPaymentIntentParams,checkoutPaymentData:destinationCheckoutPaymentData,checkoutSubscriptionData:destinationCheckoutSubscriptionData,subscriptionParams:destinationSubscriptionParams,lineItemsTotal:checkoutLineItemsTotal,splitFee,refund:connectRefund,refundParams:connectRefundParams,isDestinationCharge,subscriptionCheckout:manageDestinationSubscription.checkout,subscriptionFeePercent:manageDestinationSubscription.feePercentFor,subscriptionDestination:manageDestinationSubscription.destinationOf,createTransfer:manageTransfer.createTransfer,reverseTransfer:manageTransfer.reverseTransfer,listTransfers:manageTransfer.listTransfers,retrieveTransfer:manageTransfer.retrieveTransfer,listPayouts:managePayout.listPayouts,retrievePayout:managePayout.retrievePayout,accountBalance:managePayout.accountBalance,onAccount:manageConnectWebhook.onAccount,onPayout:manageConnectWebhook.onPayout,getAccount:manageConnectWebhook.getAccount,getPayout:manageConnectWebhook.getPayout,getConnectedAccountId:manageConnectWebhook.getConnectedAccountId,account:manageConnectedAccount,charge:manageDestinationCharge,subscription:manageDestinationSubscription,transfer:manageTransfer,payout:managePayout};
1
+ export*from"./account";export*from"./charge";export*from"./destination";export*from"./direct";export*from"./payout";export*from"./refund";export*from"./subscription";export*from"./transfer";export*from"./webhook";import{manageConnectedAccount}from"./account";import{manageDestinationCharge}from"./charge";import{asAccount,connectedCustomer,directCheckout,directCheckoutPaymentData,directCheckoutSubscriptionData,directPaymentIntentParams,directSubscriptionParams,manageDirectSubscription}from"./direct";import{checkoutLineItemsTotal,destinationCheckoutPaymentData,destinationCheckoutSubscriptionData,destinationFeeCents,destinationFeePercent,destinationPaymentIntentParams,destinationSubscriptionParams,splitFee}from"./destination";import{managePayout}from"./payout";import{connectRefund,connectRefundParams,isDestinationCharge}from"./refund";import{manageDestinationSubscription}from"./subscription";import{manageTransfer}from"./transfer";import{manageConnectWebhook}from"./webhook";export const connect={createAccount:manageConnectedAccount.createConnectedAccount,createTestAccount:manageConnectedAccount.createTestConnectedAccount,onboardingLink:manageConnectedAccount.accountOnboardingLink,accountStatus:manageConnectedAccount.accountStatus,retrieveAccount:manageConnectedAccount.retrieveConnectedAccount,destinationCharge:manageDestinationCharge.destinationCharge,applicationFeeFor:manageDestinationCharge.applicationFeeFor,feeCents:destinationFeeCents,feePercent:destinationFeePercent,paymentIntentParams:destinationPaymentIntentParams,checkoutPaymentData:destinationCheckoutPaymentData,checkoutSubscriptionData:destinationCheckoutSubscriptionData,subscriptionParams:destinationSubscriptionParams,lineItemsTotal:checkoutLineItemsTotal,splitFee,asAccount,customer:connectedCustomer,directCheckout,directPaymentIntentParams,directCheckoutPaymentData,directCheckoutSubscriptionData,directSubscriptionParams,directSubscriptionCheckout:manageDirectSubscription.checkout,refund:connectRefund,refundParams:connectRefundParams,isDestinationCharge,subscriptionCheckout:manageDestinationSubscription.checkout,subscriptionFeePercent:manageDestinationSubscription.feePercentFor,subscriptionDestination:manageDestinationSubscription.destinationOf,createTransfer:manageTransfer.createTransfer,reverseTransfer:manageTransfer.reverseTransfer,listTransfers:manageTransfer.listTransfers,retrieveTransfer:manageTransfer.retrieveTransfer,listPayouts:managePayout.listPayouts,retrievePayout:managePayout.retrievePayout,accountBalance:managePayout.accountBalance,onAccount:manageConnectWebhook.onAccount,onPayout:manageConnectWebhook.onPayout,getAccount:manageConnectWebhook.getAccount,getPayout:manageConnectWebhook.getPayout,getConnectedAccountId:manageConnectWebhook.getConnectedAccountId,account:manageConnectedAccount,charge:manageDestinationCharge,subscription:manageDestinationSubscription,directSubscription:manageDirectSubscription,transfer:manageTransfer,payout:managePayout};
@@ -63,4 +63,5 @@ export declare interface ConnectRefundOptions {
63
63
  metadata?: Record<string, string>
64
64
  refundApplicationFee?: boolean
65
65
  reverseTransfer?: boolean
66
+ account?: string
66
67
  }
@@ -1 +1 @@
1
- import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";function expandedCharge(source){return source.latest_charge&&typeof source.latest_charge==="object"?source.latest_charge:null}export function isDestinationCharge(source){if(!source)return!1;const charge=expandedCharge(source);return Boolean(source.transfer_data?.destination||source.transfer||charge?.transfer_data?.destination||charge?.transfer)}export function tookApplicationFee(source){if(!source)return!1;const charge=expandedCharge(source);return Boolean((source.application_fee_amount??0)>0||source.application_fee||(charge?.application_fee_amount??0)>0||charge?.application_fee)}export function connectRefundParams(source,options={}){if(!source?.id||typeof source.id!=="string")throw Error("[payments/connect] connectRefundParams needs the PaymentIntent or Charge to refund");if(options.amount!=null&&(!Number.isSafeInteger(options.amount)||options.amount<=0))throw Error(`[payments/connect] a refund amount is a positive whole number of minor units, got ${options.amount}`);const isIntent=source.object==="payment_intent"||source.id.startsWith("pi_"),destination=isDestinationCharge(source),reverse=destination&&(options.reverseTransfer??!0),refundFee=destination&&tookApplicationFee(source)&&(options.refundApplicationFee??!0);return{...isIntent?{payment_intent:source.id}:{charge:source.id},...options.amount!=null?{amount:options.amount}:{},...options.reason?{reason:options.reason}:{},...options.metadata?{metadata:options.metadata}:{},...reverse?{reverse_transfer:!0}:{},...refundFee?{refund_application_fee:!0}:{}}}export async function connectRefund(paymentId,options={}){if(!paymentId||typeof paymentId!=="string")throw Error("[payments/connect] connectRefund needs a PaymentIntent or Charge id");const{idempotencyKey,client=stripe,...refundOptions}=options,source=paymentId.startsWith("pi_")?await client.paymentIntents.retrieve(paymentId,{expand:["latest_charge"]}):await client.charges.retrieve(paymentId);return await client.refunds.create(connectRefundParams(source,refundOptions),{idempotencyKey:idempotencyKey??freshIdempotencyKey("connect.refund",paymentId,refundOptions.amount??"full")})}
1
+ import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";function expandedCharge(source){return source.latest_charge&&typeof source.latest_charge==="object"?source.latest_charge:null}export function isDestinationCharge(source){if(!source)return!1;const charge=expandedCharge(source);return Boolean(source.transfer_data?.destination||source.transfer||charge?.transfer_data?.destination||charge?.transfer)}export function tookApplicationFee(source){if(!source)return!1;const charge=expandedCharge(source);return Boolean((source.application_fee_amount??0)>0||source.application_fee||(charge?.application_fee_amount??0)>0||charge?.application_fee)}export function connectRefundParams(source,options={}){if(!source?.id||typeof source.id!=="string")throw Error("[payments/connect] connectRefundParams needs the PaymentIntent or Charge to refund");if(options.amount!=null&&(!Number.isSafeInteger(options.amount)||options.amount<=0))throw Error(`[payments/connect] a refund amount is a positive whole number of minor units, got ${options.amount}`);const isIntent=source.object==="payment_intent"||source.id.startsWith("pi_"),destination=isDestinationCharge(source),reverse=destination&&(options.reverseTransfer??!0),refundFee=(destination||Boolean(options.account))&&tookApplicationFee(source)&&(options.refundApplicationFee??!0);return{...isIntent?{payment_intent:source.id}:{charge:source.id},...options.amount!=null?{amount:options.amount}:{},...options.reason?{reason:options.reason}:{},...options.metadata?{metadata:options.metadata}:{},...reverse?{reverse_transfer:!0}:{},...refundFee?{refund_application_fee:!0}:{}}}export async function connectRefund(paymentId,options={}){if(!paymentId||typeof paymentId!=="string")throw Error("[payments/connect] connectRefund needs a PaymentIntent or Charge id");const{idempotencyKey,client=stripe,...refundOptions}=options,onAccount=refundOptions.account?{stripeAccount:refundOptions.account}:{},source=paymentId.startsWith("pi_")?await client.paymentIntents.retrieve(paymentId,{expand:["latest_charge"]},onAccount):await client.charges.retrieve(paymentId,{},onAccount);return await client.refunds.create(connectRefundParams(source,refundOptions),{...onAccount,idempotencyKey:idempotencyKey??freshIdempotencyKey("connect.refund",paymentId,refundOptions.amount??"full")})}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/payments",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.268",
5
+ "version": "0.75.270",
6
6
  "description": "The Stacks payments package. Currently supporting Stripe.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -60,12 +60,12 @@
60
60
  "prepublishOnly": "bun run build"
61
61
  },
62
62
  "dependencies": {
63
- "@stacksjs/config": "0.75.268",
64
- "@stacksjs/database": "0.75.268",
65
- "@stacksjs/error-handling": "0.75.268",
66
- "@stacksjs/logging": "0.75.268",
67
- "@stacksjs/orm": "0.75.268",
68
- "@stacksjs/types": "0.75.268"
63
+ "@stacksjs/config": "0.75.270",
64
+ "@stacksjs/database": "0.75.270",
65
+ "@stacksjs/error-handling": "0.75.270",
66
+ "@stacksjs/logging": "0.75.270",
67
+ "@stacksjs/orm": "0.75.270",
68
+ "@stacksjs/types": "0.75.270"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@stripe/stripe-js": "^10.0.0",
@@ -80,7 +80,7 @@
80
80
  }
81
81
  },
82
82
  "devDependencies": {
83
- "@stacksjs/utils": "0.75.268",
83
+ "@stacksjs/utils": "0.75.270",
84
84
  "@stripe/stripe-js": "^10.0.0",
85
85
  "better-dx": "^0.2.24",
86
86
  "stripe": "23.0.0"