@@ -5,9 +5,26 @@ import dotenv from 'dotenv'
5
5
6
6
import Stripe from 'stripe'
7
7
import { fileURLToPath } from 'node:url'
8
+ import { createCustomerStore , customerTableProps } from '@web3-storage/w3infra-billing/tables/customer.js'
9
+ import { createTable } from './helpers/resources.js'
10
+ import { createDynamoDB } from '@web3-storage/w3infra-billing/test/helpers/aws.js'
11
+ import { stripeIDToAccountID } from '@web3-storage/w3infra-billing/utils/stripe.js'
8
12
9
13
dotenv . config ( { path : fileURLToPath ( new URL ( '../../.env' , import . meta. url ) ) } )
10
14
15
+ /**
16
+ * @typedef {object } BillingContext
17
+ * @property {import('@web3-storage/w3infra-billing/lib/api.js').CustomerStore } BillingContext.customerStore
18
+ * @property {Stripe } BillingContext.stripe
19
+ * @property {import('../types.js').BillingProvider } BillingContext.billingProvider
20
+ */
21
+
22
+ const customerDID = /** @type {import('@web3-storage/did-mailto').DidMailto } */ (
23
+ `did:mailto:example.com:w3up-billing-test-${ Date . now ( ) } `
24
+ )
25
+ const email = toEmail ( customerDID )
26
+ const initialPlan = 'did:web:starter.web3.storage'
27
+
11
28
/**
12
29
*
13
30
* @param {Stripe } stripe
@@ -27,10 +44,20 @@ async function getCustomerPlanByEmail(stripe, email) {
27
44
*
28
45
* @param {Stripe } stripe
29
46
* @param {string } email
47
+ * @param {import('@web3-storage/w3infra-billing/lib/api.js').CustomerStore } customerStore
30
48
* @returns {Promise<Stripe.Customer> }
31
49
*/
32
- async function setupCustomer ( stripe , email ) {
50
+ async function setupCustomer ( stripe , email , customerStore ) {
33
51
const customer = await stripe . customers . create ( { email } )
52
+ const customerCreation = await customerStore . put ( {
53
+ customer : customerDID ,
54
+ account : stripeIDToAccountID ( customer . id ) ,
55
+ product : initialPlan ,
56
+ insertedAt : new Date ( )
57
+ } )
58
+ if ( ! customerCreation . ok ) {
59
+ throw customerCreation . error
60
+ }
34
61
35
62
// set up a payment method - otherwise we won't be able to update the plan later
36
63
let setupIntent = await stripe . setupIntents . create ( {
@@ -45,52 +72,83 @@ async function setupCustomer(stripe, email) {
45
72
)
46
73
const paymentMethod = /** @type {string } */ ( setupIntent . payment_method )
47
74
await stripe . customers . update ( customer . id , { invoice_settings : { default_payment_method : paymentMethod } } )
75
+ // create a subscription to initialPlan
76
+ const prices = await stripe . prices . list ( { lookup_keys : [ initialPlan ] } )
77
+ const initialPriceID = prices . data . find ( price => price . lookup_key === initialPlan ) ?. id
78
+ if ( ! initialPriceID ) {
79
+ throw new Error ( `could not find priceID ${ initialPlan } in Stripe` )
80
+ }
81
+ await stripe . subscriptions . create ( { customer : customer . id , items : [ { price : initialPriceID } ] } )
48
82
return customer
49
83
}
50
84
51
- test ( 'stripe plan can be updated' , async ( t ) => {
85
+ /**
86
+ *
87
+ * @param {BillingContext } context
88
+ * @param {(c: BillingContext) => Promise<void> } testFn
89
+ */
90
+ async function withCustomer ( context , testFn ) {
91
+ const { stripe, customerStore } = context
92
+ let customer
93
+ try {
94
+ // create a new customer and set up its subscription with "initialPlan"
95
+ customer = await setupCustomer ( stripe , email , customerStore )
96
+ await testFn ( context )
97
+ } finally {
98
+ if ( customer ) {
99
+ // clean up the user we created
100
+ await stripe . customers . del ( customer . id )
101
+ }
102
+ }
103
+ }
104
+
105
+ test . before ( async t => {
52
106
const stripeSecretKey = process . env . STRIPE_TEST_SECRET_KEY
53
- if ( stripeSecretKey ) {
54
- const stripe = new Stripe ( stripeSecretKey , { apiVersion : '2023-10-16' } )
55
- const billingProvider = createStripeBillingProvider ( stripe )
56
- const customerDID = /** @type {import('@web3-storage/did-mailto').DidMailto } */ (
57
- `did:mailto:example.com:w3up-billing-test-${ Date . now ( ) } `
58
- )
59
- const email = toEmail ( customerDID )
60
-
61
- const initialPlan = 'did:web:starter.web3.storage'
62
- const updatedPlan = 'did:web:lite.web3.storage'
63
107
64
- const prices = await stripe . prices . list ( { lookup_keys : [ initialPlan ] } )
65
- const initialPriceID = prices . data . find ( price => price . lookup_key === initialPlan ) ?. id
66
- if ( ! initialPriceID ) {
67
- t . fail ( `could not find Stripe price with lookup_key ${ initialPlan } ` )
68
- }
69
- let customer
70
- try {
71
- // create a new customer and set up its subscription with "initialPlan"
72
- customer = await setupCustomer ( stripe , email )
73
-
74
- // create a subscription to initialPlan
75
- await stripe . subscriptions . create ( { customer : customer . id , items : [ { price : initialPriceID } ] } )
76
-
77
- // use the stripe API to verify plan has been initialized correctly
78
- const initialStripePlan = await getCustomerPlanByEmail ( stripe , email )
79
- t . deepEqual ( initialPlan , initialStripePlan )
80
-
81
- // this is the actual code under test!
82
- await billingProvider . setPlan ( customerDID , updatedPlan )
83
-
84
- // use the stripe API to verify plan has been updated
85
- const updatedStripePlan = await getCustomerPlanByEmail ( stripe , email )
86
- t . deepEqual ( updatedPlan , updatedStripePlan )
87
- } finally {
88
- if ( customer ) {
89
- // clean up the user we created
90
- await stripe . customers . del ( customer . id )
91
- }
92
- }
93
- } else {
94
- t . fail ( 'STRIPE_TEST_SECRET_KEY environment variable is not set' )
108
+ if ( ! stripeSecretKey ) {
109
+ throw new Error ( 'STRIPE_TEST_SECRET_KEY environment variable is not set' )
95
110
}
111
+ const { client : dynamo } = await createDynamoDB ( )
112
+
113
+ const stripe = new Stripe ( stripeSecretKey , { apiVersion : '2023-10-16' } )
114
+
115
+ const customerStore = createCustomerStore ( dynamo , { tableName : await createTable ( dynamo , customerTableProps ) } )
116
+ const billingProvider = createStripeBillingProvider ( stripe , customerStore )
117
+
118
+ Object . assign ( t . context , {
119
+ dynamo,
120
+ customerStore,
121
+ stripe,
122
+ billingProvider
123
+ } )
124
+ } )
125
+
126
+ test ( 'stripe plan can be updated' , async ( t ) => {
127
+ const context = /** @type {typeof t.context & BillingContext } */ ( t . context )
128
+ const { stripe, billingProvider } = context
129
+
130
+ await withCustomer ( context , async ( ) => {
131
+ // use the stripe API to verify plan has been initialized correctly
132
+ const initialStripePlan = await getCustomerPlanByEmail ( stripe , email )
133
+ t . deepEqual ( initialPlan , initialStripePlan )
134
+
135
+ // this is the actual code under test!
136
+ const updatedPlan = 'did:web:lite.web3.storage'
137
+ await billingProvider . setPlan ( customerDID , updatedPlan )
138
+
139
+ // use the stripe API to verify plan has been updated
140
+ const updatedStripePlan = await getCustomerPlanByEmail ( stripe , email )
141
+ t . deepEqual ( updatedPlan , updatedStripePlan )
142
+ } )
96
143
} )
144
+
145
+ test ( 'stripe billing session can be generated' , async ( t ) => {
146
+ const context = /** @type {typeof t.context & BillingContext } */ ( t . context )
147
+ const { billingProvider } = context
148
+
149
+ await withCustomer ( context , async ( ) => {
150
+ const response = await billingProvider . createAdminSession ( customerDID , 'https://example.com/return-url' )
151
+ t . assert ( response . ok )
152
+ t . assert ( response . ok ?. url )
153
+ } )
154
+ } )
0 commit comments