@stacksjs/payments 0.70.258 → 0.70.259

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,64 +1 @@
1
- import { config } from "@stacksjs/config";
2
- import { log } from "@stacksjs/logging";
3
- import { stripe } from "..";
4
- import { freshIdempotencyKey } from "../idempotency";
5
- function defaultCurrency() {
6
- const cfg = config || {};
7
- return (cfg.payment?.currency || cfg.billing?.currency || process.env.STRIPE_CURRENCY || "usd").toLowerCase();
8
- }
9
- export const manageCharge = (() => {
10
- async function createPayment(user, amount, options) {
11
- const defaultOptions = {
12
- currency: defaultCurrency(),
13
- amount
14
- };
15
- if (user.hasStripeId())
16
- defaultOptions.customer = user.stripe_id ?? void 0;
17
- const mergedOptions = { ...defaultOptions, ...options };
18
- return await stripe.paymentIntents.create(mergedOptions, {
19
- idempotencyKey: freshIdempotencyKey("payment_intent.create", user.id, amount)
20
- });
21
- }
22
- async function findPayment(id) {
23
- try {
24
- return await stripe.paymentIntents.retrieve(id);
25
- } catch (error) {
26
- log.error(error);
27
- return null;
28
- }
29
- }
30
- async function refund(paymentIntentId, options = {}) {
31
- if (!paymentIntentId || typeof paymentIntentId !== "string")
32
- throw Error("[payments/refund] paymentIntentId is required");
33
- if (options.amount != null) {
34
- if (typeof options.amount !== "number" || !Number.isFinite(options.amount) || options.amount <= 0)
35
- throw Error("[payments/refund] amount must be a positive finite number");
36
- try {
37
- const original = await stripe.paymentIntents.retrieve(paymentIntentId), captured = original.amount_received ?? original.amount;
38
- if (captured && options.amount > captured)
39
- throw Error(`[payments/refund] amount ${options.amount} exceeds captured amount ${captured}`);
40
- } catch (err) {
41
- if (err instanceof Error && err.message.startsWith("[payments/refund]"))
42
- throw err;
43
- }
44
- }
45
- const { idempotencyKey, ...rest } = options, refundParams = {
46
- payment_intent: paymentIntentId,
47
- ...rest
48
- }, requestOptions = {
49
- idempotencyKey: idempotencyKey || `refund:${paymentIntentId}:${rest.amount ?? "full"}`
50
- };
51
- return await stripe.refunds.create(refundParams, requestOptions);
52
- }
53
- async function charge(user, amount, paymentMethod, options) {
54
- const mergedOptions = { ...{
55
- confirmation_method: "automatic",
56
- confirm: !0,
57
- payment_method: paymentMethod,
58
- currency: defaultCurrency(),
59
- amount
60
- }, ...options };
61
- return await createPayment(user, amount, mergedOptions);
62
- }
63
- return { createPayment, charge, findPayment, refund };
64
- })();
1
+ import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";function defaultCurrency(){const cfg=config||{};return(cfg.payment?.currency||cfg.billing?.currency||process.env.STRIPE_CURRENCY||"usd").toLowerCase()}export const manageCharge=(()=>{async function createPayment(user,amount,options){const defaultOptions={currency:defaultCurrency(),amount};if(user.hasStripeId())defaultOptions.customer=user.stripe_id??void 0;const mergedOptions={...defaultOptions,...options};return await stripe.paymentIntents.create(mergedOptions,{idempotencyKey:freshIdempotencyKey("payment_intent.create",user.id,amount)})}async function findPayment(id){try{return await stripe.paymentIntents.retrieve(id)}catch(error){log.error(error);return null}}async function refund(paymentIntentId,options={}){if(!paymentIntentId||typeof paymentIntentId!=="string")throw Error("[payments/refund] paymentIntentId is required");if(options.amount!=null){if(typeof options.amount!=="number"||!Number.isFinite(options.amount)||options.amount<=0)throw Error("[payments/refund] amount must be a positive finite number");try{const original=await stripe.paymentIntents.retrieve(paymentIntentId),captured=original.amount_received??original.amount;if(captured&&options.amount>captured)throw Error(`[payments/refund] amount ${options.amount} exceeds captured amount ${captured}`)}catch(err){if(err instanceof Error&&err.message.startsWith("[payments/refund]"))throw err}}const{idempotencyKey,...rest}=options,refundParams={payment_intent:paymentIntentId,...rest},requestOptions={idempotencyKey:idempotencyKey||`refund:${paymentIntentId}:${rest.amount??"full"}`};return await stripe.refunds.create(refundParams,requestOptions)}async function charge(user,amount,paymentMethod,options){const mergedOptions={...{confirmation_method:"automatic",confirm:!0,payment_method:paymentMethod,currency:defaultCurrency(),amount},...options};return await createPayment(user,amount,mergedOptions)}return{createPayment,charge,findPayment,refund}})();
@@ -1,43 +1 @@
1
- import { config } from "@stacksjs/config";
2
- import { stripe } from "..";
3
- import { freshIdempotencyKey } from "../idempotency";
4
- function assertSameOriginUrl(url, label) {
5
- if (!url)
6
- return;
7
- let parsed;
8
- try {
9
- parsed = new URL(url);
10
- } catch {
11
- throw Error(`[payments/checkout] ${label} is not a valid URL: ${url}`);
12
- }
13
- const appUrl = config?.app?.url;
14
- if (!appUrl)
15
- return;
16
- let appOrigin;
17
- try {
18
- appOrigin = new URL(appUrl.startsWith("http") ? appUrl : `https://${appUrl}`).origin;
19
- } catch {
20
- return;
21
- }
22
- if (parsed.origin !== appOrigin)
23
- throw Error(`[payments/checkout] ${label} origin (${parsed.origin}) does not match app origin (${appOrigin})`);
24
- }
25
- export const manageCheckout = (() => {
26
- async function create(user, params) {
27
- const customerId = params.customer || user.stripe_id;
28
- if (!customerId)
29
- throw Error("User has no Stripe customer");
30
- assertSameOriginUrl(params.success_url, "success_url");
31
- assertSameOriginUrl(params.cancel_url, "cancel_url");
32
- const mergedParams = { ...{
33
- customer: customerId,
34
- mode: "payment",
35
- success_url: params.success_url,
36
- cancel_url: params.cancel_url
37
- }, ...params };
38
- return await stripe.checkout.sessions.create(mergedParams, {
39
- idempotencyKey: freshIdempotencyKey("checkout.session.create", user.id)
40
- });
41
- }
42
- return { create };
43
- })();
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,117 +1 @@
1
- import { stripe } from "..";
2
- import { stacksIdempotencyKey } from "../idempotency";
3
- export const manageCustomer = (() => {
4
- function stripeId(user) {
5
- return user.stripe_id || "";
6
- }
7
- function stripeAddress(options) {
8
- return {
9
- line1: options.address?.line1,
10
- city: options.address?.city,
11
- state: options.address?.state,
12
- postal_code: options.address?.postal_code,
13
- country: options.address?.country
14
- };
15
- }
16
- function stripePreferredLocales() {
17
- return [];
18
- }
19
- function stripeMetadata(metadata) {
20
- return metadata;
21
- }
22
- function hasStripeId(user) {
23
- return Boolean(user.stripe_id);
24
- }
25
- async function createStripeCustomer(user, options = {}) {
26
- if (hasStripeId(user))
27
- throw Error("Customer already created");
28
- if (!options.name && stripeName(user))
29
- options.name = stripeName(user);
30
- if (!options.email && stripeEmail(user))
31
- options.email = stripeEmail(user);
32
- const customer = await stripe.customers.create(options, {
33
- idempotencyKey: stacksIdempotencyKey("customer.create", user.id)
34
- });
35
- await user.update({ stripe_id: customer.id });
36
- return customer;
37
- }
38
- async function updateStripeCustomer(user, options = {}) {
39
- if (!user.stripe_id)
40
- throw Error("User does not have a Stripe customer ID. Create a customer first.");
41
- return await stripe.customers.update(user.stripe_id, options, {
42
- idempotencyKey: stacksIdempotencyKey("customer.update", user.id, user.stripe_id)
43
- });
44
- }
45
- async function deleteStripeUser(user) {
46
- if (!hasStripeId(user))
47
- throw Error("User does not have a Stripe ID");
48
- try {
49
- if (!user.stripe_id)
50
- throw Error("User has no Stripe ID");
51
- const deletedCustomer = await stripe.customers.del(user.stripe_id);
52
- await user.update({ stripe_id: "" });
53
- return deletedCustomer;
54
- } catch (error) {
55
- if (error.statusCode === 404)
56
- throw Error("Customer not found in Stripe");
57
- throw error;
58
- }
59
- }
60
- async function createOrGetStripeUser(user, options = {}) {
61
- if (!hasStripeId(user))
62
- return await createStripeCustomer(user, options);
63
- try {
64
- const customer = await stripe.customers.retrieve(user.stripe_id);
65
- if (customer.deleted)
66
- throw Error("Customer was deleted");
67
- return customer;
68
- } catch (error) {
69
- if (error.statusCode === 404)
70
- return await createStripeCustomer(user, options);
71
- throw error;
72
- }
73
- }
74
- async function retrieveStripeUser(user) {
75
- if (!hasStripeId(user))
76
- return;
77
- try {
78
- const customer = await stripe.customers.retrieve(user.stripe_id);
79
- if (customer.deleted)
80
- throw Error("Customer was deleted in Stripe");
81
- return customer;
82
- } catch (error) {
83
- if (error.statusCode === 404)
84
- throw Error("Customer not found in Stripe");
85
- throw error;
86
- }
87
- }
88
- async function createOrUpdateStripeUser(user, options) {
89
- if (!hasStripeId(user))
90
- return await createStripeCustomer(user, options);
91
- try {
92
- if ((await stripe.customers.retrieve(user.stripe_id)).deleted)
93
- return await createStripeCustomer(user, options);
94
- return await updateStripeCustomer(user, options);
95
- } catch (error) {
96
- if (error.statusCode === 404)
97
- return await createStripeCustomer(user, options);
98
- throw error;
99
- }
100
- }
101
- async function syncStripeCustomerDetails(user, options) {
102
- return await updateStripeCustomer(user, {
103
- name: stripeName(user),
104
- email: stripeEmail(user),
105
- address: stripeAddress(options),
106
- preferred_locales: stripePreferredLocales(),
107
- metadata: options.metadata ? stripeMetadata(options.metadata) : {}
108
- });
109
- }
110
- function stripeName(user) {
111
- return user.name || "";
112
- }
113
- function stripeEmail(user) {
114
- return user.email || "";
115
- }
116
- return { stripeId, hasStripeId, createStripeCustomer, updateStripeCustomer, createOrGetStripeUser, createOrUpdateStripeUser, deleteStripeUser, retrieveStripeUser, syncStripeCustomerDetails };
117
- })();
1
+ import{stripe}from"../drivers/stripe";import{stacksIdempotencyKey}from"../idempotency";export const manageCustomer=(()=>{function stripeId(user){return user.stripe_id||""}function stripeAddress(options){return{line1:options.address?.line1,city:options.address?.city,state:options.address?.state,postal_code:options.address?.postal_code,country:options.address?.country}}function stripePreferredLocales(){return[]}function stripeMetadata(metadata){return metadata}function hasStripeId(user){return Boolean(user.stripe_id)}async function createStripeCustomer(user,options={}){if(hasStripeId(user))throw Error("Customer already created");if(!options.name&&stripeName(user))options.name=stripeName(user);if(!options.email&&stripeEmail(user))options.email=stripeEmail(user);const customer=await stripe.customers.create(options,{idempotencyKey:stacksIdempotencyKey("customer.create",user.id)});await user.update({stripe_id:customer.id});return customer}async function updateStripeCustomer(user,options={}){if(!user.stripe_id)throw Error("User does not have a Stripe customer ID. Create a customer first.");return await stripe.customers.update(user.stripe_id,options,{idempotencyKey:stacksIdempotencyKey("customer.update",user.id,user.stripe_id)})}async function deleteStripeUser(user){if(!hasStripeId(user))throw Error("User does not have a Stripe ID");try{if(!user.stripe_id)throw Error("User has no Stripe ID");const deletedCustomer=await stripe.customers.del(user.stripe_id);await user.update({stripe_id:""});return deletedCustomer}catch(error){if(error.statusCode===404)throw Error("Customer not found in Stripe");throw error}}async function createOrGetStripeUser(user,options={}){if(!hasStripeId(user))return await createStripeCustomer(user,options);try{const customer=await stripe.customers.retrieve(user.stripe_id);if(customer.deleted)throw Error("Customer was deleted");return customer}catch(error){if(error.statusCode===404)return await createStripeCustomer(user,options);throw error}}async function retrieveStripeUser(user){if(!hasStripeId(user))return;try{const customer=await stripe.customers.retrieve(user.stripe_id);if(customer.deleted)throw Error("Customer was deleted in Stripe");return customer}catch(error){if(error.statusCode===404)throw Error("Customer not found in Stripe");throw error}}async function createOrUpdateStripeUser(user,options){if(!hasStripeId(user))return await createStripeCustomer(user,options);try{if((await stripe.customers.retrieve(user.stripe_id)).deleted)return await createStripeCustomer(user,options);return await updateStripeCustomer(user,options)}catch(error){if(error.statusCode===404)return await createStripeCustomer(user,options);throw error}}async function syncStripeCustomerDetails(user,options){return await updateStripeCustomer(user,{name:stripeName(user),email:stripeEmail(user),address:stripeAddress(options),preferred_locales:stripePreferredLocales(),metadata:options.metadata?stripeMetadata(options.metadata):{}})}function stripeName(user){return user.name||""}function stripeEmail(user){return user.email||""}return{stripeId,hasStripeId,createStripeCustomer,updateStripeCustomer,createOrGetStripeUser,createOrUpdateStripeUser,deleteStripeUser,retrieveStripeUser,syncStripeCustomerDetails}})();
@@ -1,12 +1 @@
1
- export * from "./charge";
2
- export * from "./checkout";
3
- export * from "./customer";
4
- export * from "./intent";
5
- export * from "./invoice";
6
- export * from "./payment-method";
7
- export * from "./price";
8
- export * from "./product";
9
- export * from "./setup-products";
10
- export * from "./subscription";
11
- export * from "./transaction";
12
- export * from "./webhook";
1
+ export*from"./charge";export*from"./checkout";export*from"./customer";export*from"./intent";export*from"./invoice";export*from"./payment-method";export*from"./price";export*from"./product";export*from"./setup-products";export*from"./subscription";export*from"./transaction";export*from"./webhook";
@@ -1,18 +1 @@
1
- import { manageCustomer, stripe } from "..";
2
- import { freshIdempotencyKey } from "../idempotency";
3
- export const manageSetupIntent = (() => {
4
- async function create(user, params) {
5
- const mergedParams = { ...{
6
- customer: await manageCustomer.createOrGetStripeUser(user, {}).then((customer) => {
7
- if (!customer || !customer.id)
8
- throw Error("Customer does not exist in Stripe");
9
- return customer.id;
10
- }),
11
- payment_method_types: ["card"]
12
- }, ...params };
13
- return await stripe.setupIntents.create(mergedParams, {
14
- idempotencyKey: freshIdempotencyKey("setup_intent.create", user.id)
15
- });
16
- }
17
- return { create };
18
- })();
1
+ import{stripe}from"../drivers/stripe";import{manageCustomer}from"./customer";import{freshIdempotencyKey}from"../idempotency";export const manageSetupIntent=(()=>{async function create(user,params){const mergedParams={...{customer:await manageCustomer.createOrGetStripeUser(user,{}).then((customer)=>{if(!customer||!customer.id)throw Error("Customer does not exist in Stripe");return customer.id}),payment_method_types:["card"]},...params};return await stripe.setupIntents.create(mergedParams,{idempotencyKey:freshIdempotencyKey("setup_intent.create",user.id)})}return{create}})();
@@ -1,14 +1 @@
1
- import { stripe } from "..";
2
- export const manageInvoice = (() => {
3
- async function list(user) {
4
- if (!user.hasStripeId())
5
- throw Error("Customer does not exist in Stripe");
6
- if (!user.stripe_id)
7
- throw Error("User has no Stripe ID");
8
- return await stripe.invoices.list({
9
- customer: user.stripe_id,
10
- expand: ["data.payment_intent.payment_method"]
11
- });
12
- }
13
- return { list };
14
- })();
1
+ import{stripe}from"../drivers/stripe";export const manageInvoice=(()=>{async function list(user){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");if(!user.stripe_id)throw Error("User has no Stripe ID");return await stripe.invoices.list({customer:user.stripe_id,expand:["data.payment_intent.payment_method"]})}return{list}})();
@@ -1,119 +1 @@
1
- import { db } from "@stacksjs/database";
2
- import { PaymentMethod } from "@stacksjs/orm";
3
- import { stripe } from "..";
4
- export const managePaymentMethod = (() => {
5
- async function addPaymentMethod(user, paymentMethod) {
6
- if (!user.hasStripeId())
7
- throw Error("Customer does not exist in Stripe");
8
- let stripePaymentMethod;
9
- if (typeof paymentMethod === "string")
10
- stripePaymentMethod = await stripe.paymentMethods.retrieve(paymentMethod);
11
- else
12
- stripePaymentMethod = paymentMethod;
13
- if (stripePaymentMethod.customer !== user.stripe_id)
14
- stripePaymentMethod = await stripe.paymentMethods.attach(stripePaymentMethod.id, {
15
- customer: user.stripe_id
16
- });
17
- await storePaymentMethod(user, stripePaymentMethod);
18
- return stripePaymentMethod;
19
- }
20
- async function setUserDefaultPayment(user, paymentMethodId) {
21
- if (!user.hasStripeId())
22
- throw Error("Customer does not exist in Stripe");
23
- const paymentMethod = await stripe.paymentMethods.retrieve(paymentMethodId), paymentMethodModel = await db.selectFrom("payment_methods").where("provider_id", "=", paymentMethodId).selectAll().executeTakeFirst();
24
- if (!user.stripe_id)
25
- throw Error("User has no Stripe ID");
26
- if (paymentMethod.customer !== user.stripe_id)
27
- await stripe.paymentMethods.attach(paymentMethod.id, {
28
- customer: user.stripe_id
29
- });
30
- const updatedCustomer = await stripe.customers.update(user.stripe_id, {
31
- invoice_settings: {
32
- default_payment_method: paymentMethodId
33
- }
34
- });
35
- await db.updateTable("payment_methods").set({ is_default: !1 }).where("user_id", "=", user.id).executeTakeFirst();
36
- if (paymentMethodModel)
37
- await updateDefault(paymentMethodModel);
38
- return updatedCustomer;
39
- }
40
- async function setDefaultPaymentMethod(user, paymentId) {
41
- if (!user.hasStripeId())
42
- throw Error("Customer does not exist in Stripe");
43
- const pm = await db.selectFrom("payment_methods").where("id", "=", paymentId).selectAll().executeTakeFirst();
44
- if (!pm?.provider_id)
45
- throw Error(`Payment method with id ${paymentId} not found`);
46
- const paymentMethod = await stripe.paymentMethods.retrieve(String(pm.provider_id));
47
- if (paymentMethod.customer !== user.stripe_id)
48
- await stripe.paymentMethods.attach(paymentMethod.id, {
49
- customer: user.stripe_id
50
- });
51
- if (!user.stripe_id)
52
- throw Error("User has no Stripe ID");
53
- const updatedCustomer = await stripe.customers.update(user.stripe_id, {
54
- invoice_settings: {
55
- default_payment_method: String(pm.provider_id)
56
- }
57
- });
58
- await db.updateTable("payment_methods").set({ is_default: !1 }).where("user_id", "=", user.id).executeTakeFirst();
59
- await db.updateTable("payment_methods").set({ is_default: !0 }).where("id", "=", paymentId).executeTakeFirst();
60
- return updatedCustomer;
61
- }
62
- async function storePaymentMethod(user, paymentMethod) {
63
- if (!user.hasStripeId())
64
- throw Error("Customer does not exist in Stripe");
65
- if (!paymentMethod.card?.brand || !paymentMethod.card?.exp_year || !paymentMethod.card?.exp_month)
66
- throw Error("Invalid payment method: missing required card details");
67
- const method = {
68
- type: "card",
69
- last_four: String(paymentMethod.card.last4),
70
- brand: paymentMethod.card.brand,
71
- exp_year: paymentMethod.card.exp_year,
72
- exp_month: paymentMethod.card.exp_month,
73
- user_id: user.id,
74
- provider_id: paymentMethod.id
75
- };
76
- if (paymentMethod.customer !== user.stripe_id)
77
- await stripe.paymentMethods.attach(paymentMethod.id, {
78
- customer: user.stripe_id
79
- });
80
- return await PaymentMethod.create(method);
81
- }
82
- async function updateDefault(paymentMethodModel) {
83
- return await paymentMethodModel.update({ is_default: !0 });
84
- }
85
- async function deletePaymentMethod(user, paymentMethodId) {
86
- if (!user.hasStripeId())
87
- throw Error("Customer does not exist in Stripe");
88
- const pm = typeof paymentMethodId === "string" ? await db.selectFrom("payment_methods").where("provider_id", "=", paymentMethodId).selectAll().executeTakeFirst() : await PaymentMethod.find(paymentMethodId);
89
- if (!pm?.provider_id)
90
- throw Error(`Payment method with id ${paymentMethodId} not found`);
91
- if ((await stripe.paymentMethods.retrieve(String(pm.provider_id))).customer !== user.stripe_id)
92
- throw Error("Payment method does not belong to this customer");
93
- await pm.delete();
94
- return await stripe.paymentMethods.detach(String(pm.provider_id));
95
- }
96
- async function updatePaymentMethod(user, paymentMethodId, updateParams) {
97
- if (!user.hasStripeId())
98
- throw Error("Customer does not exist in Stripe");
99
- if ((await stripe.paymentMethods.retrieve(paymentMethodId)).customer !== user.stripe_id)
100
- throw Error("Payment method does not belong to this customer");
101
- return await stripe.paymentMethods.update(paymentMethodId, updateParams);
102
- }
103
- async function listPaymentMethods(user) {
104
- if (!user.hasStripeId())
105
- throw Error("Customer does not exist in Stripe");
106
- return await db.selectFrom("payment_methods").selectAll().where("user_id", "=", user.id).execute();
107
- }
108
- async function retrievePaymentMethod(user, paymentMethodId) {
109
- if (!user.hasStripeId())
110
- throw Error("Customer does not exist in Stripe");
111
- return await db.selectFrom("payment_methods").where("id", "=", paymentMethodId).selectAll().executeTakeFirst();
112
- }
113
- async function retrieveDefaultPaymentMethod(user) {
114
- if (!user.hasStripeId())
115
- throw Error("Customer does not exist in Stripe");
116
- return await PaymentMethod.where("user_id", user.id).where("is_default", !0).first();
117
- }
118
- return { addPaymentMethod, deletePaymentMethod, retrieveDefaultPaymentMethod, updatePaymentMethod, listPaymentMethods, setDefaultPaymentMethod, storePaymentMethod, retrievePaymentMethod, setUserDefaultPayment };
119
- })();
1
+ import{db}from"@stacksjs/database";import{PaymentMethod}from"@stacksjs/orm";import{stripe}from"../drivers/stripe";export const managePaymentMethod=(()=>{async function addPaymentMethod(user,paymentMethod){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");let stripePaymentMethod;if(typeof paymentMethod==="string")stripePaymentMethod=await stripe.paymentMethods.retrieve(paymentMethod);else stripePaymentMethod=paymentMethod;if(stripePaymentMethod.customer!==user.stripe_id)stripePaymentMethod=await stripe.paymentMethods.attach(stripePaymentMethod.id,{customer:user.stripe_id});await storePaymentMethod(user,stripePaymentMethod);return stripePaymentMethod}async function setUserDefaultPayment(user,paymentMethodId){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");const paymentMethod=await stripe.paymentMethods.retrieve(paymentMethodId),paymentMethodModel=await db.selectFrom("payment_methods").where("provider_id","=",paymentMethodId).selectAll().executeTakeFirst();if(!user.stripe_id)throw Error("User has no Stripe ID");if(paymentMethod.customer!==user.stripe_id)await stripe.paymentMethods.attach(paymentMethod.id,{customer:user.stripe_id});const updatedCustomer=await stripe.customers.update(user.stripe_id,{invoice_settings:{default_payment_method:paymentMethodId}});await db.updateTable("payment_methods").set({is_default:!1}).where("user_id","=",user.id).executeTakeFirst();if(paymentMethodModel)await updateDefault(paymentMethodModel);return updatedCustomer}async function setDefaultPaymentMethod(user,paymentId){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");const pm=await db.selectFrom("payment_methods").where("id","=",paymentId).selectAll().executeTakeFirst();if(!pm?.provider_id)throw Error(`Payment method with id ${paymentId} not found`);const paymentMethod=await stripe.paymentMethods.retrieve(String(pm.provider_id));if(paymentMethod.customer!==user.stripe_id)await stripe.paymentMethods.attach(paymentMethod.id,{customer:user.stripe_id});if(!user.stripe_id)throw Error("User has no Stripe ID");const updatedCustomer=await stripe.customers.update(user.stripe_id,{invoice_settings:{default_payment_method:String(pm.provider_id)}});await db.updateTable("payment_methods").set({is_default:!1}).where("user_id","=",user.id).executeTakeFirst();await db.updateTable("payment_methods").set({is_default:!0}).where("id","=",paymentId).executeTakeFirst();return updatedCustomer}async function storePaymentMethod(user,paymentMethod){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");if(!paymentMethod.card?.brand||!paymentMethod.card?.exp_year||!paymentMethod.card?.exp_month)throw Error("Invalid payment method: missing required card details");const method={type:"card",last_four:String(paymentMethod.card.last4),brand:paymentMethod.card.brand,exp_year:paymentMethod.card.exp_year,exp_month:paymentMethod.card.exp_month,user_id:user.id,provider_id:paymentMethod.id};if(paymentMethod.customer!==user.stripe_id)await stripe.paymentMethods.attach(paymentMethod.id,{customer:user.stripe_id});return await PaymentMethod.create(method)}async function updateDefault(paymentMethodModel){return await paymentMethodModel.update({is_default:!0})}async function deletePaymentMethod(user,paymentMethodId){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");const pm=typeof paymentMethodId==="string"?await db.selectFrom("payment_methods").where("provider_id","=",paymentMethodId).selectAll().executeTakeFirst():await PaymentMethod.find(paymentMethodId);if(!pm?.provider_id)throw Error(`Payment method with id ${paymentMethodId} not found`);if((await stripe.paymentMethods.retrieve(String(pm.provider_id))).customer!==user.stripe_id)throw Error("Payment method does not belong to this customer");await pm.delete();return await stripe.paymentMethods.detach(String(pm.provider_id))}async function updatePaymentMethod(user,paymentMethodId,updateParams){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");if((await stripe.paymentMethods.retrieve(paymentMethodId)).customer!==user.stripe_id)throw Error("Payment method does not belong to this customer");return await stripe.paymentMethods.update(paymentMethodId,updateParams)}async function listPaymentMethods(user){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");return await db.selectFrom("payment_methods").selectAll().where("user_id","=",user.id).execute()}async function retrievePaymentMethod(user,paymentMethodId){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");return await db.selectFrom("payment_methods").where("id","=",paymentMethodId).selectAll().executeTakeFirst()}async function retrieveDefaultPaymentMethod(user){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");return await PaymentMethod.where("user_id",user.id).where("is_default",!0).first()}return{addPaymentMethod,deletePaymentMethod,retrieveDefaultPaymentMethod,updatePaymentMethod,listPaymentMethods,setDefaultPaymentMethod,storePaymentMethod,retrievePaymentMethod,setUserDefaultPayment}})();
@@ -1,19 +1 @@
1
- import { stripe } from "..";
2
- export const managePrice = (() => {
3
- async function retrieveByLookupKey(lookupKey) {
4
- const prices = await stripe.prices.list({ lookup_keys: [lookupKey] });
5
- if (!prices.data.length)
6
- return;
7
- return prices.data[0];
8
- }
9
- async function createOrGet(lookupKey, params) {
10
- const existingPrice = await retrieveByLookupKey(lookupKey);
11
- if (existingPrice)
12
- return existingPrice;
13
- return await stripe.prices.create({
14
- ...params,
15
- lookup_key: lookupKey
16
- });
17
- }
18
- return { retrieveByLookupKey, createOrGet };
19
- })();
1
+ import{stripe}from"../drivers/stripe";export const managePrice=(()=>{async function retrieveByLookupKey(lookupKey){const prices=await stripe.prices.list({lookup_keys:[lookupKey]});if(!prices.data.length)return;return prices.data[0]}async function createOrGet(lookupKey,params){const existingPrice=await retrieveByLookupKey(lookupKey);if(existingPrice)return existingPrice;return await stripe.prices.create({...params,lookup_key:lookupKey})}return{retrieveByLookupKey,createOrGet}})();
@@ -1,101 +1 @@
1
- import { stripe } from "..";
2
- export const manageProduct = (() => {
3
- async function create(params) {
4
- return await stripe.products.create(params);
5
- }
6
- async function retrieve(productId) {
7
- return await stripe.products.retrieve(productId);
8
- }
9
- async function update(productId, params) {
10
- return await stripe.products.update(productId, params);
11
- }
12
- async function list(params = {}) {
13
- return await stripe.products.list({
14
- active: !0,
15
- ...params
16
- });
17
- }
18
- async function archive(productId) {
19
- return await stripe.products.update(productId, { active: !1 });
20
- }
21
- async function createWithPrice(productParams, priceParams) {
22
- const product = await stripe.products.create(productParams), price = await stripe.prices.create({
23
- ...priceParams,
24
- product: product.id
25
- });
26
- return { product, price };
27
- }
28
- async function search(query, params = { query: "" }) {
29
- return await stripe.products.search({
30
- ...params,
31
- query
32
- });
33
- }
34
- return { create, retrieve, update, list, archive, createWithPrice, search };
35
- })(), managePriceExtended = (() => {
36
- async function create(params) {
37
- return await stripe.prices.create(params);
38
- }
39
- async function retrieve(priceId) {
40
- return await stripe.prices.retrieve(priceId);
41
- }
42
- async function update(priceId, params) {
43
- return await stripe.prices.update(priceId, params);
44
- }
45
- async function list(params = {}) {
46
- return await stripe.prices.list({
47
- active: !0,
48
- ...params
49
- });
50
- }
51
- async function listByProduct(productId, params = {}) {
52
- return await stripe.prices.list({
53
- product: productId,
54
- active: !0,
55
- ...params
56
- });
57
- }
58
- async function search(query, params = { query: "" }) {
59
- return await stripe.prices.search({
60
- ...params,
61
- query
62
- });
63
- }
64
- async function archive(priceId) {
65
- return await stripe.prices.update(priceId, { active: !1 });
66
- }
67
- return { create, retrieve, update, list, listByProduct, search, archive };
68
- })(), manageCoupon = (() => {
69
- async function create(params) {
70
- return await stripe.coupons.create(params);
71
- }
72
- async function retrieve(couponId) {
73
- return await stripe.coupons.retrieve(couponId);
74
- }
75
- async function update(couponId, params) {
76
- return await stripe.coupons.update(couponId, params);
77
- }
78
- async function deleteCoupon(couponId) {
79
- return await stripe.coupons.del(couponId);
80
- }
81
- async function list(params = {}) {
82
- return await stripe.coupons.list(params);
83
- }
84
- async function createPromotionCode(params) {
85
- return await stripe.promotionCodes.create(params);
86
- }
87
- async function retrievePromotionCode(code) {
88
- try {
89
- const promotionCodes = await stripe.promotionCodes.list({
90
- code,
91
- active: !0
92
- });
93
- if (promotionCodes.data.length > 0)
94
- return promotionCodes.data[0] ?? null;
95
- return null;
96
- } catch {
97
- return null;
98
- }
99
- }
100
- return { create, retrieve, update, delete: deleteCoupon, list, createPromotionCode, retrievePromotionCode };
101
- })();
1
+ import{stripe}from"../drivers/stripe";export const manageProduct=(()=>{async function create(params){return await stripe.products.create(params)}async function retrieve(productId){return await stripe.products.retrieve(productId)}async function update(productId,params){return await stripe.products.update(productId,params)}async function list(params={}){return await stripe.products.list({active:!0,...params})}async function archive(productId){return await stripe.products.update(productId,{active:!1})}async function createWithPrice(productParams,priceParams){const product=await stripe.products.create(productParams),price=await stripe.prices.create({...priceParams,product:product.id});return{product,price}}async function search(query,params={query:""}){return await stripe.products.search({...params,query})}return{create,retrieve,update,list,archive,createWithPrice,search}})(),managePriceExtended=(()=>{async function create(params){return await stripe.prices.create(params)}async function retrieve(priceId){return await stripe.prices.retrieve(priceId)}async function update(priceId,params){return await stripe.prices.update(priceId,params)}async function list(params={}){return await stripe.prices.list({active:!0,...params})}async function listByProduct(productId,params={}){return await stripe.prices.list({product:productId,active:!0,...params})}async function search(query,params={query:""}){return await stripe.prices.search({...params,query})}async function archive(priceId){return await stripe.prices.update(priceId,{active:!1})}return{create,retrieve,update,list,listByProduct,search,archive}})(),manageCoupon=(()=>{async function create(params){return await stripe.coupons.create(params)}async function retrieve(couponId){return await stripe.coupons.retrieve(couponId)}async function update(couponId,params){return await stripe.coupons.update(couponId,params)}async function deleteCoupon(couponId){return await stripe.coupons.del(couponId)}async function list(params={}){return await stripe.coupons.list(params)}async function createPromotionCode(params){return await stripe.promotionCodes.create(params)}async function retrievePromotionCode(code){try{const promotionCodes=await stripe.promotionCodes.list({code,active:!0});if(promotionCodes.data.length>0)return promotionCodes.data[0]??null;return null}catch{return null}}return{create,retrieve,update,delete:deleteCoupon,list,createPromotionCode,retrievePromotionCode}})();
@@ -1,34 +1 @@
1
- import { saas } from "@stacksjs/config";
2
- import { err, ok } from "@stacksjs/error-handling";
3
- import { log } from "@stacksjs/logging";
4
- import { stripe } from "@stacksjs/payments";
5
- export async function createStripeProduct() {
6
- const plans = saas.plans;
7
- try {
8
- if (plans !== void 0 && plans.length)
9
- for (const plan of plans) {
10
- const product = await stripe.products.create({
11
- name: plan.productName,
12
- description: plan.description,
13
- metadata: plan.metadata
14
- });
15
- for (const pricing of plan.pricing)
16
- if (product) {
17
- const priceParams = {
18
- unit_amount: pricing.price,
19
- currency: pricing.currency,
20
- product: product.id,
21
- lookup_key: pricing.key
22
- };
23
- if (pricing.interval)
24
- priceParams.recurring = { interval: pricing.interval };
25
- await stripe.prices.create(priceParams);
26
- }
27
- }
28
- return ok("Migrations generated");
29
- } catch (error) {
30
- const e = error instanceof Error ? error : Error(String(error));
31
- log.error(e);
32
- return err(e);
33
- }
34
- }
1
+ import{saas}from"@stacksjs/config";import{err,ok}from"@stacksjs/error-handling";import{log}from"@stacksjs/logging";import{stripe}from"@stacksjs/payments";export async function createStripeProduct(){const plans=saas.plans;try{if(plans!==void 0&&plans.length)for(const plan of plans){const product=await stripe.products.create({name:plan.productName,description:plan.description,metadata:plan.metadata});for(const pricing of plan.pricing)if(product){const priceParams={unit_amount:pricing.price,currency:pricing.currency,product:product.id,lookup_key:pricing.key};if(pricing.interval)priceParams.recurring={interval:pricing.interval};await stripe.prices.create(priceParams)}}return ok("Migrations generated")}catch(error){const e=error instanceof Error?error:Error(String(error));log.error(e);return err(e)}}
@@ -1,138 +1 @@
1
- import { db } from "@stacksjs/database";
2
- import { HttpError } from "@stacksjs/error-handling";
3
- import { isUniqueViolation } from "@stacksjs/orm";
4
- import { manageCustomer, managePrice, stripe } from "..";
5
- import { stacksIdempotencyKey } from "../idempotency";
6
- export const manageSubscription = (() => {
7
- async function create(user, type, lookupKey, params) {
8
- const price = await managePrice.retrieveByLookupKey(lookupKey);
9
- if (!price)
10
- throw Error("Price does not exist in Stripe");
11
- const subscriptionItems = [
12
- {
13
- price: price.id,
14
- quantity: 1
15
- }
16
- ], mergedParams = { ...{
17
- customer: await manageCustomer.createOrGetStripeUser(user, {}).then((customer) => {
18
- if (!customer || !customer.id)
19
- throw Error("Customer does not exist in Stripe");
20
- return customer.id;
21
- }),
22
- payment_behavior: "allow_incomplete",
23
- expand: ["latest_invoice.payment_intent"],
24
- items: subscriptionItems
25
- }, ...params }, subscription = await stripe.subscriptions.create(mergedParams, {
26
- idempotencyKey: stacksIdempotencyKey("subscription.create", user.id, type, lookupKey)
27
- });
28
- await storeSubscription(user, type, lookupKey, subscription);
29
- return subscription;
30
- }
31
- async function update(user, type, lookupKey, _params = {}) {
32
- const newPrice = await managePrice.retrieveByLookupKey(lookupKey), activeSubscription = await user?.activeSubscription();
33
- if (!newPrice)
34
- throw Error("New price does not exist in Stripe");
35
- if (!activeSubscription)
36
- throw Error("No active subscription for user!");
37
- const subscriptionId = activeSubscription.subscription?.provider_id;
38
- if (!subscriptionId)
39
- throw Error("Active subscription has no provider ID");
40
- const subscription = await stripe.subscriptions.retrieve(subscriptionId);
41
- if (!subscription)
42
- throw Error("Subscription does not exist in Stripe");
43
- const subscriptionItemId = subscription.items.data[0]?.id;
44
- if (!subscriptionItemId)
45
- throw Error("No subscription items found in the subscription");
46
- await stripe.subscriptions.update(subscriptionId, {
47
- items: [{ id: subscriptionItemId, price: newPrice.id, quantity: 1 }],
48
- proration_behavior: "create_prorations"
49
- }, {
50
- idempotencyKey: stacksIdempotencyKey("subscription.update", subscriptionId, newPrice.id)
51
- });
52
- const updatedSubscription = await stripe.subscriptions.retrieve(subscriptionId);
53
- if (!activeSubscription.subscription?.id)
54
- throw Error("Active subscription has no database ID");
55
- await updateSubscription(activeSubscription.subscription.id, type, updatedSubscription);
56
- return updatedSubscription;
57
- }
58
- async function cancel(subscriptionId, params) {
59
- if (!await stripe.subscriptions.retrieve(subscriptionId))
60
- throw Error("Subscription does not exist or does not belong to the user");
61
- const updatedSubscription = await stripe.subscriptions.cancel(subscriptionId, params, {
62
- idempotencyKey: stacksIdempotencyKey("subscription.cancel", subscriptionId)
63
- });
64
- await updateStoredSubscription(subscriptionId);
65
- return updatedSubscription;
66
- }
67
- async function retrieve(user, subscriptionId) {
68
- if (!user.hasStripeId())
69
- throw Error("Customer does not exist in Stripe");
70
- return await stripe.subscriptions.retrieve(subscriptionId);
71
- }
72
- async function updateStoredSubscription(subscriptionId) {
73
- await db.updateTable("subscriptions").set({ provider_status: "canceled" }).where("provider_id", "=", subscriptionId).executeTakeFirst();
74
- }
75
- function isActive(subscription) {
76
- return subscription.provider_status === "active";
77
- }
78
- function isTrial(subscription) {
79
- return subscription.provider_status === "trialing";
80
- }
81
- async function isIncomplete(user, type) {
82
- const subscription = await db.selectFrom("subscriptions").where("user_id", "=", user.id).where("type", "=", type).selectAll().executeTakeFirst();
83
- if (!subscription)
84
- return !1;
85
- return subscription.provider_status === "incomplete";
86
- }
87
- async function isValid(user, type) {
88
- const subscription = await db.selectFrom("subscriptions").where("user_id", "=", user.id).where("type", "=", type).selectAll().executeTakeFirst();
89
- if (!subscription)
90
- return !1;
91
- const active = await isActive(subscription), trial = await isTrial(subscription);
92
- return active || trial;
93
- }
94
- async function storeSubscription(user, type, _lookupKey, options) {
95
- const firstItem = options.items.data[0];
96
- if (!firstItem)
97
- throw Error("Stripe subscription contains no line items \u2014 cannot store subscription");
98
- const data = removeNullValues({
99
- user_id: user.id,
100
- type,
101
- unit_price: Number(firstItem.price.unit_amount),
102
- provider_id: options.id,
103
- provider_status: options.status,
104
- provider_price_id: firstItem.price.id,
105
- quantity: firstItem.quantity,
106
- trial_ends_at: options.trial_end != null ? String(options.trial_end) : void 0,
107
- ends_at: options.current_period_end != null ? String(options.current_period_end) : void 0,
108
- provider_type: "stripe",
109
- last_used_at: options.current_period_end != null ? String(options.current_period_end) : void 0
110
- });
111
- let subscriptionModelCreated;
112
- try {
113
- subscriptionModelCreated = await db.insertInto("subscriptions").values(data).executeTakeFirst();
114
- } catch (error) {
115
- if (isUniqueViolation(error))
116
- throw new HttpError(409, "A subscription with this provider ID already exists");
117
- throw error;
118
- }
119
- if (!subscriptionModelCreated)
120
- throw Error("Failed to insert subscription record");
121
- return await db.selectFrom("subscriptions").where("id", "=", Number(subscriptionModelCreated.insertId)).selectAll().executeTakeFirst();
122
- }
123
- async function updateSubscription(activeSubId, type, options) {
124
- const subscription = await db.selectFrom("subscriptions").where("id", "=", activeSubId).selectAll().executeTakeFirst(), firstItem = options.items.data[0];
125
- if (!firstItem)
126
- throw Error("Stripe subscription contains no line items \u2014 cannot update subscription");
127
- await db?.updateTable("subscriptions").set({
128
- type,
129
- provider_price_id: firstItem.price.id,
130
- unit_price: Number(firstItem.price.unit_amount)
131
- }).where("id", "=", activeSubId).executeTakeFirst();
132
- return subscription;
133
- }
134
- function removeNullValues(obj) {
135
- return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value != null));
136
- }
137
- return { create, update, isValid, isIncomplete, cancel, retrieve };
138
- })();
1
+ import{db}from"@stacksjs/database";import{HttpError}from"@stacksjs/error-handling";import{isUniqueViolation}from"@stacksjs/orm";import{stripe}from"../drivers/stripe";import{manageCustomer}from"./customer";import{managePrice}from"./price";import{stacksIdempotencyKey}from"../idempotency";export const manageSubscription=(()=>{async function create(user,type,lookupKey,params){const price=await managePrice.retrieveByLookupKey(lookupKey);if(!price)throw Error("Price does not exist in Stripe");const subscriptionItems=[{price:price.id,quantity:1}],mergedParams={...{customer:await manageCustomer.createOrGetStripeUser(user,{}).then((customer)=>{if(!customer||!customer.id)throw Error("Customer does not exist in Stripe");return customer.id}),payment_behavior:"allow_incomplete",expand:["latest_invoice.payment_intent"],items:subscriptionItems},...params},subscription=await stripe.subscriptions.create(mergedParams,{idempotencyKey:stacksIdempotencyKey("subscription.create",user.id,type,lookupKey)});await storeSubscription(user,type,lookupKey,subscription);return subscription}async function update(user,type,lookupKey,_params={}){const newPrice=await managePrice.retrieveByLookupKey(lookupKey),activeSubscription=await user?.activeSubscription();if(!newPrice)throw Error("New price does not exist in Stripe");if(!activeSubscription)throw Error("No active subscription for user!");const subscriptionId=activeSubscription.subscription?.provider_id;if(!subscriptionId)throw Error("Active subscription has no provider ID");const subscription=await stripe.subscriptions.retrieve(subscriptionId);if(!subscription)throw Error("Subscription does not exist in Stripe");const subscriptionItemId=subscription.items.data[0]?.id;if(!subscriptionItemId)throw Error("No subscription items found in the subscription");await stripe.subscriptions.update(subscriptionId,{items:[{id:subscriptionItemId,price:newPrice.id,quantity:1}],proration_behavior:"create_prorations"},{idempotencyKey:stacksIdempotencyKey("subscription.update",subscriptionId,newPrice.id)});const updatedSubscription=await stripe.subscriptions.retrieve(subscriptionId);if(!activeSubscription.subscription?.id)throw Error("Active subscription has no database ID");await updateSubscription(activeSubscription.subscription.id,type,updatedSubscription);return updatedSubscription}async function cancel(subscriptionId,params){if(!await stripe.subscriptions.retrieve(subscriptionId))throw Error("Subscription does not exist or does not belong to the user");const updatedSubscription=await stripe.subscriptions.cancel(subscriptionId,params,{idempotencyKey:stacksIdempotencyKey("subscription.cancel",subscriptionId)});await updateStoredSubscription(subscriptionId);return updatedSubscription}async function retrieve(user,subscriptionId){if(!user.hasStripeId())throw Error("Customer does not exist in Stripe");return await stripe.subscriptions.retrieve(subscriptionId)}async function updateStoredSubscription(subscriptionId){await db.updateTable("subscriptions").set({provider_status:"canceled"}).where("provider_id","=",subscriptionId).executeTakeFirst()}function isActive(subscription){return subscription.provider_status==="active"}function isTrial(subscription){return subscription.provider_status==="trialing"}async function isIncomplete(user,type){const subscription=await db.selectFrom("subscriptions").where("user_id","=",user.id).where("type","=",type).selectAll().executeTakeFirst();if(!subscription)return!1;return subscription.provider_status==="incomplete"}async function isValid(user,type){const subscription=await db.selectFrom("subscriptions").where("user_id","=",user.id).where("type","=",type).selectAll().executeTakeFirst();if(!subscription)return!1;const active=await isActive(subscription),trial=await isTrial(subscription);return active||trial}async function storeSubscription(user,type,_lookupKey,options){const firstItem=options.items.data[0];if(!firstItem)throw Error("Stripe subscription contains no line items \u2014 cannot store subscription");const data=removeNullValues({user_id:user.id,type,unit_price:Number(firstItem.price.unit_amount),provider_id:options.id,provider_status:options.status,provider_price_id:firstItem.price.id,quantity:firstItem.quantity,trial_ends_at:options.trial_end!=null?String(options.trial_end):void 0,ends_at:options.current_period_end!=null?String(options.current_period_end):void 0,provider_type:"stripe",last_used_at:options.current_period_end!=null?String(options.current_period_end):void 0});let subscriptionModelCreated;try{subscriptionModelCreated=await db.insertInto("subscriptions").values(data).executeTakeFirst()}catch(error){if(isUniqueViolation(error))throw new HttpError(409,"A subscription with this provider ID already exists");throw error}if(!subscriptionModelCreated)throw Error("Failed to insert subscription record");return await db.selectFrom("subscriptions").where("id","=",Number(subscriptionModelCreated.insertId)).selectAll().executeTakeFirst()}async function updateSubscription(activeSubId,type,options){const subscription=await db.selectFrom("subscriptions").where("id","=",activeSubId).selectAll().executeTakeFirst(),firstItem=options.items.data[0];if(!firstItem)throw Error("Stripe subscription contains no line items \u2014 cannot update subscription");await db?.updateTable("subscriptions").set({type,provider_price_id:firstItem.price.id,unit_price:Number(firstItem.price.unit_amount)}).where("id","=",activeSubId).executeTakeFirst();return subscription}function removeNullValues(obj){return Object.fromEntries(Object.entries(obj).filter(([_,value])=>value!=null))}return{create,update,isValid,isIncomplete,cancel,retrieve}})();
@@ -1,25 +1 @@
1
- import { db } from "@stacksjs/database";
2
- import { PaymentTransaction } from "@stacksjs/orm";
3
- export const manageTransaction = (() => {
4
- async function store(user, productId, options) {
5
- const product = await db.selectFrom("payment_products").where("id", "=", productId).selectAll().executeTakeFirst();
6
- if (!product)
7
- throw Error(`Payment product with id ${productId} not found.`);
8
- const data = {
9
- name: product.name,
10
- description: options.description ?? "",
11
- amount: product.unit_price,
12
- brand: options.brand,
13
- type: options.type ?? "one-time",
14
- provider_id: options.provider_id,
15
- user_id: user.id
16
- }, createdTransaction = await db.insertInto("payment_transactions").values(data).executeTakeFirst();
17
- if (!createdTransaction)
18
- throw Error("Failed to insert payment transaction");
19
- return await db.selectFrom("payment_transactions").where("id", "=", Number(createdTransaction.insertId)).selectAll().executeTakeFirst();
20
- }
21
- async function list(user) {
22
- return await db.selectFrom("payment_transactions").where("user_id", "=", user.id).selectAll().execute();
23
- }
24
- return { store, list };
25
- })();
1
+ import{db}from"@stacksjs/database";import{PaymentTransaction}from"@stacksjs/orm";export const manageTransaction=(()=>{async function store(user,productId,options){const product=await db.selectFrom("payment_products").where("id","=",productId).selectAll().executeTakeFirst();if(!product)throw Error(`Payment product with id ${productId} not found.`);const data={name:product.name,description:options.description??"",amount:product.unit_price,brand:options.brand,type:options.type??"one-time",provider_id:options.provider_id,user_id:user.id},createdTransaction=await db.insertInto("payment_transactions").values(data).executeTakeFirst();if(!createdTransaction)throw Error("Failed to insert payment transaction");return await db.selectFrom("payment_transactions").where("id","=",Number(createdTransaction.insertId)).selectAll().executeTakeFirst()}async function list(user){return await db.selectFrom("payment_transactions").where("user_id","=",user.id).selectAll().execute()}return{store,list}})();
@@ -1,153 +1 @@
1
- import { stripe } from "..";
2
- const handlers = new Map;
3
- export function onWebhookEvent(eventType, handler) {
4
- const existing = handlers.get(eventType) || [];
5
- existing.push(handler);
6
- handlers.set(eventType, existing);
7
- }
8
- export function registerWebhookHandlers(handlerMap) {
9
- for (const [eventType, handler] of Object.entries(handlerMap))
10
- onWebhookEvent(eventType, handler);
11
- }
12
- export function constructEvent(payload, signature, secret, tolerance) {
13
- if (tolerance != null && Number.isFinite(tolerance) && tolerance > 0)
14
- return stripe.webhooks.constructEvent(payload, signature, secret, tolerance);
15
- return stripe.webhooks.constructEvent(payload, signature, secret);
16
- }
17
- export async function constructEventAsync(payload, signature, secret, tolerance) {
18
- if (tolerance != null && Number.isFinite(tolerance) && tolerance > 0)
19
- return await stripe.webhooks.constructEventAsync(payload, signature, secret, tolerance);
20
- return await stripe.webhooks.constructEventAsync(payload, signature, secret);
21
- }
22
- export async function handleWebhookEvent(event) {
23
- const eventHandlers = handlers.get(event.type) || [];
24
- if (eventHandlers.length === 0)
25
- return { handled: !1, eventType: event.type };
26
- const errors = [];
27
- for (const handler of eventHandlers)
28
- try {
29
- await handler(event);
30
- } catch (error) {
31
- errors.push(error instanceof Error ? error.message : String(error));
32
- }
33
- return {
34
- handled: !0,
35
- eventType: event.type,
36
- ...errors.length > 0 ? { errors } : {}
37
- };
38
- }
39
- export async function processWebhook(payload, signature, config) {
40
- try {
41
- const event = await constructEventAsync(payload, signature, config.secret, config.tolerance), result = await handleWebhookEvent(event);
42
- if (result.errors && result.errors.length > 0)
43
- return {
44
- success: !1,
45
- eventType: result.eventType,
46
- error: result.errors.join("; ")
47
- };
48
- return {
49
- success: !0,
50
- eventType: result.eventType
51
- };
52
- } catch (error) {
53
- return {
54
- success: !1,
55
- error: error instanceof Error ? error.message : "Unknown error"
56
- };
57
- }
58
- }
59
- export function onPaymentIntent(handlers) {
60
- if (handlers.succeeded)
61
- onWebhookEvent("payment_intent.succeeded", handlers.succeeded);
62
- if (handlers.failed)
63
- onWebhookEvent("payment_intent.payment_failed", handlers.failed);
64
- if (handlers.created)
65
- onWebhookEvent("payment_intent.created", handlers.created);
66
- if (handlers.canceled)
67
- onWebhookEvent("payment_intent.canceled", handlers.canceled);
68
- }
69
- export function onSubscription(handlers) {
70
- if (handlers.created)
71
- onWebhookEvent("customer.subscription.created", handlers.created);
72
- if (handlers.updated)
73
- onWebhookEvent("customer.subscription.updated", handlers.updated);
74
- if (handlers.deleted)
75
- onWebhookEvent("customer.subscription.deleted", handlers.deleted);
76
- if (handlers.trialWillEnd)
77
- onWebhookEvent("customer.subscription.trial_will_end", handlers.trialWillEnd);
78
- }
79
- export function onInvoice(handlers) {
80
- if (handlers.paid)
81
- onWebhookEvent("invoice.paid", handlers.paid);
82
- if (handlers.paymentFailed)
83
- onWebhookEvent("invoice.payment_failed", handlers.paymentFailed);
84
- if (handlers.created)
85
- onWebhookEvent("invoice.created", handlers.created);
86
- if (handlers.finalized)
87
- onWebhookEvent("invoice.finalized", handlers.finalized);
88
- }
89
- export function onCheckout(handlers) {
90
- if (handlers.completed)
91
- onWebhookEvent("checkout.session.completed", handlers.completed);
92
- if (handlers.expired)
93
- onWebhookEvent("checkout.session.expired", handlers.expired);
94
- }
95
- export function onCharge(handlers) {
96
- if (handlers.succeeded)
97
- onWebhookEvent("charge.succeeded", handlers.succeeded);
98
- if (handlers.failed)
99
- onWebhookEvent("charge.failed", handlers.failed);
100
- if (handlers.refunded)
101
- onWebhookEvent("charge.refunded", handlers.refunded);
102
- if (handlers.disputed)
103
- onWebhookEvent("charge.dispute.created", handlers.disputed);
104
- }
105
- export function getPaymentIntent(event) {
106
- if (event.type.startsWith("payment_intent."))
107
- return event.data.object;
108
- return null;
109
- }
110
- export function getSubscription(event) {
111
- if (event.type.startsWith("customer.subscription."))
112
- return event.data.object;
113
- return null;
114
- }
115
- export function getInvoice(event) {
116
- if (event.type.startsWith("invoice."))
117
- return event.data.object;
118
- return null;
119
- }
120
- export function getCheckoutSession(event) {
121
- if (event.type.startsWith("checkout.session."))
122
- return event.data.object;
123
- return null;
124
- }
125
- export function getCharge(event) {
126
- if (event.type.startsWith("charge."))
127
- return event.data.object;
128
- return null;
129
- }
130
- export function getCustomer(event) {
131
- if (event.type.startsWith("customer.") && !event.type.includes("subscription"))
132
- return event.data.object;
133
- return null;
134
- }
135
- export const manageWebhook = {
136
- onWebhookEvent,
137
- registerWebhookHandlers,
138
- constructEvent,
139
- constructEventAsync,
140
- handleWebhookEvent,
141
- processWebhook,
142
- onPaymentIntent,
143
- onSubscription,
144
- onInvoice,
145
- onCheckout,
146
- onCharge,
147
- getPaymentIntent,
148
- getSubscription,
149
- getInvoice,
150
- getCheckoutSession,
151
- getCharge,
152
- getCustomer
153
- };
1
+ import{stripe}from"../drivers/stripe";const handlers=new Map;export function onWebhookEvent(eventType,handler){const existing=handlers.get(eventType)||[];existing.push(handler);handlers.set(eventType,existing)}export function registerWebhookHandlers(handlerMap){for(const[eventType,handler]of Object.entries(handlerMap))onWebhookEvent(eventType,handler)}export function constructEvent(payload,signature,secret,tolerance){if(tolerance!=null&&Number.isFinite(tolerance)&&tolerance>0)return stripe.webhooks.constructEvent(payload,signature,secret,tolerance);return stripe.webhooks.constructEvent(payload,signature,secret)}export async function constructEventAsync(payload,signature,secret,tolerance){if(tolerance!=null&&Number.isFinite(tolerance)&&tolerance>0)return await stripe.webhooks.constructEventAsync(payload,signature,secret,tolerance);return await stripe.webhooks.constructEventAsync(payload,signature,secret)}export async function handleWebhookEvent(event){const eventHandlers=handlers.get(event.type)||[];if(eventHandlers.length===0)return{handled:!1,eventType:event.type};const errors=[];for(const handler of eventHandlers)try{await handler(event)}catch(error){errors.push(error instanceof Error?error.message:String(error))}return{handled:!0,eventType:event.type,...errors.length>0?{errors}:{}}}export async function processWebhook(payload,signature,config){try{const event=await constructEventAsync(payload,signature,config.secret,config.tolerance),result=await handleWebhookEvent(event);if(result.errors&&result.errors.length>0)return{success:!1,eventType:result.eventType,error:result.errors.join("; ")};return{success:!0,eventType:result.eventType}}catch(error){return{success:!1,error:error instanceof Error?error.message:"Unknown error"}}}export function onPaymentIntent(handlers){if(handlers.succeeded)onWebhookEvent("payment_intent.succeeded",handlers.succeeded);if(handlers.failed)onWebhookEvent("payment_intent.payment_failed",handlers.failed);if(handlers.created)onWebhookEvent("payment_intent.created",handlers.created);if(handlers.canceled)onWebhookEvent("payment_intent.canceled",handlers.canceled)}export function onSubscription(handlers){if(handlers.created)onWebhookEvent("customer.subscription.created",handlers.created);if(handlers.updated)onWebhookEvent("customer.subscription.updated",handlers.updated);if(handlers.deleted)onWebhookEvent("customer.subscription.deleted",handlers.deleted);if(handlers.trialWillEnd)onWebhookEvent("customer.subscription.trial_will_end",handlers.trialWillEnd)}export function onInvoice(handlers){if(handlers.paid)onWebhookEvent("invoice.paid",handlers.paid);if(handlers.paymentFailed)onWebhookEvent("invoice.payment_failed",handlers.paymentFailed);if(handlers.created)onWebhookEvent("invoice.created",handlers.created);if(handlers.finalized)onWebhookEvent("invoice.finalized",handlers.finalized)}export function onCheckout(handlers){if(handlers.completed)onWebhookEvent("checkout.session.completed",handlers.completed);if(handlers.expired)onWebhookEvent("checkout.session.expired",handlers.expired)}export function onCharge(handlers){if(handlers.succeeded)onWebhookEvent("charge.succeeded",handlers.succeeded);if(handlers.failed)onWebhookEvent("charge.failed",handlers.failed);if(handlers.refunded)onWebhookEvent("charge.refunded",handlers.refunded);if(handlers.disputed)onWebhookEvent("charge.dispute.created",handlers.disputed)}export function getPaymentIntent(event){if(event.type.startsWith("payment_intent."))return event.data.object;return null}export function getSubscription(event){if(event.type.startsWith("customer.subscription."))return event.data.object;return null}export function getInvoice(event){if(event.type.startsWith("invoice."))return event.data.object;return null}export function getCheckoutSession(event){if(event.type.startsWith("checkout.session."))return event.data.object;return null}export function getCharge(event){if(event.type.startsWith("charge."))return event.data.object;return null}export function getCustomer(event){if(event.type.startsWith("customer.")&&!event.type.includes("subscription"))return event.data.object;return null}export const manageWebhook={onWebhookEvent,registerWebhookHandlers,constructEvent,constructEventAsync,handleWebhookEvent,processWebhook,onPaymentIntent,onSubscription,onInvoice,onCheckout,onCharge,getPaymentIntent,getSubscription,getInvoice,getCheckoutSession,getCharge,getCustomer};
@@ -1,25 +1 @@
1
- import { createRequire } from "node:module";
2
- import { services } from "@stacksjs/config";
3
- const require = createRequire(import.meta.url);
4
- function StripeCtor() {
5
- try {
6
- return require("stripe");
7
- } catch {
8
- throw Error("Stripe payments are being used but the `stripe` package is not installed. " + "It is an opt-in dependency \u2014 run `bun add stripe` to enable server-side Stripe payments.");
9
- }
10
- }
11
- let _stripe = null;
12
- export const stripe = new Proxy({}, {
13
- get(_target, prop) {
14
- if (!_stripe) {
15
- const apiKey = services?.stripe?.secretKey;
16
- if (!apiKey)
17
- throw Error("Stripe secret key is not configured. Set STRIPE_SECRET_KEY in your .env file.");
18
- const apiVersion = "2026-06-24.dahlia", configuredVersion = services?.stripe?.apiVersion;
19
- if (configuredVersion && configuredVersion !== apiVersion)
20
- throw Error(`Stripe API version ${configuredVersion} does not match the installed SDK version ${apiVersion}`);
21
- _stripe = new (StripeCtor())(apiKey, { apiVersion });
22
- }
23
- return _stripe[prop];
24
- }
25
- });
1
+ import{createRequire}from"node:module";import{services}from"@stacksjs/config";const require=createRequire(import.meta.url);function StripeCtor(){try{return require("stripe")}catch{throw Error("Stripe payments are being used but the `stripe` package is not installed. "+"It is an opt-in dependency \u2014 run `bun add stripe` to enable server-side Stripe payments.")}}let _stripe=null;export const stripe=new Proxy({},{get(_target,prop){if(!_stripe){const apiKey=services?.stripe?.secretKey;if(!apiKey)throw Error("Stripe secret key is not configured. Set STRIPE_SECRET_KEY in your .env file.");const apiVersion="2026-06-24.dahlia",configuredVersion=services?.stripe?.apiVersion;if(configuredVersion&&configuredVersion!==apiVersion)throw Error(`Stripe API version ${configuredVersion} does not match the installed SDK version ${apiVersion}`);_stripe=new(StripeCtor())(apiKey,{apiVersion})}return _stripe[prop]}});
@@ -1,13 +1 @@
1
- import { createHash } from "node:crypto";
2
- const KEY_PREFIX = "stacks", KEY_VERSION = "v1", MAX_KEY_LENGTH = 255;
3
- export function stacksIdempotencyKey(scope, ...parts) {
4
- const tail = parts.filter((p) => p !== null && p !== void 0 && p !== "").map((p) => String(p)).join(":"), raw = tail ? `${KEY_PREFIX}:${scope}:${tail}:${KEY_VERSION}` : `${KEY_PREFIX}:${scope}:${KEY_VERSION}`;
5
- if (raw.length <= MAX_KEY_LENGTH)
6
- return raw;
7
- const hashed = createHash("sha256").update(tail).digest("hex").slice(0, 32);
8
- return `${KEY_PREFIX}:${scope}:${hashed}:${KEY_VERSION}`;
9
- }
10
- export function freshIdempotencyKey(scope, ...parts) {
11
- const random = createHash("sha256").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0, 16);
12
- return stacksIdempotencyKey(scope, ...parts, random);
13
- }
1
+ import{createHash}from"node:crypto";const KEY_PREFIX="stacks",KEY_VERSION="v1",MAX_KEY_LENGTH=255;export function stacksIdempotencyKey(scope,...parts){const tail=parts.filter((p)=>p!==null&&p!==void 0&&p!=="").map((p)=>String(p)).join(":"),raw=tail?`${KEY_PREFIX}:${scope}:${tail}:${KEY_VERSION}`:`${KEY_PREFIX}:${scope}:${KEY_VERSION}`;if(raw.length<=MAX_KEY_LENGTH)return raw;const hashed=createHash("sha256").update(tail).digest("hex").slice(0,32);return`${KEY_PREFIX}:${scope}:${hashed}:${KEY_VERSION}`}export function freshIdempotencyKey(scope,...parts){const random=createHash("sha256").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0,16);return stacksIdempotencyKey(scope,...parts,random)}
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- export * from "./payment";
2
- export { default as Payment } from "./payment";
3
- export * from "./billable";
4
- export * from "./drivers/stripe";
5
- export { freshIdempotencyKey, stacksIdempotencyKey } from "./idempotency";
1
+ export*from"./payment";export{default as Payment}from"./payment";export*from"./billable";export*from"./drivers/stripe";export{freshIdempotencyKey,stacksIdempotencyKey}from"./idempotency";
package/dist/payment.js CHANGED
@@ -1,199 +1 @@
1
- import { stripe } from "./drivers/stripe";
2
- import { manageCharge } from "./billable/charge";
3
- import { manageCheckout } from "./billable/checkout";
4
- import { manageCustomer } from "./billable/customer";
5
- import { manageInvoice } from "./billable/invoice";
6
- import { managePaymentMethod } from "./billable/payment-method";
7
- import { managePrice } from "./billable/price";
8
- import { manageCoupon, managePriceExtended, manageProduct } from "./billable/product";
9
- import { manageSubscription } from "./billable/subscription";
10
- import { manageSetupIntent } from "./billable/intent";
11
- import {
12
- manageWebhook,
13
- onCharge,
14
- onCheckout,
15
- onInvoice,
16
- onPaymentIntent,
17
- onSubscription,
18
- processWebhook
19
- } from "./billable/webhook";
20
- export async function charge(user, amount, paymentMethod, options = {}) {
21
- return manageCharge.charge(user, amount, paymentMethod, options);
22
- }
23
- export async function createPayment(user, amount, options = {}) {
24
- return manageCharge.createPayment(user, amount, options);
25
- }
26
- export async function refund(paymentIntentId, amount, options = {}) {
27
- return manageCharge.refund(paymentIntentId, {
28
- amount,
29
- ...options
30
- });
31
- }
32
- export async function checkout(user, lineItems, options = {}) {
33
- return manageCheckout.create(user, {
34
- line_items: lineItems,
35
- mode: "payment",
36
- ...options
37
- });
38
- }
39
- export async function subscriptionCheckout(user, priceId, options = {}) {
40
- return manageCheckout.create(user, {
41
- line_items: [{ price: priceId, quantity: 1 }],
42
- mode: "subscription",
43
- ...options
44
- });
45
- }
46
- export async function subscribe(user, lookupKey, options = {}) {
47
- return manageSubscription.create(user, "default", lookupKey, options);
48
- }
49
- export async function cancelSubscription(subscriptionId, immediately = !1) {
50
- return manageSubscription.cancel(subscriptionId, {
51
- prorate: !immediately,
52
- invoice_now: immediately
53
- });
54
- }
55
- export async function hasActiveSubscription(user, type = "default") {
56
- return manageSubscription.isValid(user, type);
57
- }
58
- export async function changeSubscription(user, newLookupKey, type = "default") {
59
- return manageSubscription.update(user, type, newLookupKey, {});
60
- }
61
- export async function getOrCreateCustomer(user, options = {}) {
62
- return manageCustomer.createOrGetStripeUser(user, options);
63
- }
64
- export async function updateCustomer(user, options) {
65
- return manageCustomer.updateStripeCustomer(user, options);
66
- }
67
- export async function deleteCustomer(user) {
68
- return manageCustomer.deleteStripeUser(user);
69
- }
70
- export async function addPaymentMethod(user, paymentMethodId) {
71
- return managePaymentMethod.addPaymentMethod(user, paymentMethodId);
72
- }
73
- export async function setDefaultPaymentMethod(user, paymentMethodId) {
74
- return managePaymentMethod.setUserDefaultPayment(user, paymentMethodId);
75
- }
76
- export async function removePaymentMethod(user, paymentMethodId) {
77
- return managePaymentMethod.deletePaymentMethod(user, paymentMethodId);
78
- }
79
- export async function createSetupIntent(user, options = {}) {
80
- return manageSetupIntent.create(user, options);
81
- }
82
- export async function getInvoices(user) {
83
- return manageInvoice.list(user);
84
- }
85
- export async function createInvoice(customerId, options = {}) {
86
- return stripe.invoices.create({
87
- customer: customerId,
88
- ...options
89
- });
90
- }
91
- export async function payInvoice(invoiceId) {
92
- return stripe.invoices.pay(invoiceId);
93
- }
94
- export async function createProduct(name, price, options = {}) {
95
- const { currency = "usd", interval, description, metadata } = options, priceParams = {
96
- unit_amount: price,
97
- currency
98
- };
99
- if (interval)
100
- priceParams.recurring = { interval };
101
- return manageProduct.createWithPrice({
102
- name,
103
- description,
104
- metadata
105
- }, priceParams);
106
- }
107
- export async function getPrice(lookupKey) {
108
- return managePrice.retrieveByLookupKey(lookupKey);
109
- }
110
- export async function listProducts(options = {}) {
111
- return manageProduct.list(options);
112
- }
113
- export async function createCoupon(options) {
114
- const params = {
115
- duration: options.duration || "once"
116
- };
117
- if (options.percentOff)
118
- params.percent_off = options.percentOff;
119
- else if (options.amountOff) {
120
- params.amount_off = options.amountOff;
121
- params.currency = options.currency || "usd";
122
- }
123
- if (options.name)
124
- params.name = options.name;
125
- if (options.durationInMonths)
126
- params.duration_in_months = options.durationInMonths;
127
- if (options.maxRedemptions)
128
- params.max_redemptions = options.maxRedemptions;
129
- return manageCoupon.create(params);
130
- }
131
- export async function createPromoCode(couponId, code, options = {}) {
132
- return manageCoupon.createPromotionCode({
133
- promotion: { type: "coupon", coupon: couponId },
134
- code,
135
- ...options
136
- });
137
- }
138
- export async function validatePromoCode(code) {
139
- return manageCoupon.retrievePromotionCode(code);
140
- }
141
- export function formatAmount(amount, currency) {
142
- const cfg = globalThis.config || {}, ccy = (currency || cfg.payment?.currency || cfg.billing?.currency || process.env.STRIPE_CURRENCY || "usd").toLowerCase(), locale = cfg.app?.locale || "en-US";
143
- return new Intl.NumberFormat(locale, {
144
- style: "currency",
145
- currency: ccy.toUpperCase()
146
- }).format(amount / 100);
147
- }
148
- export function toCents(dollars) {
149
- return Math.round(dollars * 100);
150
- }
151
- export function toDollars(cents) {
152
- return cents / 100;
153
- }
154
- export const Payment = {
155
- charge,
156
- createPayment,
157
- refund,
158
- checkout,
159
- subscriptionCheckout,
160
- subscribe,
161
- cancelSubscription,
162
- hasActiveSubscription,
163
- changeSubscription,
164
- getOrCreateCustomer,
165
- updateCustomer,
166
- deleteCustomer,
167
- addPaymentMethod,
168
- setDefaultPaymentMethod,
169
- removePaymentMethod,
170
- createSetupIntent,
171
- getInvoices,
172
- createInvoice,
173
- payInvoice,
174
- createProduct,
175
- getPrice,
176
- listProducts,
177
- createCoupon,
178
- createPromoCode,
179
- validatePromoCode,
180
- formatAmount,
181
- toCents,
182
- toDollars,
183
- webhook: manageWebhook,
184
- onPaymentIntent,
185
- onSubscription,
186
- onInvoice,
187
- onCheckout,
188
- onCharge,
189
- processWebhook,
190
- stripe,
191
- customer: manageCustomer,
192
- subscription: manageSubscription,
193
- invoice: manageInvoice,
194
- paymentMethod: managePaymentMethod,
195
- product: manageProduct,
196
- price: managePriceExtended,
197
- coupon: manageCoupon
198
- };
199
- export default Payment;
1
+ import{stripe}from"./drivers/stripe";import{manageCharge}from"./billable/charge";import{manageCheckout}from"./billable/checkout";import{manageCustomer}from"./billable/customer";import{manageInvoice}from"./billable/invoice";import{managePaymentMethod}from"./billable/payment-method";import{managePrice}from"./billable/price";import{manageCoupon,managePriceExtended,manageProduct}from"./billable/product";import{manageSubscription}from"./billable/subscription";import{manageSetupIntent}from"./billable/intent";import{manageWebhook,onCharge,onCheckout,onInvoice,onPaymentIntent,onSubscription,processWebhook}from"./billable/webhook";export async function charge(user,amount,paymentMethod,options={}){return manageCharge.charge(user,amount,paymentMethod,options)}export async function createPayment(user,amount,options={}){return manageCharge.createPayment(user,amount,options)}export async function refund(paymentIntentId,amount,options={}){return manageCharge.refund(paymentIntentId,{amount,...options})}export async function checkout(user,lineItems,options={}){return manageCheckout.create(user,{line_items:lineItems,mode:"payment",...options})}export async function subscriptionCheckout(user,priceId,options={}){return manageCheckout.create(user,{line_items:[{price:priceId,quantity:1}],mode:"subscription",...options})}export async function subscribe(user,lookupKey,options={}){return manageSubscription.create(user,"default",lookupKey,options)}export async function cancelSubscription(subscriptionId,immediately=!1){return manageSubscription.cancel(subscriptionId,{prorate:!immediately,invoice_now:immediately})}export async function hasActiveSubscription(user,type="default"){return manageSubscription.isValid(user,type)}export async function changeSubscription(user,newLookupKey,type="default"){return manageSubscription.update(user,type,newLookupKey,{})}export async function getOrCreateCustomer(user,options={}){return manageCustomer.createOrGetStripeUser(user,options)}export async function updateCustomer(user,options){return manageCustomer.updateStripeCustomer(user,options)}export async function deleteCustomer(user){return manageCustomer.deleteStripeUser(user)}export async function addPaymentMethod(user,paymentMethodId){return managePaymentMethod.addPaymentMethod(user,paymentMethodId)}export async function setDefaultPaymentMethod(user,paymentMethodId){return managePaymentMethod.setUserDefaultPayment(user,paymentMethodId)}export async function removePaymentMethod(user,paymentMethodId){return managePaymentMethod.deletePaymentMethod(user,paymentMethodId)}export async function createSetupIntent(user,options={}){return manageSetupIntent.create(user,options)}export async function getInvoices(user){return manageInvoice.list(user)}export async function createInvoice(customerId,options={}){return stripe.invoices.create({customer:customerId,...options})}export async function payInvoice(invoiceId){return stripe.invoices.pay(invoiceId)}export async function createProduct(name,price,options={}){const{currency="usd",interval,description,metadata}=options,priceParams={unit_amount:price,currency};if(interval)priceParams.recurring={interval};return manageProduct.createWithPrice({name,description,metadata},priceParams)}export async function getPrice(lookupKey){return managePrice.retrieveByLookupKey(lookupKey)}export async function listProducts(options={}){return manageProduct.list(options)}export async function createCoupon(options){const params={duration:options.duration||"once"};if(options.percentOff)params.percent_off=options.percentOff;else if(options.amountOff){params.amount_off=options.amountOff;params.currency=options.currency||"usd"}if(options.name)params.name=options.name;if(options.durationInMonths)params.duration_in_months=options.durationInMonths;if(options.maxRedemptions)params.max_redemptions=options.maxRedemptions;return manageCoupon.create(params)}export async function createPromoCode(couponId,code,options={}){return manageCoupon.createPromotionCode({promotion:{type:"coupon",coupon:couponId},code,...options})}export async function validatePromoCode(code){return manageCoupon.retrievePromotionCode(code)}export function formatAmount(amount,currency){const cfg=globalThis.config||{},ccy=(currency||cfg.payment?.currency||cfg.billing?.currency||process.env.STRIPE_CURRENCY||"usd").toLowerCase(),locale=cfg.app?.locale||"en-US";return new Intl.NumberFormat(locale,{style:"currency",currency:ccy.toUpperCase()}).format(amount/100)}export function toCents(dollars){return Math.round(dollars*100)}export function toDollars(cents){return cents/100}export const Payment={charge,createPayment,refund,checkout,subscriptionCheckout,subscribe,cancelSubscription,hasActiveSubscription,changeSubscription,getOrCreateCustomer,updateCustomer,deleteCustomer,addPaymentMethod,setDefaultPaymentMethod,removePaymentMethod,createSetupIntent,getInvoices,createInvoice,payInvoice,createProduct,getPrice,listProducts,createCoupon,createPromoCode,validatePromoCode,formatAmount,toCents,toDollars,webhook:manageWebhook,onPaymentIntent,onSubscription,onInvoice,onCheckout,onCharge,processWebhook,stripe,customer:manageCustomer,subscription:manageSubscription,invoice:manageInvoice,paymentMethod:managePaymentMethod,product:manageProduct,price:managePriceExtended,coupon:manageCoupon};export default Payment;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/payments",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.258",
5
+ "version": "0.70.259",
6
6
  "description": "The Stacks payments package. Currently supporting Stripe.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -36,9 +36,16 @@
36
36
  "default": "./dist/index.js"
37
37
  },
38
38
  "./*": {
39
- "bun": "./dist/*",
40
- "import": "./dist/*",
41
- "default": "./dist/*"
39
+ "types": "./dist/*.d.ts",
40
+ "bun": "./dist/*.js",
41
+ "import": "./dist/*.js",
42
+ "default": "./dist/*.js"
43
+ },
44
+ "./*.js": {
45
+ "types": "./dist/*.d.ts",
46
+ "bun": "./dist/*.js",
47
+ "import": "./dist/*.js",
48
+ "default": "./dist/*.js"
42
49
  }
43
50
  },
44
51
  "module": "dist/index.js",
@@ -65,9 +72,9 @@
65
72
  }
66
73
  },
67
74
  "devDependencies": {
68
- "@stacksjs/config": "0.70.258",
75
+ "@stacksjs/config": "0.70.259",
69
76
  "better-dx": "^0.2.17",
70
- "@stacksjs/utils": "0.70.258",
77
+ "@stacksjs/utils": "0.70.259",
71
78
  "@stripe/stripe-js": "^9.10.0",
72
79
  "stripe": "^22.3.2"
73
80
  }