@@ -37,11 +37,6 @@ def _log_updated(self, updated: List[Owner]) -> None:
3737 )
3838
3939 def invoice_payment_succeeded (self , invoice : stripe .Invoice ) -> None :
40- """
41- Stripe invoice.payment_succeeded is called when an invoice is paid. This happens
42- when an initial checkout session is completed (first upgrade from free to paid) or
43- upon a recurring schedule for the subscription (e.g., monthly or annually)
44- """
4540 log .info (
4641 "Invoice Payment Succeeded - Setting delinquency status False" ,
4742 extra = dict (
@@ -90,35 +85,24 @@ def invoice_payment_succeeded(self, invoice: stripe.Invoice) -> None:
9085
9186 def invoice_payment_failed (self , invoice : stripe .Invoice ) -> None :
9287 """
93- Stripe invoice.payment_failed is called when an invoice is not paid. This happens
94- when a recurring schedule for the subscription (e.g., monthly or annually) fails to pay.
95- Or when the initial checkout session fails to pay .
88+ Stripe invoice.payment_failed webhook event is emitted when an invoice payment fails
89+ (initial or recurring). Note that delayed payment methods (including ACH with
90+ microdeposits) may have a failed initial invoice until the account is verified .
9691 """
97- if invoice .status == "open" :
98- if invoice .default_payment_method is None :
99- # check if customer has any pending payment methods
100- unverified_payment_methods = get_unverified_payment_methods (
101- self , invoice .customer
102- )
103- if unverified_payment_methods :
92+ if invoice .default_payment_method is None :
93+ if invoice .payment_intent :
94+ payment_intent = stripe .PaymentIntent .retrieve (invoice .payment_intent )
95+ if payment_intent .status == "requires_action" :
10496 log .info (
105- "Invoice payment failed but customer has pending payment methods " ,
97+ "Invoice payment failed but still awaiting known customer action, skipping Delinquency actions " ,
10698 extra = dict (
10799 stripe_customer_id = invoice .customer ,
108100 stripe_subscription_id = invoice .subscription ,
109- pending_payment_methods = len (unverified_payment_methods ),
101+ payment_intent_status = payment_intent .status ,
102+ next_action = payment_intent .next_action ,
110103 ),
111104 )
112105 return
113- # reach here because ach is still pending
114- log .info (
115- "Invoice payment failed but requires action - skipping delinquency" ,
116- extra = dict (
117- stripe_customer_id = invoice .customer ,
118- stripe_subscription_id = invoice .subscription ,
119- ),
120- )
121- return
122106
123107 log .info (
124108 "Invoice Payment Failed - Setting Delinquency status True" ,
@@ -176,9 +160,21 @@ def invoice_payment_failed(self, invoice: stripe.Invoice) -> None:
176160
177161 def customer_subscription_deleted (self , subscription : stripe .Subscription ) -> None :
178162 """
179- Stripe customer.subscription.deleted is called when a subscription is deleted.
180- This happens when an org goes from paid to free.
163+ Stripe customer.subscription.deleted webhook event is emitted when a subscription is deleted.
164+ This happens when an org goes from paid to free (see payment_service.delete_subscription)
165+ or when cleaning up an incomplete subscription that never activated (e.g., abandoned async
166+ ACH microdeposits verification).
181167 """
168+ if subscription .status == "incomplete" :
169+ log .info (
170+ "Customer Subscription Deleted - Ignoring incomplete subscription" ,
171+ extra = dict (
172+ stripe_subscription_id = subscription .id ,
173+ stripe_customer_id = subscription .customer ,
174+ ),
175+ )
176+ return
177+
182178 log .info (
183179 "Customer Subscription Deleted - Setting free plan and deactivating repos for stripe customer" ,
184180 extra = dict (
@@ -224,7 +220,6 @@ def subscription_schedule_created(
224220 ),
225221 )
226222
227- # handler for Stripe event subscription_schedule.updated
228223 def subscription_schedule_updated (
229224 self , schedule : stripe .SubscriptionSchedule
230225 ) -> None :
@@ -249,7 +244,6 @@ def subscription_schedule_updated(
249244 ),
250245 )
251246
252- # handler for Stripe event subscription_schedule.released
253247 def subscription_schedule_released (
254248 self , schedule : stripe .SubscriptionSchedule
255249 ) -> None :
@@ -289,24 +283,17 @@ def subscription_schedule_released(
289283 )
290284
291285 def customer_created (self , customer : stripe .Customer ) -> None :
292- """
293- Stripe customer.created is called when a customer is created.
294- This happens when an owner completes a CheckoutSession for the first time.
295- """
296286 # Based on what stripe doesn't gives us (an ownerid!)
297287 # in this event we cannot reliably create a customer,
298288 # so we're just logging that we created the event and
299289 # relying on customer.subscription.created to handle sub creation
300290 log .info ("Customer created" , extra = dict (stripe_customer_id = customer .id ))
301291
302- # handler for Stripe event customer.subscription.created
303292 def customer_subscription_created (self , subscription : stripe .Subscription ) -> None :
304- log .info (
305- "Customer subscription created" ,
306- extra = dict (
307- customer_id = subscription ["customer" ], subscription_id = subscription ["id" ]
308- ),
309- )
293+ """
294+ Stripe customer.subscription.created webhook event is emitted when a subscription is created.
295+ This happens when an owner completes a CheckoutSession for a new subscription.
296+ """
310297 sub_item_plan_id = subscription .plan .id
311298
312299 if not sub_item_plan_id :
@@ -349,24 +336,15 @@ def customer_subscription_created(self, subscription: stripe.Subscription) -> No
349336 owner .stripe_customer_id = subscription .customer
350337 owner .save ()
351338
352- # check if the subscription has a pending_update attribute, if so, don't upgrade the plan yet
353- print ("subscription what are you" , subscription )
354- # Check if subscription has a default payment method
355- has_default_payment = subscription .default_payment_method is not None
356-
357- # If no default payment, check for any pending verification methods
358- if not has_default_payment :
359- payment_methods = get_unverified_payment_methods (subscription .customer )
360- if payment_methods :
361- log .info (
362- "Subscription has pending payment verification" ,
363- extra = dict (
364- subscription_id = subscription .id ,
365- customer_id = subscription .customer ,
366- payment_methods = payment_methods ,
367- ),
368- )
369- return
339+ if self ._has_unverified_initial_payment_method (subscription ):
340+ log .info (
341+ "Subscription has pending initial payment verification - will upgrade plan after initial invoice payment" ,
342+ extra = dict (
343+ subscription_id = subscription .id ,
344+ customer_id = subscription .customer ,
345+ ),
346+ )
347+ return
370348
371349 plan_service = PlanService (current_org = owner )
372350 plan_service .expire_trial_when_upgrading ()
@@ -385,15 +363,30 @@ def customer_subscription_created(self, subscription: stripe.Subscription) -> No
385363
386364 self ._log_updated ([owner ])
387365
388- # handler for Stripe event customer.subscription.updated
389- def customer_subscription_updated (self , subscription : stripe .Subscription ) -> None :
390- log .info (
391- "Customer subscription updated" ,
392- extra = dict (
393- customer_id = subscription ["customer" ], subscription_id = subscription ["id" ]
394- ),
395- )
366+ def _has_unverified_initial_payment_method (
367+ self , subscription : stripe .Subscription
368+ ) -> bool :
369+ """
370+ Helper method to check if a subscription's latest invoice has a payment intent
371+ that requires verification (e.g. ACH microdeposits)
372+ """
373+ latest_invoice = stripe .Invoice .retrieve (subscription .latest_invoice )
374+ if latest_invoice and latest_invoice .payment_intent :
375+ payment_intent = stripe .PaymentIntent .retrieve (
376+ latest_invoice .payment_intent
377+ )
378+ return (
379+ payment_intent is not None
380+ and payment_intent .status == "requires_action"
381+ )
382+ return False
396383
384+ def customer_subscription_updated (self , subscription : stripe .Subscription ) -> None :
385+ """
386+ Stripe customer.subscription.updated webhook event is emitted when a subscription is updated.
387+ This can happen when an owner updates the subscription's default payment method using our
388+ update_payment_method api
389+ """
397390 owners : QuerySet [Owner ] = Owner .objects .filter (
398391 stripe_subscription_id = subscription .id ,
399392 stripe_customer_id = subscription .customer ,
@@ -409,24 +402,15 @@ def customer_subscription_updated(self, subscription: stripe.Subscription) -> No
409402 )
410403 return
411404
412- # check if the subscription has a pending_update attribute, if so, don't upgrade the plan yet
413- print ("subscription what are you" , subscription )
414- # Check if subscription has a default payment method
415- has_default_payment = subscription .default_payment_method is not None
416-
417- # If no default payment, check for any pending verification methods
418- if not has_default_payment :
419- payment_methods = get_unverified_payment_methods (subscription .customer )
420- if payment_methods :
421- log .info (
422- "Subscription has pending payment verification" ,
423- extra = dict (
424- subscription_id = subscription .id ,
425- customer_id = subscription .customer ,
426- payment_methods = payment_methods ,
427- ),
428- )
429- return
405+ if self ._has_unverified_initial_payment_method (subscription ):
406+ log .info (
407+ "Subscription has pending initial payment verification - will upgrade plan after initial invoice payment" ,
408+ extra = dict (
409+ subscription_id = subscription .id ,
410+ customer_id = subscription .customer ,
411+ ),
412+ )
413+ return
430414
431415 indication_of_payment_failure = getattr (subscription , "pending_update" , None )
432416 if indication_of_payment_failure :
@@ -442,6 +426,7 @@ def customer_subscription_updated(self, subscription: stripe.Subscription) -> No
442426 ),
443427 )
444428 return
429+
445430 # Properly attach the payment method on the customer
446431 # This hook will be called after a checkout session completes,
447432 # updating the subscription created with it
@@ -507,7 +492,6 @@ def customer_subscription_updated(self, subscription: stripe.Subscription) -> No
507492 ),
508493 )
509494
510- # handler for Stripe event customer.updated
511495 def customer_updated (self , customer : stripe .Customer ) -> None :
512496 new_default_payment_method = customer ["invoice_settings" ][
513497 "default_payment_method"
@@ -529,7 +513,6 @@ def customer_updated(self, customer: stripe.Customer) -> None:
529513 subscription ["id" ], default_payment_method = new_default_payment_method
530514 )
531515
532- # handler for Stripe event checkout.session.completed
533516 def checkout_session_completed (
534517 self , checkout_session : stripe .checkout .Session
535518 ) -> None :
@@ -550,12 +533,21 @@ def checkout_session_completed(
550533 def _check_and_handle_delayed_notification_payment_methods (
551534 self , customer_id : str , payment_method_id : str
552535 ):
536+ """
537+ Helper method to handle payment methods that require delayed verification (like ACH).
538+ When verification succeeds, this attaches the payment method to the customer and sets
539+ it as the default payment method for both the customer and subscription.
540+ """
553541 owner = Owner .objects .get (stripe_customer_id = customer_id )
554542 payment_method = stripe .PaymentMethod .retrieve (payment_method_id )
555543
556- if payment_method .type == "us_bank_account" and hasattr (
544+ is_us_bank_account = payment_method .type == "us_bank_account" and hasattr (
557545 payment_method , "us_bank_account"
558- ):
546+ )
547+
548+ should_set_as_default = is_us_bank_account
549+
550+ if should_set_as_default :
559551 # attach the payment method + set as default on the invoice and subscription
560552 stripe .PaymentMethod .attach (
561553 payment_method , customer = owner .stripe_customer_id
@@ -570,13 +562,16 @@ def _check_and_handle_delayed_notification_payment_methods(
570562
571563 def payment_intent_succeeded (self , payment_intent : stripe .PaymentIntent ) -> None :
572564 """
573- Stripe payment intent is used for the initial checkout session.
574- Success is emitted when the payment intent goes to a success state.
565+ Stripe payment_intent.succeeded webhook event is emitted when a
566+ payment intent goes to a success state.
567+ We create a Stripe PaymentIntent for the initial checkout session.
575568 """
576569 log .info (
577570 "Payment intent succeeded" ,
578571 extra = dict (
579- payment_method_id = payment_intent .id ,
572+ stripe_customer_id = payment_intent .customer ,
573+ payment_intent_id = payment_intent .id ,
574+ payment_method_type = payment_intent .payment_method ,
580575 ),
581576 )
582577
@@ -586,12 +581,17 @@ def payment_intent_succeeded(self, payment_intent: stripe.PaymentIntent) -> None
586581
587582 def setup_intent_succeeded (self , setup_intent : stripe .SetupIntent ) -> None :
588583 """
589- Stripe setup intent is used for subsequent edits to payment methods.
590- See our createSetupIntent api which is called from the UI Stripe Payment Element
584+ Stripe setup_intent.succeeded webhook event is emitted when a setup intent
585+ goes to a success state. We create a Stripe SetupIntent for the gazebo UI
586+ PaymentElement to modify payment methods.
591587 """
592588 log .info (
593589 "Setup intent succeeded" ,
594- extra = dict (setup_intent_id = setup_intent .id ),
590+ extra = dict (
591+ stripe_customer_id = setup_intent .customer ,
592+ setup_intent_id = setup_intent .id ,
593+ payment_method_type = setup_intent .payment_method ,
594+ ),
595595 )
596596
597597 self ._check_and_handle_delayed_notification_payment_methods (
@@ -629,41 +629,3 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Response:
629629 getattr (self , self .event .type .replace ("." , "_" ))(self .event .data .object )
630630
631631 return Response (status = status .HTTP_204_NO_CONTENT )
632-
633-
634- # TODO - move this
635- def get_unverified_payment_methods (self , stripe_customer_id : str ):
636-
637- unverified_payment_methods = []
638-
639- # Check payment intents
640- payment_intents = stripe .PaymentIntent .list (customer = stripe_customer_id , limit = 100 )
641- for intent in payment_intents .data :
642- if (
643- hasattr (intent , "next_action" )
644- and intent .next_action
645- and intent .next_action .type == "verify_with_microdeposits"
646- ):
647- unverified_payment_methods .append (
648- {
649- "payment_method_id" : intent .payment_method ,
650- "hosted_verification_link" : intent .next_action .verify_with_microdeposits .hosted_verification_url ,
651- }
652- )
653-
654- # Check setup intents
655- setup_intents = stripe .SetupIntent .list (customer = stripe_customer_id , limit = 100 )
656- for intent in setup_intents .data :
657- if (
658- hasattr (intent , "next_action" )
659- and intent .next_action
660- and intent .next_action .type == "verify_with_microdeposits"
661- ):
662- unverified_payment_methods .append (
663- {
664- "payment_method_id" : intent .payment_method ,
665- "hosted_verification_link" : intent .next_action .verify_with_microdeposits .hosted_verification_url ,
666- }
667- )
668-
669- return unverified_payment_methods
0 commit comments