Coverage for app/backend/src/tests/test_donations.py: 100%

323 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-03 11:56 +0000

1import hashlib 

2import hmac 

3import json 

4import time 

5from unittest.mock import patch 

6 

7import grpc 

8import pytest 

9import stripe 

10from google.protobuf import empty_pb2 

11from sqlalchemy import select 

12 

13from couchers.config import config 

14from couchers.db import session_scope 

15from couchers.jobs.handlers import update_badges 

16from couchers.models import DonationInitiation, DonationType, Invoice, InvoiceType, User, UserBadge 

17from couchers.proto import donations_pb2 

18from couchers.proto.google.api import httpbody_pb2 

19from tests.fixtures.db import generate_user 

20from tests.fixtures.sessions import donations_session, real_stripe_session 

21 

22 

23@pytest.fixture 

24def stripe_config() -> None: 

25 # An autouse fixture runs before non-autouse fixtures of the same scope. 

26 config.STRIPE_API_KEY = "dummy_api_key" 

27 config.STRIPE_WEBHOOK_SECRET = "dummy_webhook_secret" 

28 config.STRIPE_RECURRING_PRODUCT_ID = "price_1KIbmbIfR5z29g5kFWPEUnC6" 

29 config.STRIPE_YEARLY_RECURRING_PRODUCT_ID = "price_1UFQSGIfR5z29g5koIy21lYR" 

30 config.MERCH_SHOP_URL = "https://shop.couchershq.org" 

31 

32 

33def test_donations_disabled(db, feature_flags): 

34 feature_flags.set("donations_enabled", False) 

35 _, token = generate_user() 

36 

37 with donations_session(token) as donations: 

38 with pytest.raises(grpc.RpcError) as e: 

39 donations.InitiateDonation(donations_pb2.InitiateDonationReq(amount=100)) 

40 assert e.value.code() == grpc.StatusCode.UNAVAILABLE 

41 assert e.value.details() == "Donations are currently disabled." 

42 

43 

44def test_one_time_donation_flow(db, stripe_config): 

45 user, token = generate_user() 

46 user_email = user.email 

47 user_id = user.id 

48 

49 ## User first makes a req to Donations.InitiateDonation 

50 with donations_session(token) as donations: 

51 with patch("couchers.servicers.donations.stripe") as mock: 

52 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4uq0gT0rDZWN"}) 

53 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), one_time_STRIPE_SESSION) 

54 

55 res = donations.InitiateDonation( 

56 donations_pb2.InitiateDonationReq( 

57 amount=100, 

58 frequency=donations_pb2.DONATION_FREQUENCY_ONE_TIME, 

59 source="test-one-time", 

60 ) 

61 ) 

62 

63 mock.Customer.create.assert_called_once_with( 

64 email=user_email, metadata={"user_id": user_id}, api_key="dummy_api_key" 

65 ) 

66 

67 with session_scope() as session: 

68 donation_initiation_id = session.execute(select(DonationInitiation.id)).scalar_one() 

69 

70 mock.checkout.Session.create.assert_called_once_with( 

71 client_reference_id=str(user_id), 

72 submit_type="donate", 

73 customer="cus_Pv4uq0gT0rDZWN", 

74 success_url="http://localhost:3000/donate?success=true", 

75 cancel_url="http://localhost:3000/donate?cancelled=true", 

76 payment_method_types=["card"], 

77 mode="payment", 

78 line_items=[ 

79 { 

80 "price_data": { 

81 "currency": "usd", 

82 "unit_amount": 10000, 

83 "product_data": { 

84 "name": "Couchers financial supporter (one-time)", 

85 "images": ["https://couchers.org/img/share.jpg"], 

86 }, 

87 }, 

88 "quantity": 1, 

89 } 

90 ], 

91 api_key="dummy_api_key", 

92 payment_intent_data={"metadata": {"donation_initiation_id": str(donation_initiation_id)}}, 

93 subscription_data=None, 

94 ) 

95 

96 ## Stripe then makes some webhooks requests 

97 

98 # evt_1P5EkZIfR5z29g5kRH0e4NVx:customer.created 

99 fire_stripe_event("evt_1P5EkZIfR5z29g5kRH0e4NVx") 

100 

101 # evt_3P5El3IfR5z29g5k0TLWlfHq:charge.succeeded, captured before we tagged payments with their initiation 

102 event = json.loads(STRIPE_WEBHOOK_EVENTS["evt_3P5El3IfR5z29g5k0TLWlfHq"]) 

103 event["data"]["object"]["metadata"]["donation_initiation_id"] = str(donation_initiation_id) 

104 fire_stripe_event_data(event) 

105 

106 # evt_1P5El5IfR5z29g5kNedLGqCz:checkout.session.completed 

107 fire_stripe_event("evt_1P5El5IfR5z29g5kNedLGqCz") 

108 

109 # evt_3P5El3IfR5z29g5k0tueVWGH:payment_intent.succeeded 

110 fire_stripe_event("evt_3P5El3IfR5z29g5k0tueVWGH") 

111 

112 # evt_3P5El3IfR5z29g5k0mFVJ2V7:payment_intent.created 

113 fire_stripe_event("evt_3P5El3IfR5z29g5k0mFVJ2V7") 

114 

115 ## Now finally check everything was added to the DB 

116 with session_scope() as session: 

117 donation = session.execute(select(DonationInitiation)).scalar_one() 

118 assert donation.user_id == user_id 

119 assert donation.amount == 100 

120 assert ( 

121 donation.stripe_checkout_session_id == "cs_test_a12ftevGwzCAa236NeLPq6yRAdMt0V2S1gGjFcxfsY4xT4tiREPvbr5lhG" 

122 ) 

123 assert donation.donation_type == DonationType.one_time 

124 assert donation.source == "test-one-time" 

125 

126 invoice = session.execute(select(Invoice)).scalar_one() 

127 assert invoice.user_id == user_id 

128 assert invoice.amount == 100 

129 assert invoice.stripe_payment_intent_id == "pi_3P5El3IfR5z29g5k0N5TNa7R" 

130 assert invoice.invoice_type == InvoiceType.on_platform 

131 assert invoice.donation_initiation_id == donation.id 

132 assert invoice.donation_type == DonationType.one_time 

133 assert ( 

134 invoice.stripe_receipt_url 

135 == "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xS0V6QnlJZlI1ejI5ZzVrKIqF7LAGMgbtNxpJulk6LBaePNy_2Q2RzXJsbk7t1jLwK26AQlG05P-4EPhG7AIIcqsQLgC09iDJ2srs" 

136 ) 

137 

138 # check they get a badge 

139 update_badges(empty_pb2.Empty()) 

140 with session_scope() as session: 

141 assert ( 

142 session.execute( 

143 select(UserBadge.user_id).where(UserBadge.user_id == user_id, UserBadge.badge_id == "donor") 

144 ).scalar_one() 

145 == user_id 

146 ) 

147 

148 

149def test_monthly_donation_flow(db, stripe_config): 

150 user, token = generate_user() 

151 user_email = user.email 

152 user_id = user.id 

153 

154 config.STRIPE_RECURRING_PRODUCT_ID = "price_1IRoHdE5kUmYuPWz9tX8UpRv" 

155 

156 ## User first makes a req to Donations.InitiateDonation 

157 with donations_session(token) as donations: 

158 with patch("couchers.servicers.donations.stripe") as mock: 

159 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4w8dxBpTVUsQ"}) 

160 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), RECURRING_STRIPE_SESSION) 

161 

162 res = donations.InitiateDonation( 

163 donations_pb2.InitiateDonationReq( 

164 amount=25, 

165 frequency=donations_pb2.DONATION_FREQUENCY_MONTHLY, 

166 source="test-monthly", 

167 ) 

168 ) 

169 

170 mock.Customer.create.assert_called_once_with( 

171 email=user_email, metadata={"user_id": user_id}, api_key="dummy_api_key" 

172 ) 

173 

174 with session_scope() as session: 

175 donation_initiation_id = session.execute(select(DonationInitiation.id)).scalar_one() 

176 

177 mock.checkout.Session.create.assert_called_once_with( 

178 client_reference_id=str(user_id), 

179 customer="cus_Pv4w8dxBpTVUsQ", 

180 submit_type=None, 

181 success_url="http://localhost:3000/donate?success=true", 

182 cancel_url="http://localhost:3000/donate?cancelled=true", 

183 payment_method_types=["card"], 

184 mode="subscription", 

185 line_items=[ 

186 { 

187 "price": "price_1IRoHdE5kUmYuPWz9tX8UpRv", 

188 "quantity": 25, 

189 } 

190 ], 

191 api_key="dummy_api_key", 

192 payment_intent_data=None, 

193 subscription_data={"metadata": {"donation_initiation_id": str(donation_initiation_id)}}, 

194 ) 

195 

196 ## Stripe then makes some webhooks requests 

197 

198 # evt_1P5EmWIfR5z29g5kdOMc8bxr: customer.created 

199 fire_stripe_event("evt_1P5EmWIfR5z29g5kdOMc8bxr") 

200 

201 # evt_3P5EmzIfR5z29g5k0bA1H9Vg: charge.succeeded 

202 with mock_subscription_invoice({"donation_initiation_id": str(donation_initiation_id)}) as mock_retrieve: 

203 fire_stripe_event("evt_3P5EmzIfR5z29g5k0bA1H9Vg") 

204 mock_retrieve.assert_called_once_with("in_1P5EmzIfR5z29g5kNwA5fIXq", api_key="dummy_api_key") 

205 

206 # evt_1P5En1IfR5z29g5k89Az23Na: payment_method.attached 

207 fire_stripe_event("evt_1P5En1IfR5z29g5k89Az23Na") 

208 

209 # evt_1P5En1IfR5z29g5kh279xFFB: customer.updated 

210 fire_stripe_event("evt_1P5En1IfR5z29g5kh279xFFB") 

211 

