@stacksjs/payments 0.75.407 → 0.75.409
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/dist/connect/direct.d.ts +28 -7
- package/dist/connect/direct.js +1 -1
- package/package.json +8 -8
package/dist/connect/direct.d.ts
CHANGED
|
@@ -30,20 +30,37 @@ export declare function directSubscriptionParams(options?: Pick<DirectFeeOptions
|
|
|
30
30
|
/**
|
|
31
31
|
* The idempotency key for creating `user`'s customer on `accountId`: the same
|
|
32
32
|
* for every attempt with the same details, so two requests racing to create
|
|
33
|
-
* one make one, while a changed name or
|
|
34
|
-
* reused key Stripe would refuse
|
|
33
|
+
* one make one, while a changed name, email or install is a new request
|
|
34
|
+
* rather than a reused key Stripe would refuse or answer with another
|
|
35
|
+
* install's customer.
|
|
35
36
|
*/
|
|
36
37
|
export declare function connectedCustomerIdempotencyKey(accountId: string, userId: number | string, params?: Stripe.CustomerCreateParams): string;
|
|
38
|
+
/**
|
|
39
|
+
* This install's mark on the customers it creates on connected accounts
|
|
40
|
+
* (`metadata.stacks_install`): an HMAC of a fixed label under the app key,
|
|
41
|
+
* so it is stable for the life of the install, differs between installs that
|
|
42
|
+
* share a Connect account, and does not reveal the key. Without an app key it
|
|
43
|
+
* falls back to the app URL, then to one shared mark, which leaves the email
|
|
44
|
+
* as the only proof of ownership.
|
|
45
|
+
*/
|
|
46
|
+
export declare function connectInstallFingerprint(appKey?: string | null | undefined, appUrl?: string | null | undefined): string;
|
|
37
47
|
/**
|
|
38
48
|
* The user's customer on a connected account, created on first use. A
|
|
39
49
|
* customer belongs to one account, so the platform's own customer (the user's
|
|
40
50
|
* `stripe_id`) cannot pay a direct charge; each merchant the user buys from
|
|
41
|
-
* holds its own.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
51
|
+
* holds its own.
|
|
52
|
+
*
|
|
53
|
+
* A customer is reused only when it provably belongs to this buyer in this
|
|
54
|
+
* install: its `stacks_user_id` is the user's id, its email is the user's
|
|
55
|
+
* (case aside; both empty for a user without one), and its `stacks_install`
|
|
56
|
+
* is this install's mark. Local ids repeat across databases that share an
|
|
57
|
+
* account (CI runs, previews, restored backups, two installs on one Connect
|
|
58
|
+
* account), so an id alone would hand one person another's customer. A
|
|
59
|
+
* customer from before the mark existed is adopted on a matching email and
|
|
60
|
+
* then marked as this install's. Anything else gets a new customer, created
|
|
61
|
+
* with an idempotency key over the full parameters, mark included.
|
|
45
62
|
*/
|
|
46
|
-
export declare function connectedCustomer(user: ConnectPayer, accountId: string, client?: CustomerClient): Promise<Stripe.Customer>;
|
|
63
|
+
export declare function connectedCustomer(user: ConnectPayer, accountId: string, client?: CustomerClient, customerOptions?: ConnectedCustomerOptions): Promise<Stripe.Customer>;
|
|
47
64
|
/**
|
|
48
65
|
* A Checkout Session on the connected account, as `manageCheckout.create`
|
|
49
66
|
* makes one on the platform: the user's customer there unless `params` names
|
|
@@ -62,6 +79,10 @@ export declare function connectedCustomer(user: ConnectPayer, accountId: string,
|
|
|
62
79
|
export declare function directCheckout(user: ConnectPayer, accountId: string, params: Stripe.Checkout.SessionCreateParams, client?: Pick<Stripe, 'customers' | 'checkout'>, request?: { idempotencyKey?: string }): Promise<Stripe.Response<Stripe.Checkout.Session>>;
|
|
63
80
|
/** Subscriptions billed on the merchant's account, the platform paid its fee from each invoice. */
|
|
64
81
|
export declare const manageDirectSubscription: ManageDirectSubscription;
|
|
82
|
+
/** Options for {@link connectedCustomer}. */
|
|
83
|
+
export declare interface ConnectedCustomerOptions {
|
|
84
|
+
install?: string
|
|
85
|
+
}
|
|
65
86
|
export declare interface DirectSubscriptionCheckoutOptions {
|
|
66
87
|
idempotencyKey?: string
|
|
67
88
|
account: string
|
package/dist/connect/direct.js
CHANGED
|
@@ -1 +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),
|
|
1
|
+
import{createHmac}from"node:crypto";import{app}from"@stacksjs/config";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 function connectInstallFingerprint(appKey=app?.key,appUrl=app?.url){const secret=String(appKey||"").trim()||String(appUrl||"").trim()||"stacks";return createHmac("sha256",secret).update("stacks.payments.connect.install").digest("hex").slice(0,16)}function normalizedEmail(email){return String(email??"").trim().toLowerCase()}export async function connectedCustomer(user,accountId,client=stripe,customerOptions={}){requireAccountId(accountId,"connectedCustomer");const userId=String(user.id),email=normalizedEmail(user.email),install=customerOptions.install??connectInstallFingerprint(),options=asAccount(accountId),claim=(customer)=>{if("deleted"in customer&&customer.deleted)return 0;const found=customer;if(found.metadata?.stacks_user_id!==userId||normalizedEmail(found.email)!==email)return 0;const mark=found.metadata?.stacks_install;if(mark===install)return 2;return!mark&&email?1:0},best=(customers)=>{let pick,rank=0;for(const customer of customers){const score=claim(customer);if(score>rank){pick=customer;rank=score}}return pick},adopt=async(customer)=>{if(customer.metadata?.stacks_install===install)return customer;try{return await client.customers.update(customer.id,{metadata:{stacks_install:install}},options)}catch{return customer}};if(user.email){const byEmail=await client.customers.list({email:user.email,limit:100},options),found=best(byEmail.data);if(found)return await adopt(found)}try{const searched=await client.customers.search({query:`metadata['stacks_user_id']:'${userId.replace(/'/g,"")}'`,limit:100},options),found=best(searched.data);if(found)return await adopt(found)}catch{}const params={...user.email?{email:user.email}:{},...user.name?{name:user.name}:{},metadata:{stacks_user_id:userId,stacks_install:install}};return await client.customers.create(params,asAccount(accountId,{idempotencyKey:connectedCustomerIdempotencyKey(accountId,userId,params)}))}export async function directCheckout(user,accountId,params,client=stripe,request={}){requireAccountId(accountId,"directCheckout");if(request.idempotencyKey!==void 0&&(typeof request.idempotencyKey!=="string"||!/^[\x21-\x7e]{1,255}$/.test(request.idempotencyKey)))throw TypeError("A checkout idempotency key must contain 1-255 printable ASCII characters");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:request.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,{idempotencyKey:options.idempotencyKey})}return{subscriptionData,checkout}})();
|
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.
|
|
5
|
+
"version": "0.75.409",
|
|
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.
|
|
64
|
-
"@stacksjs/database": "0.75.
|
|
65
|
-
"@stacksjs/error-handling": "0.75.
|
|
66
|
-
"@stacksjs/logging": "0.75.
|
|
67
|
-
"@stacksjs/orm": "0.75.
|
|
68
|
-
"@stacksjs/types": "0.75.
|
|
63
|
+
"@stacksjs/config": "0.75.409",
|
|
64
|
+
"@stacksjs/database": "0.75.409",
|
|
65
|
+
"@stacksjs/error-handling": "0.75.409",
|
|
66
|
+
"@stacksjs/logging": "0.75.409",
|
|
67
|
+
"@stacksjs/orm": "0.75.409",
|
|
68
|
+
"@stacksjs/types": "0.75.409"
|
|
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.
|
|
83
|
+
"@stacksjs/utils": "0.75.409",
|
|
84
84
|
"@stripe/stripe-js": "^10.0.0",
|
|
85
85
|
"better-dx": "^0.2.24",
|
|
86
86
|
"stripe": "23.0.0"
|