@stacksjs/payments 0.75.87 → 0.75.88
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/driver/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stripe } from '../drivers/stripe';
|
|
2
2
|
import type { AdyenConfig } from './adyen';
|
|
3
|
+
import type { LemonSqueezyConfig } from './lemonsqueezy';
|
|
3
4
|
import type { PaddleConfig } from './paddle';
|
|
4
5
|
import type { PaymentDriver } from './types';
|
|
5
6
|
import type { StripeOperations } from './stripe';
|
|
@@ -12,6 +13,8 @@ export declare function registerPaymentDriver(name: string, factory: DriverFacto
|
|
|
12
13
|
export declare function adyenConfig(settings?: Partial<AdyenConfig>, env?: Record<string, string | undefined>): AdyenConfig;
|
|
13
14
|
/** Paddle's settings: config/payment.ts, then the PADDLE_* environment. */
|
|
14
15
|
export declare function paddleConfig(settings?: Partial<PaddleConfig>, env?: Record<string, string | undefined>): PaddleConfig;
|
|
16
|
+
/** Lemon Squeezy's settings: config/payment.ts, then the LEMONSQUEEZY_* environment. */
|
|
17
|
+
export declare function lemonSqueezyConfig(settings?: Partial<LemonSqueezyConfig>, env?: Record<string, string | undefined>): LemonSqueezyConfig;
|
|
15
18
|
/** The driver `config.payment.driver` names: `stripe` unless it says otherwise. */
|
|
16
19
|
export declare function configuredPaymentDriver(): string;
|
|
17
20
|
/**
|
|
@@ -30,6 +33,7 @@ export declare function paymentDriver(name?: string): PaymentDriver;
|
|
|
30
33
|
export declare const defaultStripeOperations: StripeOperations;
|
|
31
34
|
declare type DriverFactory = () => PaymentDriver;
|
|
32
35
|
export * from './adyen';
|
|
36
|
+
export * from './lemonsqueezy';
|
|
33
37
|
export * from './paddle';
|
|
34
38
|
export * from './paddle-checkout';
|
|
35
39
|
export * from './stripe';
|
package/dist/driver/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import process from"node:process";import{config,services}from"@stacksjs/config";import{manageCharge}from"../billable/charge";import{manageCheckout}from"../billable/checkout";import{manageCustomer}from"../billable/customer";import{managePaymentMethod}from"../billable/payment-method";import{manageSubscription}from"../billable/subscription";import{constructEvent}from"../billable/webhook";import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";import{AdyenDriver}from"./adyen";import{PaddleDriver}from"./paddle";import{PaymentUnsupportedError}from"./types";import{StripeDriver}from"./stripe";export*from"./adyen";export*from"./paddle";export*from"./paddle-checkout";export*from"./stripe";export*from"./types";export const defaultStripeOperations={customer:(payer)=>manageCustomer.createOrGetStripeUser(payer,{}),createIntent:(payer,params,idempotencyKey)=>stripe.paymentIntents.create(params,{idempotencyKey:idempotencyKey??freshIdempotencyKey("payment_intent.create",payer.id,params.amount)}),refund:(paymentIntentId,params)=>manageCharge.refund(paymentIntentId,params),createCheckout:(payer,params)=>manageCheckout.create(payer,params),listPaymentMethods:async(customerId)=>(await stripe.paymentMethods.list({customer:customerId,limit:100})).data,deletePaymentMethod:(payer,paymentMethodId)=>managePaymentMethod.deletePaymentMethod(payer,paymentMethodId),subscribe:(payer,type,lookupKey)=>manageSubscription.create(payer,type,lookupKey,{}),cancelSubscription:(subscriptionId,atPeriodEnd)=>atPeriodEnd?manageSubscription.cancelAtPeriodEnd(subscriptionId):manageSubscription.cancel(subscriptionId),listSubscriptions:async(customerId)=>(await stripe.subscriptions.list({customer:customerId,status:"all",limit:100})).data,constructEvent:(payload,signature,secret)=>constructEvent(payload,signature,secret)};const custom=new Map;export function registerPaymentDriver(name,factory){if(name
|
|
1
|
+
import process from"node:process";import{config,services}from"@stacksjs/config";import{manageCharge}from"../billable/charge";import{manageCheckout}from"../billable/checkout";import{manageCustomer}from"../billable/customer";import{managePaymentMethod}from"../billable/payment-method";import{manageSubscription}from"../billable/subscription";import{constructEvent}from"../billable/webhook";import{stripe}from"../drivers/stripe";import{freshIdempotencyKey}from"../idempotency";import{AdyenDriver}from"./adyen";import{LemonSqueezyDriver}from"./lemonsqueezy";import{PaddleDriver}from"./paddle";import{PaymentUnsupportedError}from"./types";import{StripeDriver}from"./stripe";export*from"./adyen";export*from"./lemonsqueezy";export*from"./paddle";export*from"./paddle-checkout";export*from"./stripe";export*from"./types";export const defaultStripeOperations={customer:(payer)=>manageCustomer.createOrGetStripeUser(payer,{}),createIntent:(payer,params,idempotencyKey)=>stripe.paymentIntents.create(params,{idempotencyKey:idempotencyKey??freshIdempotencyKey("payment_intent.create",payer.id,params.amount)}),refund:(paymentIntentId,params)=>manageCharge.refund(paymentIntentId,params),createCheckout:(payer,params)=>manageCheckout.create(payer,params),listPaymentMethods:async(customerId)=>(await stripe.paymentMethods.list({customer:customerId,limit:100})).data,deletePaymentMethod:(payer,paymentMethodId)=>managePaymentMethod.deletePaymentMethod(payer,paymentMethodId),subscribe:(payer,type,lookupKey)=>manageSubscription.create(payer,type,lookupKey,{}),cancelSubscription:(subscriptionId,atPeriodEnd)=>atPeriodEnd?manageSubscription.cancelAtPeriodEnd(subscriptionId):manageSubscription.cancel(subscriptionId),listSubscriptions:async(customerId)=>(await stripe.subscriptions.list({customer:customerId,status:"all",limit:100})).data,constructEvent:(payload,signature,secret)=>constructEvent(payload,signature,secret)};const BUILT_IN_DRIVERS=["stripe","adyen","paddle","lemonsqueezy"],custom=new Map;export function registerPaymentDriver(name,factory){if(BUILT_IN_DRIVERS.includes(name))throw Error(`"${name}" is a built-in payment driver and cannot be replaced.`);custom.set(name,factory)}export function adyenConfig(settings={},env=process.env){const environment=settings.environment??(env.ADYEN_ENVIRONMENT==="live"?"live":"test");return{apiKey:settings.apiKey||env.ADYEN_API_KEY||"",merchantAccount:settings.merchantAccount||env.ADYEN_MERCHANT_ACCOUNT||"",environment,liveUrlPrefix:settings.liveUrlPrefix||env.ADYEN_LIVE_URL_PREFIX||void 0,hmacKey:settings.hmacKey||env.ADYEN_HMAC_KEY||void 0,shopperReferencePrefix:settings.shopperReferencePrefix}}export function paddleConfig(settings={},env=process.env){const environment=settings.environment??(env.PADDLE_ENVIRONMENT==="live"?"live":"sandbox");return{apiKey:settings.apiKey||env.PADDLE_API_KEY||"",environment,webhookSecret:settings.webhookSecret||env.PADDLE_WEBHOOK_SECRET||void 0,clientToken:settings.clientToken||env.PADDLE_CLIENT_TOKEN||void 0,taxCategory:settings.taxCategory||env.PADDLE_TAX_CATEGORY||void 0,webhookTolerance:settings.webhookTolerance}}export function lemonSqueezyConfig(settings={},env=process.env){return{apiKey:settings.apiKey||env.LEMONSQUEEZY_API_KEY||"",storeId:settings.storeId||env.LEMONSQUEEZY_STORE_ID||"",webhookSecret:settings.webhookSecret||env.LEMONSQUEEZY_WEBHOOK_SECRET||void 0,customPriceVariantId:settings.customPriceVariantId||env.LEMONSQUEEZY_VARIANT_ID||void 0,testMode:settings.testMode??env.LEMONSQUEEZY_TEST_MODE==="true"}}export function configuredPaymentDriver(){return(config.payment??{}).driver??"stripe"}export function assertStripeDriver(operation){const configured=configuredPaymentDriver();if(configured!=="stripe")throw new PaymentUnsupportedError(configured,operation,"it is a Stripe-only operation")}export function paymentDriver(name){const settings=config.payment??{},chosen=name??configuredPaymentDriver();if(chosen==="stripe")return new StripeDriver({operations:defaultStripeOperations,webhookSecret:settings.stripe?.webhookSecret||services?.stripe?.webhookSecret||process.env.STRIPE_WEBHOOK_SECRET||void 0});if(chosen==="adyen"){const appUrl=config.app?.url;return new AdyenDriver({...adyenConfig(settings.adyen),...appUrl?{appUrl}:{}})}if(chosen==="paddle"){const appUrl=config.app?.url;return new PaddleDriver({...paddleConfig(settings.paddle),...appUrl?{appUrl}:{}})}if(chosen==="lemonsqueezy"){const appUrl=config.app?.url;return new LemonSqueezyDriver({...lemonSqueezyConfig(settings.lemonsqueezy),...appUrl?{appUrl}:{}})}const factory=custom.get(chosen);if(!factory)throw Error(`No payment driver named "${chosen}". Built in: ${BUILT_IN_DRIVERS.join(", ")}${custom.size?`; registered: ${[...custom.keys()].join(", ")}`:""}.`);return factory()}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CheckoutRequest, CheckoutSession, Customer, Payer, PaymentDriver, PaymentEvent, PaymentResult, RefundOptions, RefundResult, StoredPaymentMethod, SubscriptionSummary, WebhookRequest } from './types';
|
|
2
|
+
/** A Lemon Squeezy subscription in our terms. */
|
|
3
|
+
export declare function summarizeLemonSqueezySubscription(subscription: Resource<SubscriptionAttributes>): SubscriptionSummary;
|
|
4
|
+
/** HMAC-SHA256 of the raw body under the webhook's signing secret, hex: Lemon Squeezy's `X-Signature`. */
|
|
5
|
+
export declare function lemonSqueezySignature(payload: string, secret: string): string;
|
|
6
|
+
export declare const LEMONSQUEEZY_API_URL: 'https://api.lemonsqueezy.com/v1';
|
|
7
|
+
/**
|
|
8
|
+
* The Lemon Squeezy driver (stacksjs/stacks#536), on API v1.
|
|
9
|
+
*
|
|
10
|
+
* Lemon Squeezy is a merchant of record with a hosted checkout, and its API
|
|
11
|
+
* is narrower than a processor's. The driver says where rather than
|
|
12
|
+
* pretending:
|
|
13
|
+
*
|
|
14
|
+
* - Every payment goes through the hosted checkout, and a checkout sells
|
|
15
|
+
* one variant. There is no server-side charge, no payment the browser
|
|
16
|
+
* completes on an SDK, and no stored payment methods to list
|
|
17
|
+
* (`charge`, `createPayment` and `paymentMethods` are unsupported).
|
|
18
|
+
* - A subscription starts at checkout, on a subscription variant
|
|
19
|
+
* (`subscribe` is unsupported), and cancelling one always runs to the end
|
|
20
|
+
* of the period it has paid for.
|
|
21
|
+
* - A line priced in the request (`name` and `unitAmount`) is sold as a
|
|
22
|
+
* custom price on one variant you set aside for it, in the store's own
|
|
23
|
+
* currency.
|
|
24
|
+
* - A refund is issued at once against the order, whole or in part.
|
|
25
|
+
* - The `order_refunded` webhook states the order's refunded total rather
|
|
26
|
+
* than the one refund, so it arrives as `refundedTotal`.
|
|
27
|
+
*
|
|
28
|
+
* Verified against Lemon Squeezy's API reference; not yet run against a live
|
|
29
|
+
* store.
|
|
30
|
+
*/
|
|
31
|
+
export declare interface LemonSqueezyConfig {
|
|
32
|
+
apiKey: string
|
|
33
|
+
storeId: string
|
|
34
|
+
webhookSecret?: string
|
|
35
|
+
testMode?: boolean
|
|
36
|
+
customPriceVariantId?: string
|
|
37
|
+
appUrl?: string
|
|
38
|
+
}
|
|
39
|
+
declare interface Resource<A> {
|
|
40
|
+
type: string
|
|
41
|
+
id: string
|
|
42
|
+
attributes: A
|
|
43
|
+
}
|
|
44
|
+
declare interface SubscriptionAttributes {
|
|
45
|
+
status: string
|
|
46
|
+
variant_id?: number | string
|
|
47
|
+
variant_name?: string | null
|
|
48
|
+
product_name?: string | null
|
|
49
|
+
cancelled?: boolean
|
|
50
|
+
renews_at?: string | null
|
|
51
|
+
ends_at?: string | null
|
|
52
|
+
}
|
|
53
|
+
export declare class LemonSqueezyDriver implements PaymentDriver {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly capabilities: unknown;
|
|
56
|
+
constructor(config: LemonSqueezyConfig, options?: { fetch?: typeof fetch });
|
|
57
|
+
customer(payer: Payer): Promise<Customer>;
|
|
58
|
+
charge(): Promise<PaymentResult>;
|
|
59
|
+
createPayment(): Promise<PaymentResult>;
|
|
60
|
+
refund(paymentId: string, options?: RefundOptions): Promise<RefundResult>;
|
|
61
|
+
checkout(payer: Payer, request: CheckoutRequest): Promise<CheckoutSession>;
|
|
62
|
+
paymentMethods(): Promise<StoredPaymentMethod[]>;
|
|
63
|
+
removePaymentMethod(): Promise<void>;
|
|
64
|
+
subscribe(): Promise<SubscriptionSummary>;
|
|
65
|
+
cancelSubscription(subscriptionId: string, options?: { atPeriodEnd?: boolean }): Promise<SubscriptionSummary>;
|
|
66
|
+
subscriptions(payer: Payer): Promise<SubscriptionSummary[]>;
|
|
67
|
+
verifyWebhook(request: WebhookRequest): Promise<PaymentEvent[]>;
|
|
68
|
+
acknowledgeWebhook(): Response;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash,createHmac,timingSafeEqual}from"node:crypto";import{assertMoney,headerOf,PaymentProviderError,PaymentUnsupportedError,WebhookNotConfiguredError,WebhookSignatureError}from"./types";export const LEMONSQUEEZY_API_URL="https://api.lemonsqueezy.com/v1";const CAPABILITIES=new Set(["customers","refund","checkout","subscriptions","webhooks"]),SUBSCRIPTION_STATUSES={on_trial:"trialing",active:"active",paused:"paused",past_due:"past_due",unpaid:"unpaid",cancelled:"active",expired:"canceled"};export function summarizeLemonSqueezySubscription(subscription){const{attributes}=subscription,cancelled=attributes.status==="cancelled"||attributes.cancelled===!0,periodEnd=cancelled?attributes.ends_at:attributes.renews_at;return{id:String(subscription.id),status:SUBSCRIPTION_STATUSES[attributes.status]??"unknown",price:attributes.variant_id!==void 0?{id:String(attributes.variant_id),name:attributes.variant_name??attributes.product_name??null,amount:null,interval:null}:null,currentPeriodEnd:periodEnd?new Date(periodEnd):null,cancelAtPeriodEnd:cancelled&&attributes.status!=="expired",raw:subscription}}export function lemonSqueezySignature(payload,secret){return createHmac("sha256",secret).update(payload,"utf8").digest("hex")}export class LemonSqueezyDriver{name="lemonsqueezy";capabilities=CAPABILITIES;config;fetch;storeCurrency;constructor(config,options={}){if(!config.apiKey)throw Error("The Lemon Squeezy payment driver needs an API key: set LEMONSQUEEZY_API_KEY or payment.lemonsqueezy.apiKey.");if(!config.storeId)throw Error("The Lemon Squeezy payment driver needs a store: set LEMONSQUEEZY_STORE_ID or payment.lemonsqueezy.storeId.");this.config=config;this.fetch=options.fetch??fetch}async request(method,path,options={}){const url=new URL(`${LEMONSQUEEZY_API_URL}${path}`);for(const[key,value]of Object.entries(options.query??{}))url.searchParams.set(key,value);const headers={Authorization:`Bearer ${this.config.apiKey}`,Accept:"application/vnd.api+json"};if(options.body!==void 0)headers["Content-Type"]="application/vnd.api+json";const response=await this.fetch(url,{method,headers,body:options.body===void 0?void 0:JSON.stringify(options.body)}),text=await response.text(),body=text?JSON.parse(text):{};if(!response.ok){const error=body.errors?.[0];throw new PaymentProviderError("lemonsqueezy",response.status,error?.detail??error?.title??response.statusText,error?.code??null)}return body}async list(path,filters){const all=[];for(let page=1;;page++){const result=await this.request("GET",path,{query:{...filters,"page[number]":String(page),"page[size]":"100"}});all.push(...result.data??[]);if(page>=(result.meta?.page?.lastPage??1))return all}}store(){return{data:{type:"stores",id:String(this.config.storeId)}}}assertReturnUrl(url,label){let target;try{target=new URL(url)}catch{throw TypeError(`${label} is not a valid URL: ${url}`)}if(!this.config.appUrl)return;const app=new URL(/^https?:\/\//.test(this.config.appUrl)?this.config.appUrl:`https://${this.config.appUrl}`);if(target.origin!==app.origin)throw TypeError(`${label} (${target.origin}) is not on the application's origin (${app.origin})`)}currency(){this.storeCurrency??=this.request("GET",`/stores/${encodeURIComponent(this.config.storeId)}`).then((result)=>result.data.attributes.currency.toLowerCase());return this.storeCurrency}async customer(payer){if(!payer.email)throw TypeError("A Lemon Squeezy customer is identified by email: the payer has none.");const existing=(await this.request("GET","/customers",{query:{"filter[store_id]":String(this.config.storeId),"filter[email]":payer.email}})).data?.[0];if(existing)return{id:String(existing.id),email:existing.attributes.email,name:existing.attributes.name??null};const created=await this.request("POST","/customers",{body:{data:{type:"customers",attributes:{email:payer.email,name:payer.name||payer.email},relationships:{store:this.store()}}}});return{id:String(created.data.id),email:created.data.attributes.email,name:created.data.attributes.name??null}}async charge(){throw new PaymentUnsupportedError("lemonsqueezy","charge","Lemon Squeezy takes every payment on its hosted checkout: use checkout")}async createPayment(){throw new PaymentUnsupportedError("lemonsqueezy","createPayment","Lemon Squeezy takes every payment on its hosted checkout: use checkout")}async refund(paymentId,options={}){const attributes={};if(options.amount){assertMoney(options.amount,"refund amount");const order=await this.request("GET",`/orders/${encodeURIComponent(paymentId)}`);if(order.data.attributes.currency.toLowerCase()!==options.amount.currency.toLowerCase())throw TypeError(`The refund is in ${options.amount.currency.toUpperCase()}, but the order is in ${order.data.attributes.currency.toUpperCase()}.`);attributes.amount=options.amount.amount}const result=await this.request("POST",`/orders/${encodeURIComponent(paymentId)}/refund`,{body:{data:{type:"orders",id:String(paymentId),...options.amount?{attributes}:{}}}}),order=result.data.attributes;return{id:`${paymentId}:refunded:${order.refunded_amount??0}`,paymentId,status:"succeeded",amount:options.amount?{amount:options.amount.amount,currency:options.amount.currency.toLowerCase()}:null,raw:result.data}}async checkout(payer,request){if(request.mode==="setup")throw new PaymentUnsupportedError("lemonsqueezy","setup checkout","Lemon Squeezy saves a payment method while taking a payment, never on its own");if(request.lines.length!==1)throw new PaymentUnsupportedError("lemonsqueezy",`a checkout with ${request.lines.length} lines`,"a Lemon Squeezy checkout sells one variant");if(request.cancelUrl&&request.cancelUrl!==request.successUrl)throw new PaymentUnsupportedError("lemonsqueezy","a separate cancelUrl","the hosted checkout has no cancel page");if(request.trialDays)throw new PaymentUnsupportedError("lemonsqueezy","a checkout trial","a Lemon Squeezy trial is set on the subscription variant");this.assertReturnUrl(request.successUrl,"successUrl");const line=request.lines[0];if(!Number.isInteger(line.quantity)||line.quantity<1)throw TypeError(`A checkout line's quantity must be a positive whole number, got ${line.quantity}`);let variantId;const attributes={},productOptions={redirect_url:request.successUrl};if("price"in line)variantId=line.price;else{if(request.mode==="subscription")throw TypeError("A Lemon Squeezy subscription checkout needs a subscription variant: a line priced here has no billing interval.");if(!this.config.customPriceVariantId)throw TypeError("A checkout line priced here needs a variant to sell it as: set LEMONSQUEEZY_VARIANT_ID or payment.lemonsqueezy.customPriceVariantId.");const currency=await this.currency();if(request.currency&&request.currency.toLowerCase()!==currency)throw TypeError(`The checkout is in ${request.currency.toUpperCase()}, but the Lemon Squeezy store sells in ${currency.toUpperCase()}.`);const total={amount:line.unitAmount*line.quantity,currency};assertMoney(total,"checkout amount");variantId=this.config.customPriceVariantId;attributes.custom_price=total.amount;productOptions.name=line.quantity===1?line.name:`${line.name} x ${line.quantity}`}const checkoutData={custom:{...request.metadata,...request.reference?{reference:request.reference}:{},stacks_user_id:String(payer.id)}};if(payer.email)checkoutData.email=payer.email;if(payer.name)checkoutData.name=payer.name;if("price"in line&&line.quantity>1)checkoutData.variant_quantities=[{variant_id:Number(variantId),quantity:line.quantity}];const result=await this.request("POST","/checkouts",{body:{data:{type:"checkouts",attributes:{...attributes,product_options:productOptions,checkout_options:{discount:request.allowPromotionCodes===!0},checkout_data:checkoutData,...this.config.testMode?{test_mode:!0}:{}},relationships:{store:this.store(),variant:{data:{type:"variants",id:String(variantId)}}}}}});return{id:String(result.data.id),url:result.data.attributes.url,expiresAt:result.data.attributes.expires_at?new Date(result.data.attributes.expires_at):null,raw:result.data}}async paymentMethods(){throw new PaymentUnsupportedError("lemonsqueezy","paymentMethods","Lemon Squeezy keeps the card on the subscription; send the payer to its update_payment_method URL")}async removePaymentMethod(){throw new PaymentUnsupportedError("lemonsqueezy","removePaymentMethod","Lemon Squeezy keeps the card on the subscription; send the payer to its update_payment_method URL")}async subscribe(){throw new PaymentUnsupportedError("lemonsqueezy","subscribe","a Lemon Squeezy subscription starts at checkout: use checkout with a subscription variant")}async cancelSubscription(subscriptionId,options={}){if(!options.atPeriodEnd)throw new PaymentUnsupportedError("lemonsqueezy","an immediate cancellation","Lemon Squeezy cancels at the end of the billing period: pass { atPeriodEnd: true }");const result=await this.request("DELETE",`/subscriptions/${encodeURIComponent(subscriptionId)}`);return summarizeLemonSqueezySubscription(result.data)}async subscriptions(payer){if(!payer.email)throw TypeError("Lemon Squeezy finds a payer's subscriptions by email: the payer has none.");return(await this.list("/subscriptions",{"filter[store_id]":String(this.config.storeId),"filter[user_email]":payer.email})).map(summarizeLemonSqueezySubscription)}async verifyWebhook(request){if(!this.config.webhookSecret)throw new WebhookNotConfiguredError("lemonsqueezy","Lemon Squeezy webhooks cannot be verified without the signing secret: set LEMONSQUEEZY_WEBHOOK_SECRET or payment.lemonsqueezy.webhookSecret.");const given=headerOf(request.headers,"x-signature");if(!given)throw new WebhookSignatureError("lemonsqueezy","there is no X-Signature header");const expected=Buffer.from(lemonSqueezySignature(request.payload,this.config.webhookSecret)),actual=Buffer.from(given.trim());if(actual.length!==expected.length||!timingSafeEqual(actual,expected))throw new WebhookSignatureError("lemonsqueezy","it is signed with another secret");let notification;try{notification=JSON.parse(request.payload)}catch{throw new WebhookSignatureError("lemonsqueezy","the body is not JSON")}const id=`${notification.meta?.event_name??"unknown"}:${createHash("sha256").update(request.payload,"utf8").digest("hex").slice(0,32)}`;return[this.translate(id,notification)]}translate(id,notification){const name=notification.meta?.event_name??"",data=notification.data,attributes=data?.attributes??{},reference=typeof notification.meta?.custom_data?.reference==="string"?notification.meta.custom_data.reference:null,money=(amount)=>typeof amount==="number"&&typeof attributes.currency==="string"?{amount,currency:attributes.currency.toLowerCase()}:null,base={provider:this.name,id,providerType:name,reference:data?.id!==void 0?String(data.id):null,merchantReference:reference,raw:notification};if(name==="order_created"){const type=attributes.status==="paid"?"payment.succeeded":attributes.status==="failed"?"payment.failed":"unknown";return{...base,type,amount:money(attributes.total),reason:null}}if(name==="order_refunded"){const refundedTotal=money(attributes.refunded_amount);return{...base,type:"refund.succeeded",amount:null,...refundedTotal?{refundedTotal}:{},reason:null}}const type={subscription_created:"subscription.created",subscription_updated:"subscription.updated",subscription_cancelled:"subscription.updated",subscription_resumed:"subscription.updated",subscription_paused:"subscription.updated",subscription_unpaused:"subscription.updated",subscription_expired:"subscription.canceled",subscription_payment_success:"invoice.paid",subscription_payment_recovered:"invoice.paid",subscription_payment_failed:"invoice.payment_failed"}[name]??"unknown";return{...base,type,amount:type.startsWith("invoice.")?money(attributes.total):null,reason:null}}acknowledgeWebhook(){return new Response(null,{status:200})}}
|
package/dist/driver/types.d.ts
CHANGED
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.88",
|
|
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.88",
|
|
64
|
+
"@stacksjs/database": "0.75.88",
|
|
65
|
+
"@stacksjs/error-handling": "0.75.88",
|
|
66
|
+
"@stacksjs/logging": "0.75.88",
|
|
67
|
+
"@stacksjs/orm": "0.75.88",
|
|
68
|
+
"@stacksjs/types": "0.75.88"
|
|
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.88",
|
|
84
84
|
"@stripe/stripe-js": "^10.0.0",
|
|
85
85
|
"better-dx": "^0.2.24",
|
|
86
86
|
"stripe": "23.0.0"
|