212 # evt_1P5En2IfR5z29g5khvLimtc3: customer.subscription.created 

213 fire_stripe_event("evt_1P5En2IfR5z29g5khvLimtc3") 

214 

215 # evt_1P5En2IfR5z29g5kQ3f7d9C6: customer.subscription.updated 

216 fire_stripe_event("evt_1P5En2IfR5z29g5kQ3f7d9C6") 

217 

218 # evt_3P5EmzIfR5z29g5k0taFsMsl: payment_intent.succeeded 

219 fire_stripe_event("evt_3P5EmzIfR5z29g5k0taFsMsl") 

220 

221 # evt_3P5EmzIfR5z29g5k0bxxQl9f: payment_intent.created 

222 fire_stripe_event("evt_3P5EmzIfR5z29g5k0bxxQl9f") 

223 

224 # evt_1P5En2IfR5z29g5kQZBnH8bR: invoice.created 

225 fire_stripe_event("evt_1P5En2IfR5z29g5kQZBnH8bR") 

226 

227 # evt_1P5En2IfR5z29g5kGrG7cxm7: invoice.finalized 

228 fire_stripe_event("evt_1P5En2IfR5z29g5kGrG7cxm7") 

229 

230 # evt_1P5En3IfR5z29g5kRKYnPpgc: invoice.updated 

231 fire_stripe_event("evt_1P5En3IfR5z29g5kRKYnPpgc") 

232 

233 # evt_1P5En3IfR5z29g5kXDrXUkQa: invoice.paid 

234 fire_stripe_event("evt_1P5En3IfR5z29g5kXDrXUkQa") 

235 

236 # evt_1P5En3IfR5z29g5k3ElqoMso: invoice.payment_succeeded 

237 fire_stripe_event("evt_1P5En3IfR5z29g5k3ElqoMso") 

238 

239 # evt_1P5En3IfR5z29g5kOoaABPf4: checkout.session.completed 

240 fire_stripe_event("evt_1P5En3IfR5z29g5kOoaABPf4") 

241 

242 ## Now finally check everything was added to the DB 

243 with session_scope() as session: 

244 donation = session.execute(select(DonationInitiation)).scalar_one() 

245 assert donation.user_id == user_id 

246 assert donation.amount == 25 

247 assert ( 

248 donation.stripe_checkout_session_id == "cs_test_a1JoMu1FbksL058ob6T6AC1byYR2DCXVRwi0ybLSZKwINYe868OQr25qaC" 

249 ) 

250 assert donation.donation_type == DonationType.monthly 

251 assert donation.source == "test-monthly" 

252 

253 invoice = session.execute(select(Invoice)).scalar_one() 

254 assert invoice.user_id == user_id 

255 assert invoice.amount == 25 

256 assert invoice.stripe_payment_intent_id == "pi_3P5EmzIfR5z29g5k0uVvI3kX" 

257 assert invoice.invoice_type == InvoiceType.on_platform 

258 assert invoice.donation_initiation_id == donation.id 

259 assert invoice.donation_type == DonationType.monthly 

260 assert ( 

261 invoice.stripe_receipt_url 

262 == "https://pay.stripe.com/receipts/invoices/CAcaFwoVYWNjdF8xS0V6QnlJZlI1ejI5ZzVrKIOG7LAGMgYTBIebo2c6LBYFs4BgdV7T4S5nHXQyHt4uh5azZ3_ss_S2wi27m52wbg4yQAoirZ9eBhbH?s=ap" 

263 ) 

264 

265 # check they get a badge 

266 update_badges(empty_pb2.Empty()) 

267 with session_scope() as session: 

268 assert ( 

269 session.execute( 

270 select(UserBadge.user_id).where(UserBadge.user_id == user_id, UserBadge.badge_id == "donor") 

271 ).scalar_one() 

272 == user_id 

273 ) 

274 

275 

276def test_yearly_donation_flow(db, stripe_config): 

277 user, token = generate_user() 

278 user_id = user.id 

279 

280 with donations_session(token) as donations: 

281 with patch("couchers.servicers.donations.stripe") as mock: 

282 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4w8dxBpTVUsQ"}) 

283 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), RECURRING_STRIPE_SESSION) 

284 

285 donations.InitiateDonation( 

286 donations_pb2.InitiateDonationReq( 

287 amount=250, 

288 frequency=donations_pb2.DONATION_FREQUENCY_YEARLY, 

289 source="test-yearly", 

290 ) 

291 ) 

292 

293 with session_scope() as session: 

294 donation_initiation_id = session.execute(select(DonationInitiation.id)).scalar_one() 

295 

296 mock.checkout.Session.create.assert_called_once_with( 

297 client_reference_id=str(user_id), 

298 customer="cus_Pv4w8dxBpTVUsQ", 

299 submit_type=None, 

300 success_url="http://localhost:3000/donate?success=true", 

301 cancel_url="http://localhost:3000/donate?cancelled=true", 

302 payment_method_types=["card"], 

303 mode="subscription", 

304 line_items=[ 

305 { 

306 "price": "price_1UFQSGIfR5z29g5koIy21lYR", 

307 "quantity": 250, 

308 } 

309 ], 

310 api_key="dummy_api_key", 

311 payment_intent_data=None, 

312 subscription_data={"metadata": {"donation_initiation_id": str(donation_initiation_id)}}, 

313 ) 

314 

315 with session_scope() as session: 

316 donation = session.execute(select(DonationInitiation)).scalar_one() 

317 assert donation.user_id == user_id 

318 assert donation.amount == 250 

319 assert donation.donation_type == DonationType.yearly 

320 assert donation.source == "test-yearly" 

321 

322 

323def test_unspecified_donation_frequency(db, stripe_config): 

324 _, token = generate_user() 

325 

326 with donations_session(token) as donations: 

327 with pytest.raises(grpc.RpcError) as e: 

328 donations.InitiateDonation(donations_pb2.InitiateDonationReq(amount=100)) 

329 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

330 assert e.value.details() == "Invalid donation frequency." 

331 

332 

333def test_unknown_donation_frequency(db, stripe_config): 

334 _, token = generate_user() 

335 

336 with donations_session(token) as donations: 

337 with pytest.raises(grpc.RpcError) as e: 

338 donations.InitiateDonation( 

339 donations_pb2.InitiateDonationReq(amount=100, frequency=donations_pb2.DonationFrequency.ValueType(42)) 

340 ) 

341 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

342 assert e.value.details() == "Invalid donation frequency." 

343 

344 

345def test_customer_portal_url(db, stripe_config): 

346 user, token = generate_user() 

347 user_email = user.email 

348 user_id = user.id 

349 

350 ## User first makes a req to Donations.InitiateDonation 

351 with donations_session(token) as donations: 

352 with patch("couchers.servicers.donations.stripe") as mock: 

353 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4uq0gT0rDZWN"}) 

354 mock.billing_portal.Session.create.return_value = type( 

355 "__MockBillingPortalSession", (), {"url": "https://stripe.com.invalid/subman"} 

356 ) 

357 

358 res = donations.GetDonationPortalLink(empty_pb2.Empty()) 

359 assert res.stripe_portal_url == "https://stripe.com.invalid/subman" 

360 

361 res = donations.GetDonationPortalLink(empty_pb2.Empty()) 

362 assert res.stripe_portal_url == "https://stripe.com.invalid/subman" 

363 

364 mock.Customer.create.assert_called_once_with( 

365 email=user_email, metadata={"user_id": user_id}, api_key="dummy_api_key" 

366 ) 

367 

368 

369def test_merch_invoice_flow(db, stripe_config): 

370 """Test that external shop purchases (e.g., from merch shop) grant swagster badge but don't update last_donated""" 

371 user, token = generate_user(email="test@couchers.org.invalid", last_donated=None) 

372 

373 ## Stripe sends a charge.succeeded webhook for a merch purchase 

374 fire_stripe_event("evt_merch_charge_succeeded") 

375 

376 ## Check that no invoice was created, last_donated was not updated, but swagster badge was granted 

377 with session_scope() as session: 

378 assert not session.execute(select(Invoice.id)).scalar_one_or_none() 

379 assert session.execute(select(User.last_donated)).scalar_one() is None 

380 # Check that swagster badge was granted 

381 badge = session.execute( 

382 select(UserBadge).where(UserBadge.user_id == user.id, UserBadge.badge_id == "swagster") 

383 ).scalar_one_or_none() 

384 assert badge is not None 

385 

386 

387def test_merch_invoice_flow_nonexistent_user(db, stripe_config): 

388 """Test that external shop purchases for non-existent users don't error and don't grant badges""" 

389 user, _ = generate_user(last_donated=None) 

390 

391 ## Stripe sends a charge.succeeded webhook for a merch purchase with a non-matching email 

392 fire_stripe_event("evt_merch_charge_succeeded") 

393 

394 ## Check that no invoice was created, last_donated was not updated, and no badge was granted 

395 with session_scope() as session: 

396 assert not session.execute(select(Invoice.id)).scalar_one_or_none() 

397 assert session.execute(select(User.last_donated)).scalar_one() is None 

398 # Check that no swagster badge was granted 

399 badge_count = session.execute(select(UserBadge).where(UserBadge.badge_id == "swagster")).all() 

400 assert len(badge_count) == 0 

401 

402 

403def test_slack_notification_on_merch_purchase(db, stripe_config): 

404 """Test that a Slack notification is sent when a merch purchase is made by a known user.""" 

405 user, _ = generate_user(email="test@couchers.org.invalid", last_donated=None) 

406 

407 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

408 fire_stripe_event("evt_merch_charge_succeeded") 

409 mock_slack.assert_called_once() 

410 call_args = mock_slack.call_args[0] 

411 assert call_args[0] == config.get("SLACK_MERCH_CHANNEL", "merch") 

412 assert "$50" in call_args[1] 

413 assert "Merch purchase" in call_args[1] 

