@stacksjs/payments 0.75.169 → 0.75.170
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/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/terminal.d.ts +163 -0
- package/dist/terminal.js +1 -0
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -27,3 +27,6 @@ export { subscriptionGrantsAccess, subscriptionPeriod } from './subscription-sta
|
|
|
27
27
|
// Bank debits (SEPA Direct Debit, Bacs, ACH, BECS): methods per currency,
|
|
28
28
|
// debit webhook outcomes and mandates. Pure; also `@stacksjs/payments/direct-debit`.
|
|
29
29
|
export * from './direct-debit';
|
|
30
|
+
// Card-present payments with Stripe Terminal: locations, readers, charges on a
|
|
31
|
+
// reader, refunds to the card. Also `@stacksjs/payments/terminal`.
|
|
32
|
+
export * from './terminal';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./payment";export{default as Payment}from"./payment";export*from"./driver";export*from"./billable";export*from"./connect";export*from"./drivers/stripe";export{freshIdempotencyKey,stacksIdempotencyKey}from"./idempotency";export{subscriptionGrantsAccess,subscriptionPeriod}from"./subscription-state";export*from"./direct-debit";
|
|
1
|
+
export*from"./payment";export{default as Payment}from"./payment";export*from"./driver";export*from"./billable";export*from"./connect";export*from"./drivers/stripe";export{freshIdempotencyKey,stacksIdempotencyKey}from"./idempotency";export{subscriptionGrantsAccess,subscriptionPeriod}from"./subscription-state";export*from"./direct-debit";export*from"./terminal";
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stripe's form encoding: nested objects as `a[b]=c`, arrays as `a[0]=x`,
|
|
3
|
+
* booleans as `true`/`false`; null and undefined are left out.
|
|
4
|
+
*/
|
|
5
|
+
export declare function stripeFormEncode(params: Record<string, FormValue>): string;
|
|
6
|
+
/** A minimal Stripe REST client: one call, Stripe's errors as StripeApiError. */
|
|
7
|
+
export declare function stripeRest(options: StripeRestOptions): StripeRest;
|
|
8
|
+
/** The Terminal calls a till makes, against Stripe or a double of it (`apiBase`). */
|
|
9
|
+
export declare function stripeTerminal(options: StripeRestOptions): StripeTerminal;
|
|
10
|
+
/**
|
|
11
|
+
* Reads a charge's state the way Stripe's server-driven guide does: the
|
|
12
|
+
* PaymentIntent decides success and cancellation; while it waits, the
|
|
13
|
+
* reader's action for this PaymentIntent says whether the card was declined
|
|
14
|
+
* or the reader gave up. A failed attempt leaves the PaymentIntent
|
|
15
|
+
* `requires_payment_method`, so the same charge can go to the reader again.
|
|
16
|
+
*/
|
|
17
|
+
export declare function terminalChargeState(paymentIntent: { id?: string, status?: string, last_payment_error?: { code?: string, decline_code?: string, message?: string } | null } | null, reader?: Pick<TerminalReader, 'action'> | null): TerminalChargeOutcome;
|
|
18
|
+
/** The card details of a charge (or of a PaymentIntent with `latest_charge` expanded). */
|
|
19
|
+
export declare function cardPresentDetails(chargeOrIntent: any): CardPresentDetails | null;
|
|
20
|
+
/** The charge id behind a PaymentIntent (`latest_charge`, expanded or not). */
|
|
21
|
+
export declare function latestChargeId(paymentIntent: any): string | null;
|
|
22
|
+
/** Whether a secret key is a test-mode key (simulated readers, test cards). */
|
|
23
|
+
export declare function isTestKey(secretKey: string | null | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The API version these calls are written for. Pinned, so an account's
|
|
26
|
+
* default version cannot change the shapes: on it a card-present
|
|
27
|
+
* PaymentIntent names its method with `allowed_payment_method_types` (the
|
|
28
|
+
* older `payment_method_types` is refused since 2026-09-30.endive).
|
|
29
|
+
*/
|
|
30
|
+
export declare const STRIPE_TERMINAL_API_VERSION: '2026-09-30.endive';
|
|
31
|
+
declare const path: (value: string) => string;
|
|
32
|
+
/** Registration codes Stripe accepts for a simulated reader in test mode. */
|
|
33
|
+
export declare const SIMULATED_READER_CODES: readonly ['simulated-wpe', 'simulated-s700', 'simulated-s710', 'simulated-v660p', 'simulated-wp3'];
|
|
34
|
+
export declare interface StripeRestOptions {
|
|
35
|
+
secretKey: string
|
|
36
|
+
apiBase?: string
|
|
37
|
+
apiVersion?: string | null
|
|
38
|
+
stripeAccount?: string
|
|
39
|
+
fetch?: typeof fetch
|
|
40
|
+
timeoutMs?: number
|
|
41
|
+
}
|
|
42
|
+
export declare interface StripeRequestOptions {
|
|
43
|
+
idempotencyKey?: string
|
|
44
|
+
}
|
|
45
|
+
export declare interface StripeRest {
|
|
46
|
+
request: <T = any>(method: 'GET' | 'POST' | 'DELETE', path: string, params?: Record<string, FormValue>, options?: StripeRequestOptions) => Promise<T>
|
|
47
|
+
}
|
|
48
|
+
export declare interface TerminalAddress {
|
|
49
|
+
line1: string
|
|
50
|
+
line2?: string | null
|
|
51
|
+
city?: string | null
|
|
52
|
+
postal_code?: string | null
|
|
53
|
+
state?: string | null
|
|
54
|
+
country: string
|
|
55
|
+
}
|
|
56
|
+
export declare interface TerminalLocation {
|
|
57
|
+
id: string
|
|
58
|
+
object: 'terminal.location'
|
|
59
|
+
display_name: string
|
|
60
|
+
address: TerminalAddress
|
|
61
|
+
metadata: Record<string, string>
|
|
62
|
+
livemode: boolean
|
|
63
|
+
}
|
|
64
|
+
export declare interface ReaderAction {
|
|
65
|
+
type: 'process_payment_intent' | 'process_setup_intent' | 'refund_payment' | 'set_reader_display' | string
|
|
66
|
+
status: 'in_progress' | 'succeeded' | 'failed'
|
|
67
|
+
failure_code: string | null
|
|
68
|
+
failure_message: string | null
|
|
69
|
+
process_payment_intent?: { payment_intent: string } | null
|
|
70
|
+
}
|
|
71
|
+
export declare interface TerminalReader {
|
|
72
|
+
id: string
|
|
73
|
+
object: 'terminal.reader'
|
|
74
|
+
label: string
|
|
75
|
+
device_type: string
|
|
76
|
+
serial_number: string
|
|
77
|
+
status: 'online' | 'offline' | null
|
|
78
|
+
location: string | null
|
|
79
|
+
ip_address: string | null
|
|
80
|
+
device_sw_version: string | null
|
|
81
|
+
action: ReaderAction | null
|
|
82
|
+
metadata: Record<string, string>
|
|
83
|
+
livemode: boolean
|
|
84
|
+
}
|
|
85
|
+
export declare interface CardPresentPaymentIntentInput {
|
|
86
|
+
amount: number
|
|
87
|
+
currency: string
|
|
88
|
+
description?: string
|
|
89
|
+
metadata?: Record<string, string>
|
|
90
|
+
onBehalfOf?: string
|
|
91
|
+
transferDestination?: string
|
|
92
|
+
captureMethod?: 'automatic' | 'manual'
|
|
93
|
+
statementDescriptorSuffix?: string
|
|
94
|
+
}
|
|
95
|
+
export declare interface StripeTerminal {
|
|
96
|
+
connectionToken: (location?: string) => Promise<{ object: 'terminal.connection_token', secret: string, location?: string }>
|
|
97
|
+
createLocation: (input: { displayName: string, address: TerminalAddress, metadata?: Record<string, string> }, idempotencyKey?: string) => Promise<TerminalLocation>
|
|
98
|
+
retrieveLocation: (id: string) => Promise<TerminalLocation>
|
|
99
|
+
registerReader: (input: { registrationCode: string, label?: string, location: string, metadata?: Record<string, string> }) => Promise<TerminalReader>
|
|
100
|
+
retrieveReader: (id: string) => Promise<TerminalReader>
|
|
101
|
+
listReaders: (filter?: { location?: string, limit?: number }) => Promise<TerminalReader[]>
|
|
102
|
+
deleteReader: (id: string) => Promise<{ id: string, deleted: boolean }>
|
|
103
|
+
createPaymentIntent: (input: CardPresentPaymentIntentInput, idempotencyKey: string) => Promise<any>
|
|
104
|
+
retrievePaymentIntent: (id: string) => Promise<any>
|
|
105
|
+
cancelPaymentIntent: (id: string) => Promise<any>
|
|
106
|
+
processPaymentIntent: (readerId: string, paymentIntentId: string, idempotencyKey?: string) => Promise<TerminalReader>
|
|
107
|
+
cancelReaderAction: (readerId: string) => Promise<TerminalReader>
|
|
108
|
+
presentPaymentMethod: (readerId: string, card?: { number?: string, type?: 'card_present' | 'interac_present' }) => Promise<TerminalReader>
|
|
109
|
+
refund: (input: { paymentIntent: string, amount?: number, reverseTransfer?: boolean, metadata?: Record<string, string>, reason?: 'duplicate' | 'fraudulent' | 'requested_by_customer' }, idempotencyKey: string) => Promise<any>
|
|
110
|
+
}
|
|
111
|
+
export declare interface TerminalChargeOutcome {
|
|
112
|
+
state: TerminalChargeState
|
|
113
|
+
failureCode: string | null
|
|
114
|
+
failureMessage: string | null
|
|
115
|
+
}
|
|
116
|
+
/** What a card receipt prints about the card, from a card-present charge. */
|
|
117
|
+
export declare interface CardPresentDetails {
|
|
118
|
+
brand: string | null
|
|
119
|
+
last4: string | null
|
|
120
|
+
funding: string | null
|
|
121
|
+
readMethod: string | null
|
|
122
|
+
applicationName: string | null
|
|
123
|
+
applicationId: string | null
|
|
124
|
+
authorizationCode: string | null
|
|
125
|
+
authorizationResponseCode: string | null
|
|
126
|
+
accountType: string | null
|
|
127
|
+
cardholderVerification: string | null
|
|
128
|
+
}
|
|
129
|
+
export type FormValue = string | number | boolean | null | undefined | FormValue[] | { [key: string]: FormValue }
|
|
130
|
+
/** Where a card-present charge stands, from the PaymentIntent and the reader that has it. */
|
|
131
|
+
export type TerminalChargeState = 'waiting' | 'succeeded' | 'failed' | 'canceled';
|
|
132
|
+
/**
|
|
133
|
+
* Card-present payments through Stripe Terminal, the server-driven way: the
|
|
134
|
+
* till asks the server to charge, the server tells a smart reader (WisePOS E,
|
|
135
|
+
* S700, or a simulated one) to collect the card, and the till asks the server
|
|
136
|
+
* how it went. Also `@stacksjs/payments/terminal`.
|
|
137
|
+
*
|
|
138
|
+
* - A Terminal **location** groups readers (one per shop or gym location).
|
|
139
|
+
* - A **reader** is registered with the code it shows on screen and a label.
|
|
140
|
+
* - A charge is a PaymentIntent with `payment_method_types: ['card_present']`,
|
|
141
|
+
* handed to a reader with `process_payment_intent`. The reader's `action`
|
|
142
|
+
* says whether it is waiting for the card, done, or failed (a decline, a
|
|
143
|
+
* timeout); `cancel_action` takes the charge off its screen.
|
|
144
|
+
* - A refund to the card is an ordinary refund of that PaymentIntent (not
|
|
145
|
+
* Interac, which needs the card back at the reader).
|
|
146
|
+
* - A connection token is for the client SDKs (Tap to Pay, Bluetooth readers).
|
|
147
|
+
*
|
|
148
|
+
* The client talks to Stripe's REST API directly (form-encoded requests, JSON
|
|
149
|
+
* answers, `Idempotency-Key` on every create) instead of through the `stripe`
|
|
150
|
+
* SDK: it needs no optional dependency, and `apiBase` lets a test point it at
|
|
151
|
+
* an HTTP double of Stripe. The pure helpers (`terminalChargeState`,
|
|
152
|
+
* `cardPresentDetails`) read Stripe's objects without any client.
|
|
153
|
+
*/
|
|
154
|
+
/** Stripe's error object (`{ error: { type, code, decline_code, message, param } }`). */
|
|
155
|
+
export declare class StripeApiError extends Error {
|
|
156
|
+
readonly type: string;
|
|
157
|
+
readonly code: string | null;
|
|
158
|
+
readonly declineCode: string | null;
|
|
159
|
+
readonly param: string | null;
|
|
160
|
+
readonly status: number;
|
|
161
|
+
constructor(message: string, status: number, error?: { type?: string, code?: string, decline_code?: string, param?: string });
|
|
162
|
+
get definitive(): boolean;
|
|
163
|
+
}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class StripeApiError extends Error{status;type;code;declineCode;param;constructor(message,status,error={}){super(message);this.status=status;this.name="StripeApiError";this.type=error.type||(status===0?"api_connection_error":"api_error");this.code=error.code??null;this.declineCode=error.decline_code??null;this.param=error.param??null}get definitive(){return["invalid_request_error","card_error","authentication_error","permission_error"].includes(this.type)}}export const STRIPE_TERMINAL_API_VERSION="2026-09-30.endive";export function stripeFormEncode(params){const pairs=[],add=(key,value)=>{if(value===null||value===void 0)return;if(Array.isArray(value)){value.forEach((item,index)=>add(`${key}[${index}]`,item));return}if(typeof value==="object"){for(const[child,inner]of Object.entries(value))add(`${key}[${child}]`,inner);return}pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)};for(const[key,value]of Object.entries(params))add(key,value);return pairs.join("&")}export function stripeRest(options){const base=(options.apiBase||"https://api.stripe.com").replace(/\/$/,""),doFetch=options.fetch??fetch;return{async request(method,path,params={},requestOptions={}){const body=stripeFormEncode(params),url=method==="POST"||!body?`${base}${path}`:`${base}${path}?${body}`,headers={Authorization:`Bearer ${options.secretKey}`,Accept:"application/json"};if(method==="POST")headers["Content-Type"]="application/x-www-form-urlencoded";const version=options.apiVersion===void 0?STRIPE_TERMINAL_API_VERSION:options.apiVersion;if(version)headers["Stripe-Version"]=version;if(options.stripeAccount)headers["Stripe-Account"]=options.stripeAccount;if(requestOptions.idempotencyKey)headers["Idempotency-Key"]=requestOptions.idempotencyKey;let res;try{res=await doFetch(url,{method,headers,body:method==="POST"?body:void 0,signal:AbortSignal.timeout(options.timeoutMs??30000)})}catch(error){throw new StripeApiError(`Could not reach Stripe: ${error instanceof Error?error.message:String(error)}`,0)}const text=await res.text();let json=null;try{json=text?JSON.parse(text):null}catch{json=null}if(!res.ok){const error=json?.error&&typeof json.error==="object"?json.error:{};throw new StripeApiError(typeof error.message==="string"&&error.message?error.message:`Stripe answered ${res.status}`,res.status,error)}return json}}}const path=(value)=>encodeURIComponent(value);export function stripeTerminal(options){const api=stripeRest(options);return{connectionToken:(location)=>api.request("POST","/v1/terminal/connection_tokens",{location}),createLocation:(input,idempotencyKey)=>api.request("POST","/v1/terminal/locations",{display_name:input.displayName,address:{...input.address},metadata:input.metadata},{idempotencyKey}),retrieveLocation:(id)=>api.request("GET",`/v1/terminal/locations/${path(id)}`),registerReader:(input)=>api.request("POST","/v1/terminal/readers",{registration_code:input.registrationCode,label:input.label,location:input.location,metadata:input.metadata}),retrieveReader:(id)=>api.request("GET",`/v1/terminal/readers/${path(id)}`),async listReaders(filter={}){const page=await api.request("GET","/v1/terminal/readers",{location:filter.location,limit:filter.limit??100});return Array.isArray(page?.data)?page.data:[]},deleteReader:(id)=>api.request("DELETE",`/v1/terminal/readers/${path(id)}`),createPaymentIntent:(input,idempotencyKey)=>api.request("POST","/v1/payment_intents",{amount:input.amount,currency:input.currency.toLowerCase(),allowed_payment_method_types:["card_present"],capture_method:input.captureMethod??"automatic",description:input.description,metadata:input.metadata,on_behalf_of:input.onBehalfOf,transfer_data:input.transferDestination?{destination:input.transferDestination}:void 0,statement_descriptor_suffix:input.statementDescriptorSuffix},{idempotencyKey}),retrievePaymentIntent:(id)=>api.request("GET",`/v1/payment_intents/${path(id)}`,{expand:["latest_charge"]}),cancelPaymentIntent:(id)=>api.request("POST",`/v1/payment_intents/${path(id)}/cancel`),processPaymentIntent:(readerId,paymentIntentId,idempotencyKey)=>api.request("POST",`/v1/terminal/readers/${path(readerId)}/process_payment_intent`,{payment_intent:paymentIntentId},{idempotencyKey}),cancelReaderAction:(readerId)=>api.request("POST",`/v1/terminal/readers/${path(readerId)}/cancel_action`),presentPaymentMethod:(readerId,card={})=>api.request("POST",`/v1/test_helpers/terminal/readers/${path(readerId)}/present_payment_method`,{type:card.type,card_present:card.number?{number:card.number}:void 0}),refund:(input,idempotencyKey)=>api.request("POST","/v1/refunds",{payment_intent:input.paymentIntent,amount:input.amount,reverse_transfer:input.reverseTransfer,reason:input.reason,metadata:input.metadata},{idempotencyKey})}}export function terminalChargeState(paymentIntent,reader){const status=paymentIntent?.status;if(status==="succeeded"||status==="requires_capture")return{state:"succeeded",failureCode:null,failureMessage:null};if(status==="canceled")return{state:"canceled",failureCode:null,failureMessage:null};const action=reader?.action,forThis=!!action&&action.type==="process_payment_intent"&&(!paymentIntent?.id||action.process_payment_intent?.payment_intent===paymentIntent.id);if(forThis&&action.status==="failed")return{state:"failed",failureCode:action.failure_code||"reader_action_failed",failureMessage:action.failure_message||null};if(forThis&&action.status==="in_progress")return{state:"waiting",failureCode:null,failureMessage:null};const error=paymentIntent?.last_payment_error;if(status==="requires_payment_method"&&error)return{state:"failed",failureCode:error.decline_code||error.code||"card_declined",failureMessage:error.message||null};return{state:"waiting",failureCode:null,failureMessage:null}}export function cardPresentDetails(chargeOrIntent){const charge=chargeOrIntent?.object==="payment_intent"?typeof chargeOrIntent.latest_charge==="object"&&chargeOrIntent.latest_charge?chargeOrIntent.latest_charge:chargeOrIntent.charges?.data?.[0]:chargeOrIntent,card=charge?.payment_method_details?.card_present??charge?.payment_method_details?.interac_present;if(!card)return null;const receipt=card.receipt??{},text=(value)=>typeof value==="string"&&value?value:null;return{brand:text(card.brand),last4:text(card.last4),funding:text(card.funding),readMethod:text(card.read_method),applicationName:text(receipt.application_preferred_name),applicationId:text(receipt.dedicated_file_name),authorizationCode:text(charge.authorization_code??receipt.authorization_code),authorizationResponseCode:text(receipt.authorization_response_code),accountType:text(receipt.account_type),cardholderVerification:text(receipt.cardholder_verification_method)}}export function latestChargeId(paymentIntent){const charge=paymentIntent?.latest_charge;if(typeof charge==="string"&&charge)return charge;if(charge&&typeof charge.id==="string")return charge.id;const legacy=paymentIntent?.charges?.data?.[0]?.id;return typeof legacy==="string"?legacy:null}export const SIMULATED_READER_CODES=["simulated-wpe","simulated-s700","simulated-s710","simulated-v660p","simulated-wp3"];export function isTestKey(secretKey){return typeof secretKey==="string"&&/^(sk|rk)_test_/.test(secretKey)}
|
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.170",
|
|
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.170",
|
|
64
|
+
"@stacksjs/database": "0.75.170",
|
|
65
|
+
"@stacksjs/error-handling": "0.75.170",
|
|
66
|
+
"@stacksjs/logging": "0.75.170",
|
|
67
|
+
"@stacksjs/orm": "0.75.170",
|
|
68
|
+
"@stacksjs/types": "0.75.170"
|
|
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.170",
|
|
84
84
|
"@stripe/stripe-js": "^10.0.0",
|
|
85
85
|
"better-dx": "^0.2.24",
|
|
86
86
|
"stripe": "23.0.0"
|