@stacksjs/payments 0.75.366 → 0.75.367
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.
|
@@ -10,7 +10,7 @@ import type Stripe from 'stripe';
|
|
|
10
10
|
export declare function collectDirectInvoice(invoiceId: string, account: string, options: DirectInvoiceCollectionOptions, client?: Pick<Stripe, 'invoices' | 'invoicePayments' | 'paymentMethods' | 'paymentIntents'>): Promise<DirectInvoiceCollection>;
|
|
11
11
|
export declare interface DirectInvoiceCollectionOptions extends DirectInvoiceFeeOptions {
|
|
12
12
|
approvedTotal: number
|
|
13
|
-
paymentMethod
|
|
13
|
+
paymentMethod?: string
|
|
14
14
|
}
|
|
15
15
|
export declare interface DirectInvoiceCollection {
|
|
16
16
|
invoice: Stripe.Invoice
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{stripe}from"../drivers/stripe";import{stacksIdempotencyKey}from"../idempotency";import{asAccount}from"./direct";import{splitFee}from"./destination";import{prepareDirectInvoiceFee}from"./invoice-fee";import{allPages}from"./pagination";import{manageDestinationSubscription}from"./subscription";import{invoiceTaxAmounts}from"./tax";function id(value){return typeof value==="string"?value:value?.id}export async function collectDirectInvoice(invoiceId,account,options,client=stripe){const request=asAccount(account);if(!/^in_[a-z0-9_]+$/i.test(invoiceId)||!/^cus_[a-z0-9_]+$/i.test(options.customer)||options.subscription!=null&&!/^sub_[a-z0-9_]+$/i.test(options.subscription)||!/^[a-z]{3}$/i.test(options.currency)
|
|
1
|
+
import{stripe}from"../drivers/stripe";import{stacksIdempotencyKey}from"../idempotency";import{asAccount}from"./direct";import{splitFee}from"./destination";import{prepareDirectInvoiceFee}from"./invoice-fee";import{allPages}from"./pagination";import{manageDestinationSubscription}from"./subscription";import{invoiceTaxAmounts}from"./tax";function id(value){return typeof value==="string"?value:value?.id}export async function collectDirectInvoice(invoiceId,account,options,client=stripe){const request=asAccount(account);if(!/^in_[a-z0-9_]+$/i.test(invoiceId)||!/^cus_[a-z0-9_]+$/i.test(options.customer)||options.subscription!=null&&!/^sub_[a-z0-9_]+$/i.test(options.subscription)||!/^[a-z]{3}$/i.test(options.currency)||options.paymentMethod!==void 0&&!/^pm_[a-z0-9_]+$/i.test(options.paymentMethod)||!Number.isSafeInteger(options.approvedTotal)||options.approvedTotal<0)throw Error("[payments/connect] Expected original invoice, customer, subscription, currency, saved method and approved total");if(options.platformFeePercent==null)throw Error("[payments/connect] An explicit invoice fee percentage is required");manageDestinationSubscription.feePercentFor({platformFeePercent:options.platformFeePercent});const verify=(value)=>{if(value.id!==invoiceId||!["draft","open","paid"].includes(value.status||"")||id(value.customer)!==options.customer||id(value.parent?.subscription_details?.subscription)!==options.subscription||value.currency.toUpperCase()!==options.currency.toUpperCase()||value.amount_due!==options.approvedTotal)throw Error("[payments/connect] Collection requires the original collectible invoice and approved amount");const totals=invoiceTaxAmounts(value);if(totals.gross!==options.approvedTotal)throw Error("[payments/connect] Invoice total differs from the buyer-approved amount");return totals};let invoice=await client.invoices.retrieve(invoiceId,{},request);const basis=verify(invoice),bps=Math.round(options.platformFeePercent*100),fee=splitFee(basis.net,{platform:bps,merchant:1e4-bps},"merchant").platform,stable=(value)=>{const totals=verify(value);if(totals.net!==basis.net||totals.tax!==basis.tax)throw Error("[payments/connect] Invoice tax changed during collection; payment was not authorized")};if(invoice.status!=="paid"&&invoice.auto_advance!==!1){invoice=await client.invoices.update(invoiceId,{auto_advance:!1},{...request,idempotencyKey:stacksIdempotencyKey("connect.invoice.hold",account,invoiceId)});stable(invoice)}if(invoice.status!=="paid"&&basis.gross>0&&options.paymentMethod){const method=await client.paymentMethods.retrieve(options.paymentMethod,{},request);if(method.id!==options.paymentMethod||id(method.customer)!==options.customer)throw Error("[payments/connect] Saved payment method belongs to another customer")}if(invoice.status==="draft"){const prepared=await prepareDirectInvoiceFee(invoiceId,account,options,client);stable(prepared.invoice);invoice=await client.invoices.finalizeInvoice(invoiceId,{auto_advance:!1},{...request,idempotencyKey:stacksIdempotencyKey("connect.invoice.finalize",account,invoiceId,basis.gross,basis.tax,fee)});stable(invoice)}const result=(status,intent)=>({invoice,status,...intent?{paymentIntent:intent}:{},...status==="requires_payment"&&invoice.hosted_invoice_url?{paymentUrl:invoice.hosted_invoice_url}:{},currency:invoice.currency.toUpperCase(),...basis,fee});if(basis.gross===0){if(invoice.status!=="paid")throw Error("[payments/connect] Zero invoice has not been marked paid by the provider");return result("paid")}const candidates=(await allPages((after)=>client.invoicePayments.list({invoice:invoiceId,limit:100,...after?{starting_after:after}:{}},request))).filter((payment)=>payment.status!=="canceled"&&payment.payment.type==="payment_intent");if(candidates.length!==1||id(candidates[0].invoice)!==invoiceId||!id(candidates[0].payment.payment_intent))throw Error("[payments/connect] Expected one canonical invoice payment intent");const intentId=id(candidates[0].payment.payment_intent),readIntent=async()=>{const intent=await client.paymentIntents.retrieve(intentId,{},request);if(intent.id!==intentId||id(intent.customer)!==options.customer||intent.currency.toUpperCase()!==options.currency.toUpperCase()||intent.amount!==basis.gross||(intent.application_fee_amount??0)!==fee||intent.status==="canceled")throw Error("[payments/connect] Invoice payment does not match its original customer, currency, amount or after-tax fee");return intent};let intent=await readIntent();if(invoice.status==="paid"){if(intent.status!=="succeeded")throw Error("[payments/connect] Paid invoice has no successful original payment");return result("paid",intent)}if(intent.status==="processing"||intent.status==="succeeded")return result("processing",intent);if(intent.status==="requires_action")return result("requires_payment",intent);if(!["requires_confirmation","requires_payment_method"].includes(intent.status))throw Error("[payments/connect] Invoice payment is not ready for automatic collection");if(!options.paymentMethod)return result("requires_payment",intent);try{invoice=await client.invoices.pay(invoiceId,{payment_method:options.paymentMethod,off_session:!0},{...request,idempotencyKey:stacksIdempotencyKey("connect.invoice.pay",account,invoiceId,intentId,options.paymentMethod,basis.gross,basis.tax,fee)});stable(invoice);intent=await readIntent();if(invoice.status==="paid"&&intent.status!=="succeeded")throw Error("[payments/connect] Paid invoice has no successful original payment");return result(invoice.status==="paid"?"paid":["processing","succeeded"].includes(intent.status)?"processing":"requires_payment",intent)}catch(error){if(error?.type!=="StripeCardError")throw error;invoice=await client.invoices.retrieve(invoiceId,{},request);stable(invoice);intent=await readIntent();if(invoice.status==="paid"&&intent.status!=="succeeded")throw Error("[payments/connect] Paid invoice has no successful original payment");return result(invoice.status==="paid"?"paid":"requires_payment",intent)}}
|
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.367",
|
|
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.367",
|
|
64
|
+
"@stacksjs/database": "0.75.367",
|
|
65
|
+
"@stacksjs/error-handling": "0.75.367",
|
|
66
|
+
"@stacksjs/logging": "0.75.367",
|
|
67
|
+
"@stacksjs/orm": "0.75.367",
|
|
68
|
+
"@stacksjs/types": "0.75.367"
|
|
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.367",
|
|
84
84
|
"@stripe/stripe-js": "^10.0.0",
|
|
85
85
|
"better-dx": "^0.2.24",
|
|
86
86
|
"stripe": "23.0.0"
|