414 assert user.name in call_args[1] 

415 

416 

417def test_slack_notification_on_merch_purchase_unknown_user(db, stripe_config): 

418 """Test that a Slack notification is sent with email when merch purchase is by an unknown user.""" 

419 generate_user(last_donated=None) 

420 

421 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

422 fire_stripe_event("evt_merch_charge_succeeded") 

423 mock_slack.assert_called_once() 

424 call_args = mock_slack.call_args[0] 

425 assert call_args[0] == config.get("SLACK_MERCH_CHANNEL", "merch") 

426 assert "$50" in call_args[1] 

427 assert "Merch purchase" in call_args[1] 

428 assert "test@couchers.org.invalid" in call_args[1] 

429 

430 

431def test_merch_purchase_with_customer_email_metadata(db, stripe_config): 

432 """Test that a customer_email in the metadata takes precedence over the emails on the charge.""" 

433 user, _ = generate_user(email="test@couchers.org.invalid", last_donated=None) 

434 

435 event = json.loads(STRIPE_WEBHOOK_EVENTS["evt_merch_charge_succeeded"]) 

436 event["data"]["object"]["metadata"]["customer_email"] = "test@couchers.org.invalid" 

437 event["data"]["object"]["billing_details"]["email"] = "someone.else@couchers.org.invalid" 

438 event["data"]["object"]["receipt_email"] = "someone.else@couchers.org.invalid" 

439 

440 fire_stripe_event_data(event) 

441 

442 with session_scope() as session: 

443 badge = session.execute( 

444 select(UserBadge).where(UserBadge.user_id == user.id, UserBadge.badge_id == "swagster") 

445 ).scalar_one_or_none() 

446 assert badge is not None 

447 

448 

449def test_merch_purchase_without_billing_email(db, stripe_config): 

450 """Test that a merch charge with no billing email doesn't error and doesn't grant badges, even if the receipt email 

451 matches a user.""" 

452 generate_user(email="test@couchers.org.invalid", last_donated=None) 

453 

454 event = json.loads(STRIPE_WEBHOOK_EVENTS["evt_merch_charge_succeeded"]) 

455 event["data"]["object"]["billing_details"]["email"] = None 

456 event["data"]["object"]["receipt_email"] = "test@couchers.org.invalid" 

457 

458 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

459 fire_stripe_event_data(event) 

460 assert "unknown email" in mock_slack.call_args[0][1] 

461 

462 with session_scope() as session: 

463 assert not session.execute(select(UserBadge).where(UserBadge.badge_id == "swagster")).all() 

464 

465 

466def test_slack_notification_on_one_time_donation(db, stripe_config): 

467 """Test that a Slack notification is sent when a one-time donation is received.""" 

468 user, token = generate_user() 

469 

470 # Initiate a one-time donation 

471 with donations_session(token) as donations: 

472 with patch("couchers.servicers.donations.stripe") as mock: 

473 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4uq0gT0rDZWN"}) 

474 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), one_time_STRIPE_SESSION) 

475 donations.InitiateDonation( 

476 donations_pb2.InitiateDonationReq(amount=100, frequency=donations_pb2.DONATION_FREQUENCY_ONE_TIME) 

477 ) 

478 

479 # Fire the charge.succeeded webhook and check Slack message 

480 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

481 # Captured Stripe test-mode event: charge.succeeded for one-time $100 donation 

482 fire_stripe_event("evt_3P5El3IfR5z29g5k0TLWlfHq") 

483 mock_slack.assert_called_once() 

484 call_args = mock_slack.call_args[0][1] 

485 assert "$100" in call_args 

486 assert "(one-time)" in call_args 

487 assert user.name in call_args 

488 

489 

490def test_slack_notification_on_monthly_donation(db, stripe_config): 

491 """Test that a Slack notification is sent when a monthly donation is received.""" 

492 user, token = generate_user() 

493 

494 config.STRIPE_RECURRING_PRODUCT_ID = "price_1IRoHdE5kUmYuPWz9tX8UpRv" 

495 

496 # Initiate a recurring donation 

497 with donations_session(token) as donations: 

498 with patch("couchers.servicers.donations.stripe") as mock: 

499 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4w8dxBpTVUsQ"}) 

500 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), RECURRING_STRIPE_SESSION) 

501 donations.InitiateDonation( 

502 donations_pb2.InitiateDonationReq(amount=25, frequency=donations_pb2.DONATION_FREQUENCY_MONTHLY) 

503 ) 

504 

505 # Fire the charge.succeeded webhook and check Slack message 

506 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

507 # Captured Stripe test-mode event: charge.succeeded for recurring $25 donation 

508 with mock_subscription_invoice({}): 

509 fire_stripe_event("evt_3P5EmzIfR5z29g5k0bA1H9Vg") 

510 mock_slack.assert_called_once() 

511 call_args = mock_slack.call_args[0][1] 

512 assert "$25" in call_args 

513 assert "(recurring)" in call_args 

514 assert user.name in call_args 

515 

516 

517def test_revenue_metric_on_donation(db, stripe_config): 

518 """A successful donation charge records revenue in cents under the 'donation' type.""" 

519 user, token = generate_user() 

520 

521 with donations_session(token) as donations: 

522 with patch("couchers.servicers.donations.stripe") as mock: 

523 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4uq0gT0rDZWN"}) 

524 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), one_time_STRIPE_SESSION) 

525 donations.InitiateDonation( 

526 donations_pb2.InitiateDonationReq(amount=100, frequency=donations_pb2.DONATION_FREQUENCY_ONE_TIME) 

527 ) 

528 

529 with patch("couchers.servicers.donations.observe_revenue") as mock_observe_revenue: 

530 # Captured Stripe test-mode event: charge.succeeded for one-time $100 donation 

531 fire_stripe_event("evt_3P5El3IfR5z29g5k0TLWlfHq") 

532 mock_observe_revenue.assert_called_once_with("donation", 10000) 

533 

534 

535def test_revenue_metric_on_merch(db, stripe_config): 

536 """A successful merch charge records revenue in cents under the 'merch' type.""" 

537 generate_user(email="test@couchers.org.invalid", last_donated=None) 

538 

539 with patch("couchers.servicers.donations.observe_revenue") as mock_observe_revenue: 

540 # Captured Stripe test-mode event: charge.succeeded for a $50 merch purchase 

541 fire_stripe_event("evt_merch_charge_succeeded") 

542 mock_observe_revenue.assert_called_once_with("merch", 5000) 

543 

544 

545def test_duplicate_charge_webhook(db, stripe_config): 

546 """Stripe may deliver the same charge.succeeded more than once, and it should only be recorded once.""" 

547 generate_user(stripe_customer_id="cus_Pv4uq0gT0rDZWN") 

548 

549 with patch("couchers.servicers.donations.send_slack_message") as mock_slack: 

550 with patch("couchers.servicers.donations.observe_revenue") as mock_observe_revenue: 

551 fire_stripe_event("evt_3P5El3IfR5z29g5k0TLWlfHq") 

552 fire_stripe_event("evt_3P5El3IfR5z29g5k0TLWlfHq") 

553 mock_slack.assert_called_once() 

554 mock_observe_revenue.assert_called_once() 

555 

556 with session_scope() as session: 

557 invoice = session.execute(select(Invoice)).scalar_one() 

558 assert invoice.stripe_payment_intent_id == "pi_3P5El3IfR5z29g5k0N5TNa7R" 

559 

560 

561def test_untagged_one_time_donation(db, stripe_config): 

562 """One-time charges from checkouts started before we tagged them still record their frequency.""" 

563 generate_user(stripe_customer_id="cus_Pv4uq0gT0rDZWN") 

564 

565 fire_stripe_event("evt_3P5El3IfR5z29g5k0TLWlfHq") 

566 

567 with session_scope() as session: 

568 invoice = session.execute(select(Invoice)).scalar_one() 

569 assert invoice.donation_initiation_id is None 

570 assert invoice.donation_type == DonationType.one_time 

571 

572 

573def test_untagged_recurring_donation(db, stripe_config): 

574 """Renewals of subscriptions started before we tagged them are recorded without an initiation or frequency.""" 

575 generate_user(stripe_customer_id="cus_Pv4w8dxBpTVUsQ") 

576 

577 with mock_subscription_invoice({}): 

578 fire_stripe_event("evt_3P5EmzIfR5z29g5k0bA1H9Vg") 

579 

580 with session_scope() as session: 

581 invoice = session.execute(select(Invoice)).scalar_one() 

582 assert invoice.donation_initiation_id is None 

583 assert invoice.donation_type is None 

584 

585 

586def test_recurring_donation_renewal(db, stripe_config): 

587 """Every renewal of a subscription is linked back to the initiation that started it.""" 

588 user, token = generate_user() 

589 

590 with donations_session(token) as donations: 

591 with patch("couchers.servicers.donations.stripe") as mock: 

592 mock.Customer.create.return_value = type("__MockCustomer", (), {"id": "cus_Pv4w8dxBpTVUsQ"}) 

593 mock.checkout.Session.create.return_value = type("__MockCheckoutSession", (), RECURRING_STRIPE_SESSION) 

594 donations.InitiateDonation( 

595 donations_pb2.InitiateDonationReq(amount=25, frequency=donations_pb2.DONATION_FREQUENCY_YEARLY) 

596 ) 

597 

598 with session_scope() as session: 

599 donation_initiation_id = session.execute(select(DonationInitiation.id)).scalar_one() 

600 

601 event = json.loads(STRIPE_WEBHOOK_EVENTS["evt_3P5EmzIfR5z29g5k0bA1H9Vg"]) 

602 with mock_subscription_invoice({"donation_initiation_id": str(donation_initiation_id)}): 

603 fire_stripe_event_data(event) 

604 event["data"]["object"]["payment_intent"] = "pi_renewal" 

605 fire_stripe_event_data(event) 

606 

607 with session_scope() as session: 

608 invoices = session.execute(select(Invoice).order_by(Invoice.id)).scalars().all() 

609 assert [invoice.stripe_payment_intent_id for invoice in invoices] == [ 

610 "pi_3P5EmzIfR5z29g5k0uVvI3kX", 

611 "pi_renewal", 

612 ] 

613 for invoice in invoices: 

614 assert invoice.donation_initiation_id == donation_initiation_id 

615 assert invoice.donation_type == DonationType.yearly 

616 

617 

618def mock_subscription_invoice(subscription_metadata): 

619 return patch.object( 

620 stripe.Invoice, 

621 "retrieve", 

622 return_value=stripe.Invoice.construct_from( 

623 { 

624 "id": "in_1P5EmzIfR5z29g5kNwA5fIXq", 

625 "object": "invoice", 

626 "parent": {"subscription_details": {"metadata": subscription_metadata}}, 

627 }, 

628 "dummy_api_key", 

629 ), 

630 ) 

631 

632 

633def fire_stripe_event(event_id): 

634 fire_stripe_event_data(json.loads(STRIPE_WEBHOOK_EVENTS[event_id])) 

635 

636 

637def fire_stripe_event_data(event): 

638 payload = json.dumps(event).encode("utf-8") 

639 timestamp = int(time.time()) 

640 signature = hmac.new(b"dummy_webhook_secret", f"{timestamp}.".encode() + payload, hashlib.sha256).hexdigest() 

641 

642 with real_stripe_session() as api: 

643 api.Webhook( 

644 httpbody_pb2.HttpBody(content_type="application/json", data=payload), 

645 metadata=(("stripe-signature", f"t={timestamp},v1={signature}"),), 

646 ) 

647 

648 

649STRIPE_WEBHOOK_EVENTS = { 

650 "evt_1P5EkZIfR5z29g5kRH0e4NVx": '{"id": "evt_1P5EkZIfR5z29g5kRH0e4NVx", "object": "event", "api_version": "2024-04-10", "created": 1713046123, "data": {"object": {"id": "cus_Pv4uq0gT0rDZWN", "object": "customer", "address": null, "balance": 0, "created": 1713046123, "currency": null, "default_source": null, "delinquent": false, "description": null, "discount": null, "email": "aapeli@couchers.org", "invoice_prefix": "1D722509", "invoice_settings": {"custom_fields": null, "default_payment_method": null, "footer": null, "rendering_options": null}, "livemode": false, "metadata": {"user_id": "1"}, "name": null, "next_invoice_sequence": 1, "phone": null, "preferred_locales": [], "shipping": null, "tax_exempt": "none", "test_clock": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_fmlaiE4CYTgEof", "idempotency_key": "8e902937-af49-4880-b3fa-6ccf88bc38ee"}, "type": "customer.created"}', 

651 "evt_1P5El5IfR5z29g5kNedLGqCz": '{"id": "evt_1P5El5IfR5z29g5kNedLGqCz", "object": "event", "api_version": "2024-04-10", "created": 1713046154, "data": {"object": {"id": "cs_test_a12ftevGwzCAa236NeLPq6yRAdMt0V2S1gGjFcxfsY4xT4tiREPvbr5lhG", "object": "checkout.session", "after_expiration": null, "allow_promotion_codes": null, "amount_subtotal": 10000, "amount_total": 10000, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_address_collection": null, "cancel_url": "http://localhost:3000/donate?cancelled=true", "client_reference_id": "1", "client_secret": null, "consent": null, "consent_collection": null, "created": 1713046124, "currency": "usd", "currency_conversion": null, "custom_fields": [], "custom_text": {"after_submit": null, "shipping_address": null, "submit": null, "terms_of_service_acceptance": null}, "customer": "cus_Pv4uq0gT0rDZWN", "customer_creation": null, "customer_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "aapeli@couchers.org", "name": "Aapeli Vuorinen", "phone": null, "tax_exempt": "none", "tax_ids": []}, "customer_email": null, "expires_at": 1713132523, "invoice": null, "invoice_creation": {"enabled": false, "invoice_data": {"account_tax_ids": null, "custom_fields": null, "description": null, "footer": null, "issuer": null, "metadata": {}, "rendering_options": null}}, "livemode": false, "locale": null, "metadata": {}, "mode": "payment", "payment_intent": "pi_3P5El3IfR5z29g5k0N5TNa7R", "payment_link": null, "payment_method_collection": "if_required", "payment_method_configuration_details": null, "payment_method_options": {"card": {"request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "payment_status": "paid", "phone_number_collection": {"enabled": false}, "recovered_from": null, "setup_intent": null, "shipping_address_collection": null, "shipping_cost": null, "shipping_details": null, "shipping_options": [], "status": "complete", "submit_type": "donate", "subscription": null, "success_url": "http://localhost:3000/donate?success=true", "total_details": {"amount_discount": 0, "amount_shipping": 0, "amount_tax": 0}, "ui_mode": "hosted", "url": null}}, "livemode": false, "pending_webhooks": 3, "request": {"id": null, "idempotency_key": null}, "type": "checkout.session.completed"}', 

652 "evt_1P5EmWIfR5z29g5kdOMc8bxr": '{"id": "evt_1P5EmWIfR5z29g5kdOMc8bxr", "object": "event", "api_version": "2024-04-10", "created": 1713046244, "data": {"object": {"id": "cus_Pv4w8dxBpTVUsQ", "object": "customer", "address": null, "balance": 0, "created": 1713046244, "currency": null, "default_source": null, "delinquent": false, "description": null, "discount": null, "email": "aapeli@couchers.org", "invoice_prefix": "D86E702F", "invoice_settings": {"custom_fields": null, "default_payment_method": null, "footer": null, "rendering_options": null}, "livemode": false, "metadata": {"user_id": "1"}, "name": null, "next_invoice_sequence": 1, "phone": null, "preferred_locales": [], "shipping": null, "tax_exempt": "none", "test_clock": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_3k3Wk50uSmpv6d", "idempotency_key": "a6fc3ba9-0dfa-4f44-a556-4150e6c54922"}, "type": "customer.created"}', 

653 "evt_1P5En1IfR5z29g5k89Az23Na": '{"id": "evt_1P5En1IfR5z29g5k89Az23Na", "object": "event", "api_version": "2024-04-10", "created": 1713046274, "data": {"object": {"id": "pm_1P5EmyIfR5z29g5kAIZoJcSv", "object": "payment_method", "billing_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "aapeli@couchers.org", "name": "Aapeli Vuorinen", "phone": null}, "card": {"brand": "visa", "checks": {"address_line1_check": null, "address_postal_code_check": "pass", "cvc_check": "pass"}, "country": "US", "display_brand": "visa", "exp_month": 12, "exp_year": 2050, "fingerprint": "2uVHwVtZ157kRjpi", "funding": "credit", "generated_from": null, "last4": "4242", "networks": {"available": ["visa"], "preferred": null}, "three_d_secure_usage": {"supported": true}, "wallet": null}, "created": 1713046272, "customer": "cus_Pv4w8dxBpTVUsQ", "livemode": false, "metadata": {}, "type": "card"}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "payment_method.attached"}', 

654 "evt_1P5En1IfR5z29g5kh279xFFB": '{"id": "evt_1P5En1IfR5z29g5kh279xFFB", "object": "event", "api_version": "2024-04-10", "created": 1713046273, "data": {"object": {"id": "cus_Pv4w8dxBpTVUsQ", "object": "customer", "address": null, "balance": 0, "created": 1713046244, "currency": "usd", "default_source": null, "delinquent": false, "description": null, "discount": null, "email": "aapeli@couchers.org", "invoice_prefix": "D86E702F", "invoice_settings": {"custom_fields": null, "default_payment_method": null, "footer": null, "rendering_options": null}, "livemode": false, "metadata": {"user_id": "1"}, "name": null, "next_invoice_sequence": 2, "phone": null, "preferred_locales": [], "shipping": null, "tax_exempt": "none", "test_clock": null}, "previous_attributes": {"currency": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "customer.updated"}', 

655 "evt_1P5En2IfR5z29g5kGrG7cxm7": '{"id": "evt_1P5En2IfR5z29g5kGrG7cxm7", "object": "event", "api_version": "2024-04-10", "created": 1713046273, "data": {"object": {"id": "in_1P5EmzIfR5z29g5kNwA5fIXq", "object": "invoice", "account_country": "US", "account_name": "Couchers, Inc.", "account_tax_ids": null, "amount_due": 2500, "amount_paid": 0, "amount_remaining": 2500, "amount_shipping": 0, "application": null, "application_fee_amount": null, "attempt_count": 0, "attempted": false, "auto_advance": false, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_reason": "subscription_create", "charge": null, "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "custom_fields": null, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_address": null, "customer_email": "aapeli@couchers.org", "customer_name": null, "customer_phone": null, "customer_shipping": null, "customer_tax_exempt": "none", "customer_tax_ids": [], "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "due_date": null, "effective_at": 1713046273, "ending_balance": 0, "footer": null, "from_invoice": null, "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik?s=ap", "invoice_pdf": "https://pay.stripe.com/invoice/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik/pdf?s=ap", "issuer": {"type": "self"}, "last_finalization_error": null, "latest_revision": null, "lines": {"object": "list", "data": [{"id": "il_1P5EmzIfR5z29g5khRFJxpUq", "object": "line_item", "amount": 2500, "amount_excluding_tax": 2500, "currency": "usd", "description": "25 \u00d7 Couchers financial supporter (Tier 2 at $1.00 / month)", "discount_amounts": [], "discountable": true, "discounts": [], "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "period": {"end": 1715638273, "start": 1713046273}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "proration": false, "proration_details": {"credited_items": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_item": "si_Pv4w0QuX2JUBXm", "tax_amounts": [], "tax_rates": [], "type": "subscription", "unit_amount_excluding_tax": "100"}], "has_more": false, "total_count": 1, "url": "/v1/invoices/in_1P5EmzIfR5z29g5kNwA5fIXq/lines"}, "livemode": false, "metadata": {}, "next_payment_attempt": null, "number": "D86E702F-0001", "on_behalf_of": null, "paid": false, "paid_out_of_band": false, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_settings": {"default_mandate": null, "payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null}, "period_end": 1713046273, "period_start": 1713046273, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "quote": null, "receipt_number": null, "rendering": null, "shipping_cost": null, "shipping_details": null, "starting_balance": 0, "statement_descriptor": null, "status": "open", "status_transitions": {"finalized_at": 1713046273, "marked_uncollectible_at": null, "paid_at": null, "voided_at": null}, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_details": {"metadata": {}}, "subtotal": 2500, "subtotal_excluding_tax": 2500, "tax": null, "test_clock": null, "total": 2500, "total_discount_amounts": [], "total_excluding_tax": 2500, "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"},"type": "invoice.finalized"}', 

656 "evt_1P5En2IfR5z29g5khvLimtc3": '{"id": "evt_1P5En2IfR5z29g5khvLimtc3", "object": "event", "api_version": "2024-04-10", "created": 1713046273, "data": {"object": {"id": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "object": "subscription", "application": null, "application_fee_percent": null, "automatic_tax": {"enabled": false, "liability": null}, "billing_cycle_anchor": 1713046273, "billing_cycle_anchor_config": null, "billing_thresholds": null, "cancel_at": null, "cancel_at_period_end": false, "canceled_at": null, "cancellation_details": {"comment": null, "feedback": null, "reason": null}, "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "current_period_end": 1715638273, "current_period_start": 1713046273, "customer": "cus_Pv4w8dxBpTVUsQ", "days_until_due": null, "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "ended_at": null, "invoice_settings": {"account_tax_ids": null, "issuer": {"type": "self"}}, "items": {"object": "list", "data": [{"id": "si_Pv4w0QuX2JUBXm", "object": "subscription_item", "billing_thresholds": null, "created": 1713046273, "discounts": [], "metadata": {}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "tax_rates": []}], "has_more": false, "total_count": 1, "url": "/v1/subscription_items?subscription=sub_1P5EmzIfR5z29g5k2nmkX0l3"}, "latest_invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "next_pending_invoice_item_invoice": null, "on_behalf_of": null, "pause_collection": null, "payment_settings": {"payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"network": null, "request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null, "save_default_payment_method": "off"}, "pending_invoice_item_interval": null, "pending_setup_intent": null, "pending_update": null, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "quantity": 25, "schedule": null, "start_date": 1713046273, "status": "incomplete", "test_clock": null, "transfer_data": null, "trial_end": null, "trial_settings": {"end_behavior": {"missing_payment_method": "create_invoice"}}, "trial_start": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "customer.subscription.created"}', 

657 "evt_1P5En2IfR5z29g5kQ3f7d9C6": '{"id": "evt_1P5En2IfR5z29g5kQ3f7d9C6", "object": "event", "api_version": "2024-04-10", "created": 1713046275, "data": {"object": {"id": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "object": "subscription", "application": null, "application_fee_percent": null, "automatic_tax": {"enabled": false, "liability": null}, "billing_cycle_anchor": 1713046273, "billing_cycle_anchor_config": null, "billing_thresholds": null, "cancel_at": null, "cancel_at_period_end": false, "canceled_at": null, "cancellation_details": {"comment": null, "feedback": null, "reason": null}, "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "current_period_end": 1715638273, "current_period_start": 1713046273, "customer": "cus_Pv4w8dxBpTVUsQ", "days_until_due": null, "default_payment_method": "pm_1P5EmyIfR5z29g5kAIZoJcSv", "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "ended_at": null, "invoice_settings": {"account_tax_ids": null, "issuer": {"type": "self"}}, "items": {"object": "list", "data": [{"id": "si_Pv4w0QuX2JUBXm", "object": "subscription_item", "billing_thresholds": null, "created": 1713046273, "discounts": [], "metadata": {}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "tax_rates": []}], "has_more": false, "total_count": 1, "url": "/v1/subscription_items?subscription=sub_1P5EmzIfR5z29g5k2nmkX0l3"}, "latest_invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "next_pending_invoice_item_invoice": null, "on_behalf_of": null, "pause_collection": null, "payment_settings": {"payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"network": null, "request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null, "save_default_payment_method": "off"}, "pending_invoice_item_interval": null, "pending_setup_intent": null, "pending_update": null, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "quantity": 25, "schedule": null, "start_date": 1713046273, "status": "active", "test_clock": null, "transfer_data": null, "trial_end": null, "trial_settings": {"end_behavior": {"missing_payment_method": "create_invoice"}}, "trial_start": null}, "previous_attributes": {"default_payment_method": null, "status": "incomplete"}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "customer.subscription.updated"}', 

658 "evt_1P5En2IfR5z29g5kQZBnH8bR": '{"id": "evt_1P5En2IfR5z29g5kQZBnH8bR", "object": "event", "api_version": "2024-04-10", "created": 1713046273, "data": {"object": {"id": "in_1P5EmzIfR5z29g5kNwA5fIXq", "object": "invoice", "account_country": "US", "account_name": "Couchers, Inc.", "account_tax_ids": null, "amount_due": 2500, "amount_paid": 0, "amount_remaining": 2500, "amount_shipping": 0, "application": null, "application_fee_amount": null, "attempt_count": 0, "attempted": false, "auto_advance": false, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_reason": "subscription_create", "charge": null, "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "custom_fields": null, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_address": null, "customer_email": "aapeli@couchers.org", "customer_name": null, "customer_phone": null, "customer_shipping": null, "customer_tax_exempt": "none", "customer_tax_ids": [], "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "due_date": null, "effective_at": 1713046273, "ending_balance": 0, "footer": null, "from_invoice": null, "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik?s=ap", "invoice_pdf": "https://pay.stripe.com/invoice/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik/pdf?s=ap", "issuer": {"type": "self"}, "last_finalization_error": null, "latest_revision": null, "lines": {"object": "list", "data": [{"id": "il_1P5EmzIfR5z29g5khRFJxpUq", "object": "line_item", "amount": 2500, "amount_excluding_tax": 2500, "currency": "usd", "description": "25 \u00d7 Couchers financial supporter (Tier 2 at $1.00 / month)", "discount_amounts": [], "discountable": true, "discounts": [], "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "period": {"end": 1715638273, "start": 1713046273}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "proration": false, "proration_details": {"credited_items": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_item": "si_Pv4w0QuX2JUBXm", "tax_amounts": [], "tax_rates": [], "type": "subscription", "unit_amount_excluding_tax": "100"}], "has_more": false, "total_count": 1, "url": "/v1/invoices/in_1P5EmzIfR5z29g5kNwA5fIXq/lines"}, "livemode": false, "metadata": {}, "next_payment_attempt": null, "number": "D86E702F-0001", "on_behalf_of": null, "paid": false, "paid_out_of_band": false, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_settings": {"default_mandate": null, "payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null}, "period_end": 1713046273, "period_start": 1713046273, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "quote": null, "receipt_number": null, "rendering": null, "shipping_cost": null, "shipping_details": null, "starting_balance": 0, "statement_descriptor": null, "status": "open", "status_transitions": {"finalized_at": 1713046273, "marked_uncollectible_at": null, "paid_at": null, "voided_at": null}, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_details": {"metadata": {}}, "subtotal": 2500, "subtotal_excluding_tax": 2500, "tax": null, "test_clock": null, "total": 2500, "total_discount_amounts": [], "total_excluding_tax": 2500, "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "invoice.created"}', 

659 "evt_1P5En3IfR5z29g5k3ElqoMso": '{"id": "evt_1P5En3IfR5z29g5k3ElqoMso", "object": "event", "api_version": "2024-04-10", "created": 1713046275, "data": {"object": {"id": "in_1P5EmzIfR5z29g5kNwA5fIXq", "object": "invoice", "account_country": "US", "account_name": "Couchers, Inc.", "account_tax_ids": null, "amount_due": 2500, "amount_paid": 2500, "amount_remaining": 0, "amount_shipping": 0, "application": null, "application_fee_amount": null, "attempt_count": 1, "attempted": true, "auto_advance": false, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_reason": "subscription_create", "charge": "ch_3P5EmzIfR5z29g5k05Mw6ZV2", "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "custom_fields": null, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_address": null, "customer_email": "aapeli@couchers.org", "customer_name": null, "customer_phone": null, "customer_shipping": null, "customer_tax_exempt": "none", "customer_tax_ids": [], "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "due_date": null, "effective_at": 1713046273, "ending_balance": 0, "footer": null, "from_invoice": null, "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Nw0200Xbr3gdqx?s=ap", "invoice_pdf": "https://pay.stripe.com/invoice/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Nw0200Xbr3gdqx/pdf?s=ap", "issuer": {"type": "self"}, "last_finalization_error": null, "latest_revision": null, "lines": {"object": "list", "data": [{"id": "il_1P5EmzIfR5z29g5khRFJxpUq", "object": "line_item", "amount": 2500, "amount_excluding_tax": 2500, "currency": "usd", "description": "25 \u00d7 Couchers financial supporter (Tier 2 at $1.00 / month)", "discount_amounts": [], "discountable": true, "discounts": [], "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "period": {"end": 1715638273, "start": 1713046273}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "proration": false, "proration_details": {"credited_items": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_item": "si_Pv4w0QuX2JUBXm", "tax_amounts": [], "tax_rates": [], "type": "subscription", "unit_amount_excluding_tax": "100"}], "has_more": false, "total_count": 1, "url": "/v1/invoices/in_1P5EmzIfR5z29g5kNwA5fIXq/lines"}, "livemode": false, "metadata": {}, "next_payment_attempt": null, "number": "D86E702F-0001", "on_behalf_of": null, "paid": true, "paid_out_of_band": false, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_settings": {"default_mandate": null, "payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null}, "period_end": 1713046273, "period_start": 1713046273, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "quote": null, "receipt_number": null, "rendering": null, "shipping_cost": null, "shipping_details": null, "starting_balance": 0, "statement_descriptor": null, "status": "paid", "status_transitions": {"finalized_at": 1713046273, "marked_uncollectible_at": null, "paid_at": 1713046275, "voided_at": null}, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_details": {"metadata": {}}, "subtotal": 2500, "subtotal_excluding_tax": 2500, "tax": null, "test_clock": null, "total": 2500, "total_discount_amounts": [], "total_excluding_tax": 2500, "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "invoice.payment_succeeded"}', 

660 "evt_1P5En3IfR5z29g5kOoaABPf4": '{"id": "evt_1P5En3IfR5z29g5kOoaABPf4", "object": "event", "api_version": "2024-04-10", "created": 1713046277, "data": {"object": {"id": "cs_test_a1JoMu1FbksL058ob6T6AC1byYR2DCXVRwi0ybLSZKwINYe868OQr25qaC", "object": "checkout.session", "after_expiration": null, "allow_promotion_codes": null, "amount_subtotal": 2500, "amount_total": 2500, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_address_collection": null, "cancel_url": "http://localhost:3000/donate?cancelled=true", "client_reference_id": "1", "client_secret": null, "consent": null, "consent_collection": null, "created": 1713046244, "currency": "usd", "currency_conversion": null, "custom_fields": [], "custom_text": {"after_submit": null, "shipping_address": null, "submit": null, "terms_of_service_acceptance": null}, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_creation": null, "customer_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "aapeli@couchers.org", "name": "Aapeli Vuorinen", "phone": null, "tax_exempt": "none", "tax_ids": []}, "customer_email": null, "expires_at": 1713132644, "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "invoice_creation": null, "livemode": false, "locale": null, "metadata": {}, "mode": "subscription", "payment_intent": null, "payment_link": null, "payment_method_collection": "always", "payment_method_configuration_details": null, "payment_method_options": {"card": {"request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "payment_status": "paid", "phone_number_collection": {"enabled": false}, "recovered_from": null, "setup_intent": null, "shipping_address_collection": null, "shipping_cost": null, "shipping_details": null, "shipping_options": [], "status": "complete", "submit_type": null, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "success_url": "http://localhost:3000/donate?success=true", "total_details": {"amount_discount": 0, "amount_shipping": 0, "amount_tax": 0}, "ui_mode": "hosted", "url": null}}, "livemode": false, "pending_webhooks": 3, "request": {"id": null, "idempotency_key": null}, "type": "checkout.session.completed"}', 

661 "evt_1P5En3IfR5z29g5kRKYnPpgc": '{"id": "evt_1P5En3IfR5z29g5kRKYnPpgc", "object": "event", "api_version": "2024-04-10", "created": 1713046275, "data": {"object": {"id": "in_1P5EmzIfR5z29g5kNwA5fIXq", "object": "invoice", "account_country": "US", "account_name": "Couchers, Inc.", "account_tax_ids": null, "amount_due": 2500, "amount_paid": 2500, "amount_remaining": 0, "amount_shipping": 0, "application": null, "application_fee_amount": null, "attempt_count": 1, "attempted": true, "auto_advance": false, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_reason": "subscription_create", "charge": "ch_3P5EmzIfR5z29g5k05Mw6ZV2", "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "custom_fields": null, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_address": null, "customer_email": "aapeli@couchers.org", "customer_name": null, "customer_phone": null, "customer_shipping": null, "customer_tax_exempt": "none", "customer_tax_ids": [], "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "due_date": null, "effective_at": 1713046273, "ending_balance": 0, "footer": null, "from_invoice": null, "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik?s=ap", "invoice_pdf": "https://pay.stripe.com/invoice/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Ng0200xNecenik/pdf?s=ap", "issuer": {"type": "self"}, "last_finalization_error": null, "latest_revision": null, "lines": {"object": "list", "data": [{"id": "il_1P5EmzIfR5z29g5khRFJxpUq", "object": "line_item", "amount": 2500, "amount_excluding_tax": 2500, "currency": "usd", "description": "25 \u00d7 Couchers financial supporter (Tier 2 at $1.00 / month)", "discount_amounts": [], "discountable": true, "discounts": [], "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "period": {"end": 1715638273, "start": 1713046273}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "proration": false, "proration_details": {"credited_items": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_item": "si_Pv4w0QuX2JUBXm", "tax_amounts": [], "tax_rates": [], "type": "subscription", "unit_amount_excluding_tax": "100"}], "has_more": false, "total_count": 1, "url": "/v1/invoices/in_1P5EmzIfR5z29g5kNwA5fIXq/lines"}, "livemode": false, "metadata": {}, "next_payment_attempt": null, "number": "D86E702F-0001", "on_behalf_of": null, "paid": true, "paid_out_of_band": false, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_settings": {"default_mandate": null, "payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null}, "period_end": 1713046273, "period_start": 1713046273, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "quote": null, "receipt_number": null, "rendering": null, "shipping_cost": null, "shipping_details": null, "starting_balance": 0, "statement_descriptor": null, "status": "paid", "status_transitions": {"finalized_at": 1713046273, "marked_uncollectible_at": null, "paid_at": 1713046275, "voided_at": null}, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_details": {"metadata": {}}, "subtotal": 2500, "subtotal_excluding_tax": 2500, "tax": null, "test_clock": null, "total": 2500, "total_discount_amounts": [], "total_excluding_tax": 2500, "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null}, "previous_attributes": {"amount_paid": 0, "amount_remaining": 2500, "attempt_count": 0, "attempted": false, "charge": null, "paid": false, "status": "open", "status_transitions": {"paid_at": null}}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "invoice.updated"}', 

662 "evt_1P5En3IfR5z29g5kXDrXUkQa": '{"id": "evt_1P5En3IfR5z29g5kXDrXUkQa", "object": "event", "api_version": "2024-04-10", "created": 1713046275, "data": {"object": {"id": "in_1P5EmzIfR5z29g5kNwA5fIXq", "object": "invoice", "account_country": "US", "account_name": "Couchers, Inc.", "account_tax_ids": null, "amount_due": 2500, "amount_paid": 2500, "amount_remaining": 0, "amount_shipping": 0, "application": null, "application_fee_amount": null, "attempt_count": 1, "attempted": true, "auto_advance": false, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_reason": "subscription_create", "charge": "ch_3P5EmzIfR5z29g5k05Mw6ZV2", "collection_method": "charge_automatically", "created": 1713046273, "currency": "usd", "custom_fields": null, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_address": null, "customer_email": "aapeli@couchers.org", "customer_name": null, "customer_phone": null, "customer_shipping": null, "customer_tax_exempt": "none", "customer_tax_ids": [], "default_payment_method": null, "default_source": null, "default_tax_rates": [], "description": null, "discount": null, "discounts": [], "due_date": null, "effective_at": 1713046273, "ending_balance": 0, "footer": null, "from_invoice": null, "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Nw0200Xbr3gdqx?s=ap", "invoice_pdf": "https://pay.stripe.com/invoice/acct_1KEzByIfR5z29g5k/test_YWNjdF8xS0V6QnlJZlI1ejI5ZzVrLF9QdjR3dzFNVDRGcHo4ZTI1Z05tMXA5WldlMld2VGE2LDEwMzU4NzA3Nw0200Xbr3gdqx/pdf?s=ap", "issuer": {"type": "self"}, "last_finalization_error": null, "latest_revision": null, "lines": {"object": "list", "data": [{"id": "il_1P5EmzIfR5z29g5khRFJxpUq", "object": "line_item", "amount": 2500, "amount_excluding_tax": 2500, "currency": "usd", "description": "25 \u00d7 Couchers financial supporter (Tier 2 at $1.00 / month)", "discount_amounts": [], "discountable": true, "discounts": [], "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "period": {"end": 1715638273, "start": 1713046273}, "plan": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "plan", "active": true, "aggregate_usage": null, "amount": null, "amount_decimal": null, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "interval": "month", "interval_count": 1, "livemode": false, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "tiers_mode": "volume", "transform_usage": null, "trial_period_days": null, "usage_type": "licensed"}, "price": {"id": "price_1KIbmbIfR5z29g5kFWPEUnC6", "object": "price", "active": true, "billing_scheme": "tiered", "created": 1642351245, "currency": "usd", "custom_unit_amount": null, "livemode": false, "lookup_key": null, "metadata": {}, "nickname": null, "product": "prod_KyYuo4dO67NKtG", "recurring": {"aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed"}, "tax_behavior": "unspecified", "tiers_mode": "volume", "transform_quantity": null, "type": "recurring", "unit_amount": null, "unit_amount_decimal": null}, "proration": false, "proration_details": {"credited_items": null}, "quantity": 25, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_item": "si_Pv4w0QuX2JUBXm", "tax_amounts": [], "tax_rates": [], "type": "subscription", "unit_amount_excluding_tax": "100"}], "has_more": false, "total_count": 1, "url": "/v1/invoices/in_1P5EmzIfR5z29g5kNwA5fIXq/lines"}, "livemode": false, "metadata": {}, "next_payment_attempt": null, "number": "D86E702F-0001", "on_behalf_of": null, "paid": true, "paid_out_of_band": false, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_settings": {"default_mandate": null, "payment_method_options": {"acss_debit": null, "bancontact": null, "card": {"request_three_d_secure": "automatic"}, "customer_balance": null, "konbini": null, "sepa_debit": null, "us_bank_account": null}, "payment_method_types": null}, "period_end": 1713046273, "period_start": 1713046273, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "quote": null, "receipt_number": null, "rendering": null, "shipping_cost": null, "shipping_details": null, "starting_balance": 0, "statement_descriptor": null, "status": "paid", "status_transitions": {"finalized_at": 1713046273, "marked_uncollectible_at": null, "paid_at": 1713046275, "voided_at": null}, "subscription": "sub_1P5EmzIfR5z29g5k2nmkX0l3", "subscription_details": {"metadata": {}}, "subtotal": 2500, "subtotal_excluding_tax": 2500, "tax": null, "test_clock": null, "total": 2500, "total_discount_amounts": [], "total_excluding_tax": 2500, "total_tax_amounts": [], "transfer_data": null, "webhooks_delivered_at": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "invoice.paid"}', 

663 "evt_3P5El3IfR5z29g5k0mFVJ2V7": '{"id": "evt_3P5El3IfR5z29g5k0mFVJ2V7", "object": "event", "api_version": "2024-04-10", "created": 1713046153, "data": {"object": {"id": "pi_3P5El3IfR5z29g5k0N5TNa7R", "object": "payment_intent", "amount": 10000, "amount_capturable": 0, "amount_details": {"tip": {}}, "amount_received": 0, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "automatic", "client_secret": "pi_3P5El3IfR5z29g5k0N5TNa7R_secret_BIdVdUUKk8zPCuttemp8Jl4uf", "confirmation_method": "automatic", "created": 1713046153, "currency": "usd", "customer": "cus_Pv4uq0gT0rDZWN", "description": null, "invoice": null, "last_payment_error": null, "latest_charge": null, "livemode": false, "metadata": {}, "next_action": null, "on_behalf_of": null, "payment_method": null, "payment_method_configuration_details": null, "payment_method_options": {"card": {"installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "processing": null, "receipt_email": "aapeli@couchers.org", "review": null, "setup_future_usage": null, "shipping": null, "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "requires_payment_method", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_kVsPqUwBoL4xen", "idempotency_key": "d3051b38-f32b-4118-b456-56897ce6b96d"}, "type": "payment_intent.created"}', 

664 "evt_3P5El3IfR5z29g5k0TLWlfHq": '{"id": "evt_3P5El3IfR5z29g5k0TLWlfHq", "object": "event", "api_version": "2024-04-10", "created": 1713046154, "data": {"object": {"id": "ch_3P5El3IfR5z29g5k03QfMZRt", "object": "charge", "amount": 10000, "amount_captured": 10000, "amount_refunded": 0, "application": null, "application_fee": null, "application_fee_amount": null, "balance_transaction": "txn_3P5El3IfR5z29g5k0Jmn0th5", "billing_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "aapeli@couchers.org", "name": "Aapeli Vuorinen", "phone": null}, "calculated_statement_descriptor": "COUCHERS.ORG", "captured": true, "created": 1713046154, "currency": "usd", "customer": "cus_Pv4uq0gT0rDZWN", "description": null, "destination": null, "dispute": null, "disputed": false, "failure_balance_transaction": null, "failure_code": null, "failure_message": null, "fraud_details": {}, "invoice": null, "livemode": false, "metadata": {}, "on_behalf_of": null, "order": null, "outcome": {"network_status": "approved_by_network", "reason": null, "risk_level": "normal", "risk_score": 31, "seller_message": "Payment complete.", "type": "authorized"}, "paid": true, "payment_intent": "pi_3P5El3IfR5z29g5k0N5TNa7R", "payment_method": "pm_1P5El3IfR5z29g5k33WlbWVC", "payment_method_details": {"card": {"amount_authorized": 10000, "brand": "visa", "checks": {"address_line1_check": null, "address_postal_code_check": "pass", "cvc_check": "pass"}, "country": "US", "exp_month": 12, "exp_year": 2050, "extended_authorization": {"status": "disabled"}, "fingerprint": "2uVHwVtZ157kRjpi", "funding": "credit", "incremental_authorization": {"status": "unavailable"}, "installments": null, "last4": "4242", "mandate": null, "multicapture": {"status": "unavailable"}, "network": "visa", "network_token": {"used": false}, "overcapture": {"maximum_amount_capturable": 10000, "status": "unavailable"}, "three_d_secure": null, "wallet": null}, "type": "card"}, "radar_options": {}, "receipt_email": "aapeli@couchers.org", "receipt_number": null, "receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xS0V6QnlJZlI1ejI5ZzVrKIqF7LAGMgbtNxpJulk6LBaePNy_2Q2RzXJsbk7t1jLwK26AQlG05P-4EPhG7AIIcqsQLgC09iDJ2srs", "refunded": false, "review": null, "shipping": null, "source": null, "source_transfer": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_kVsPqUwBoL4xen", "idempotency_key": "d3051b38-f32b-4118-b456-56897ce6b96d"}, "type": "charge.succeeded"}', 

665 "evt_3P5El3IfR5z29g5k0tueVWGH": '{"id": "evt_3P5El3IfR5z29g5k0tueVWGH", "object": "event", "api_version": "2024-04-10", "created": 1713046154, "data": {"object": {"id": "pi_3P5El3IfR5z29g5k0N5TNa7R", "object": "payment_intent", "amount": 10000, "amount_capturable": 0, "amount_details": {"tip": {}}, "amount_received": 10000, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "automatic", "client_secret": "pi_3P5El3IfR5z29g5k0N5TNa7R_secret_BIdVdUUKk8zPCuttemp8Jl4uf", "confirmation_method": "automatic", "created": 1713046153, "currency": "usd", "customer": "cus_Pv4uq0gT0rDZWN", "description": null, "invoice": null, "last_payment_error": null, "latest_charge": "ch_3P5El3IfR5z29g5k03QfMZRt", "livemode": false, "metadata": {}, "next_action": null, "on_behalf_of": null, "payment_method": "pm_1P5El3IfR5z29g5k33WlbWVC", "payment_method_configuration_details": null, "payment_method_options": {"card": {"installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "processing": null, "receipt_email": "aapeli@couchers.org", "review": null, "setup_future_usage": null, "shipping": null, "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 3, "request": {"id": "req_kVsPqUwBoL4xen", "idempotency_key": "d3051b38-f32b-4118-b456-56897ce6b96d"}, "type": "payment_intent.succeeded"}', 

666 "evt_3P5EmzIfR5z29g5k0bA1H9Vg": '{"id": "evt_3P5EmzIfR5z29g5k0bA1H9Vg", "object": "event", "api_version": "2024-04-10", "created": 1713046274, "data": {"object": {"id": "ch_3P5EmzIfR5z29g5k05Mw6ZV2", "object": "charge", "amount": 2500, "amount_captured": 2500, "amount_refunded": 0, "application": null, "application_fee": null, "application_fee_amount": null, "balance_transaction": "txn_3P5EmzIfR5z29g5k0U5iEbk1", "billing_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "aapeli@couchers.org", "name": "Aapeli Vuorinen", "phone": null}, "calculated_statement_descriptor": "COUCHERS.ORG", "captured": true, "created": 1713046274, "currency": "usd", "customer": "cus_Pv4w8dxBpTVUsQ", "description": "Subscription creation", "destination": null, "dispute": null, "disputed": false, "failure_balance_transaction": null, "failure_code": null, "failure_message": null, "fraud_details": {}, "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "livemode": false, "metadata": {}, "on_behalf_of": null, "order": null, "outcome": {"network_status": "approved_by_network", "reason": null, "risk_level": "normal", "risk_score": 44, "seller_message": "Payment complete.", "type": "authorized"}, "paid": true, "payment_intent": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "payment_method": "pm_1P5EmyIfR5z29g5kAIZoJcSv", "payment_method_details": {"card": {"amount_authorized": 2500, "brand": "visa", "checks": {"address_line1_check": null, "address_postal_code_check": "pass", "cvc_check": "pass"}, "country": "US", "exp_month": 12, "exp_year": 2050, "extended_authorization": {"status": "disabled"}, "fingerprint": "2uVHwVtZ157kRjpi", "funding": "credit", "incremental_authorization": {"status": "unavailable"}, "installments": null, "last4": "4242", "mandate": null, "multicapture": {"status": "unavailable"}, "network": "visa", "network_token": {"used": false}, "overcapture": {"maximum_amount_capturable": 2500, "status": "unavailable"}, "three_d_secure": null, "wallet": null}, "type": "card"}, "radar_options": {}, "receipt_email": "aapeli@couchers.org", "receipt_number": null, "receipt_url": "https://pay.stripe.com/receipts/invoices/CAcaFwoVYWNjdF8xS0V6QnlJZlI1ejI5ZzVrKIOG7LAGMgYTBIebo2c6LBYFs4BgdV7T4S5nHXQyHt4uh5azZ3_ss_S2wi27m52wbg4yQAoirZ9eBhbH?s=ap", "refunded": false, "review": null, "shipping": null, "source": null, "source_transfer": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "charge.succeeded"}', 

667 "evt_3P5EmzIfR5z29g5k0bxxQl9f": '{"id": "evt_3P5EmzIfR5z29g5k0bxxQl9f", "object": "event", "api_version": "2024-04-10", "created": 1713046273, "data": {"object": {"id": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "object": "payment_intent", "amount": 2500, "amount_capturable": 0, "amount_details": {"tip": {}}, "amount_received": 0, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "automatic", "client_secret": "pi_3P5EmzIfR5z29g5k0uVvI3kX_secret_mw1JYS1lig6dYcy922zoeyzkK", "confirmation_method": "automatic", "created": 1713046273, "currency": "usd", "customer": "cus_Pv4w8dxBpTVUsQ", "description": "Subscription creation", "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "last_payment_error": null, "latest_charge": null, "livemode": false, "metadata": {}, "next_action": null, "on_behalf_of": null, "payment_method": null, "payment_method_configuration_details": null, "payment_method_options": {"card": {"installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic"}, "cashapp": {}}, "payment_method_types": ["card", "cashapp"], "processing": null, "receipt_email": "aapeli@couchers.org", "review": null, "setup_future_usage": "off_session", "shipping": null, "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "requires_payment_method", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "payment_intent.created"}', 

668 "evt_3P5EmzIfR5z29g5k0taFsMsl": '{"id": "evt_3P5EmzIfR5z29g5k0taFsMsl", "object": "event", "api_version": "2024-04-10", "created": 1713046274, "data": {"object": {"id": "pi_3P5EmzIfR5z29g5k0uVvI3kX", "object": "payment_intent", "amount": 2500, "amount_capturable": 0, "amount_details": {"tip": {}}, "amount_received": 2500, "application": null, "application_fee_amount": null, "automatic_payment_methods": null, "canceled_at": null, "cancellation_reason": null, "capture_method": "automatic", "client_secret": "pi_3P5EmzIfR5z29g5k0uVvI3kX_secret_mw1JYS1lig6dYcy922zoeyzkK", "confirmation_method": "automatic", "created": 1713046273, "currency": "usd", "customer": "cus_Pv4w8dxBpTVUsQ", "description": "Subscription creation", "invoice": "in_1P5EmzIfR5z29g5kNwA5fIXq", "last_payment_error": null, "latest_charge": "ch_3P5EmzIfR5z29g5k05Mw6ZV2", "livemode": false, "metadata": {}, "next_action": null, "on_behalf_of": null, "payment_method": "pm_1P5EmyIfR5z29g5kAIZoJcSv", "payment_method_configuration_details": null, "payment_method_options": {"card": {"installments": null, "mandate_options": null, "network": null, "request_three_d_secure": "automatic", "setup_future_usage": "off_session"}, "cashapp": {}}, "payment_method_types": ["card", "cashapp"], "processing": null, "receipt_email": "aapeli@couchers.org", "review": null, "setup_future_usage": "off_session", "shipping": null, "source": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 3, "request": {"id": "req_x1PqgmCHBOPD0i", "idempotency_key": "44ec8acd-be18-4de0-af2e-715760e96725"}, "type": "payment_intent.succeeded"}', 

669 # External shop purchase event 

670 "evt_merch_charge_succeeded": '{"id": "evt_merch_charge_succeeded", "object": "event", "api_version": "2024-04-10", "created": 1713046154, "data": {"object": {"id": "ch_merch_test_12345", "object": "charge", "amount": 5000, "amount_captured": 5000, "amount_refunded": 0, "application": null, "application_fee": null, "application_fee_amount": null, "balance_transaction": "txn_merch_test", "billing_details": {"address": {"city": null, "country": "US", "line1": null, "line2": null, "postal_code": "10001", "state": null}, "email": "test@couchers.org.invalid", "name": "Test User", "phone": null}, "calculated_statement_descriptor": "COUCHERS.ORG", "captured": true, "created": 1713046154, "currency": "usd", "customer": null, "description": null, "destination": null, "dispute": null, "disputed": false, "failure_balance_transaction": null, "failure_code": null, "failure_message": null, "fraud_details": {}, "invoice": null, "livemode": false, "metadata": {"checkout_type": "adaptive_pricing_checkout", "site_url": "https://shop.couchershq.org", "payment_type": "single"}, "on_behalf_of": null, "order": null, "outcome": {"network_status": "approved_by_network", "reason": null, "risk_level": "normal", "risk_score": 31, "seller_message": "Payment complete.", "type": "authorized"}, "paid": true, "payment_intent": "pi_merch_test_12345", "payment_method": "pm_merch_test", "payment_method_details": {"card": {"amount_authorized": 5000, "brand": "visa", "checks": {"address_line1_check": null, "address_postal_code_check": "pass", "cvc_check": "pass"}, "country": "US", "exp_month": 12, "exp_year": 2050, "extended_authorization": {"status": "disabled"}, "fingerprint": "2uVHwVtZ157kRjpi", "funding": "credit", "incremental_authorization": {"status": "unavailable"}, "installments": null, "last4": "4242", "mandate": null, "multicapture": {"status": "unavailable"}, "network": "visa", "network_token": {"used": false}, "overcapture": {"maximum_amount_capturable": 5000, "status": "unavailable"}, "three_d_secure": null, "wallet": null}, "type": "card"}, "radar_options": {}, "receipt_email": "test@example.org", "receipt_number": null, "receipt_url": "https://pay.stripe.com/receipts/merch_test", "refunded": false, "review": null, "shipping": null, "source": null, "source_transfer": null, "statement_descriptor": null, "statement_descriptor_suffix": null, "status": "succeeded", "transfer_data": null, "transfer_group": null}}, "livemode": false, "pending_webhooks": 2, "request": {"id": "req_merch_test", "idempotency_key": "merch-test-key"}, "type": "charge.succeeded"}', 

671} 

672 

673one_time_STRIPE_SESSION = json.loads( 

674 '{"id": "cs_test_a12ftevGwzCAa236NeLPq6yRAdMt0V2S1gGjFcxfsY4xT4tiREPvbr5lhG", "object": "checkout.session", "after_expiration": null, "allow_promotion_codes": null, "amount_subtotal": 10000, "amount_total": 10000, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_address_collection": null, "cancel_url": "http://localhost:3000/donate?cancelled=true", "client_reference_id": "1", "client_secret": null, "consent": null, "consent_collection": null, "created": 1713046124, "currency": "usd", "currency_conversion": null, "custom_fields": [], "custom_text": {"after_submit": null, "shipping_address": null, "submit": null, "terms_of_service_acceptance": null}, "customer": "cus_Pv4uq0gT0rDZWN", "customer_creation": null, "customer_details": {"address": null, "email": "aapeli@couchers.org", "name": null, "phone": null, "tax_exempt": "none", "tax_ids": null}, "customer_email": null, "expires_at": 1713132523, "invoice": null, "invoice_creation": {"enabled": false, "invoice_data": {"account_tax_ids": null, "custom_fields": null, "description": null, "footer": null, "issuer": null, "metadata": {}, "rendering_options": null}}, "livemode": false, "locale": null, "metadata": {}, "mode": "payment", "payment_intent": null, "payment_link": null, "payment_method_collection": "if_required", "payment_method_configuration_details": null, "payment_method_options": {"card": {"request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "payment_status": "unpaid", "phone_number_collection": {"enabled": false}, "recovered_from": null, "setup_intent": null, "shipping_address_collection": null, "shipping_cost": null, "shipping_details": null, "shipping_options": [], "status": "open", "submit_type": "donate", "subscription": null, "success_url": "http://localhost:3000/donate?success=true", "total_details": {"amount_discount": 0, "amount_shipping": 0, "amount_tax": 0}, "ui_mode": "hosted", "url": "https://checkout.stripe.com/c/pay/cs_test_a12ftevGwzCAa236NeLPq6yRAdMt0V2S1gGjFcxfsY4xT4tiREPvbr5lhG#fidkdWxOYHwnPyd1blpxYHZxWjA0TkB%2FR3xMY1cwfzc8YjBua0NNSEZjVUhUd2h%2FYW1yQGlLaU9tXVNoMVE3ZE1QQEluajNiS3xEaVxxQ3xXT1J%2FU3dWb3NhSUhIf2tXTmpVNUpUMzJDaGZLNTVVTmhTcEJocycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl"}' 

675) 

676RECURRING_STRIPE_SESSION = json.loads( 

677 '{"id": "cs_test_a1JoMu1FbksL058ob6T6AC1byYR2DCXVRwi0ybLSZKwINYe868OQr25qaC", "object": "checkout.session", "after_expiration": null, "allow_promotion_codes": null, "amount_subtotal": 2500, "amount_total": 2500, "automatic_tax": {"enabled": false, "liability": null, "status": null}, "billing_address_collection": null, "cancel_url": "http://localhost:3000/donate?cancelled=true", "client_reference_id": "1", "client_secret": null, "consent": null, "consent_collection": null, "created": 1713046244, "currency": "usd", "currency_conversion": null, "custom_fields": [], "custom_text": {"after_submit": null, "shipping_address": null, "submit": null, "terms_of_service_acceptance": null}, "customer": "cus_Pv4w8dxBpTVUsQ", "customer_creation": null, "customer_details": {"address": null, "email": "aapeli@couchers.org", "name": null, "phone": null, "tax_exempt": "none", "tax_ids": null}, "customer_email": null, "expires_at": 1713132644, "invoice": null, "invoice_creation": null, "livemode": false, "locale": null, "metadata": {}, "mode": "subscription", "payment_intent": null, "payment_link": null, "payment_method_collection": "always", "payment_method_configuration_details": null, "payment_method_options": {"card": {"request_three_d_secure": "automatic"}}, "payment_method_types": ["card"], "payment_status": "unpaid", "phone_number_collection": {"enabled": false}, "recovered_from": null, "setup_intent": null, "shipping_address_collection": null, "shipping_cost": null, "shipping_details": null, "shipping_options": [], "status": "open", "submit_type": null, "subscription": null, "success_url": "http://localhost:3000/donate?success=true", "total_details": {"amount_discount": 0, "amount_shipping": 0, "amount_tax": 0}, "ui_mode": "hosted", "url": "https://checkout.stripe.com/c/pay/cs_test_a1JoMu1FbksL058ob6T6AC1byYR2DCXVRwi0ybLSZKwINYe868OQr25qaC#fid2cGd2ZndsdXFsamtQa2x0cGBrYHZ2QGtkZ2lgYSc%2FY2RpdmApJ2R1bE5gfCc%2FJ3VuWnFgdnFaMDROQH9HfExjVzB%2FNzxiMG5rQ01IRmNVSFR3aH9hbXJAaUtpT21dU2gxUTdkTVBASW5qM2JLfERpXHFDfFdPUn9Td1Zvc2FJSEh%2Fa1dOalU1SlQzMkNoZks1NVVOaFNwQmhzJyknY3dqaFZgd3Ngdyc%2FcXdwYCknaWR8anBxUXx1YCc%2FJ3Zsa2JpYFpscWBoJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"}' 

678)