22import json
33from subscribie import settings
44from subscribie .database import database # noqa
5+ from subscribie .tasks import background_task
56from flask import (
67 Blueprint ,
78 render_template ,
@@ -249,7 +250,7 @@ def update_payment_fulfillment(stripe_external_id):
249250@admin .route ("/stripe/charge" , methods = ["POST" , "GET" ])
250251@login_required
251252def stripe_create_charge ():
252- """Charge an existing subscriber x ammount immediately
253+ """Charge an existing subscriber x amount immediately
253254
254255 :param stripe_customer_id: Stripe customer id
255256 :param amount: Positive integer amount to charge in smallest currency unit
@@ -267,7 +268,7 @@ def stripe_create_charge():
267268 currency = data ["currency" ]
268269 statement_descriptor_suffix = data ["statement_descriptor_suffix" ]
269270 except Exception :
270- # Assumme form submission
271+ # Assume form submission
271272 # Get stripe customer_id from subscribers subscription -> customer reference
272273 person = Person .query .get (request .form .get ("person_id" ))
273274 stripe_subscription_id = person .subscriptions [0 ].stripe_subscription_id
@@ -334,12 +335,84 @@ def stripe_create_charge():
334335 return jsonify (paymentIntent .status )
335336
336337
338+ @background_task
339+ def do_pause_stripe_subscription_payment_collection (
340+ subscription_id , pause_collection_behavior = "keep_as_draft" , app = None
341+ ):
342+ """Pause pause_stripe_subscription payment collections via its
343+ Stripe subscription_id
344+
345+ Choices of `pause_collection_behavior` include:
346+
347+ - keep_as_draft (Subscribie default- Temporarily offer services for free, and
348+ collect payment later)
349+ - void (Temporarily offer services for free and never collect payment)
350+ - mark_uncollectible (Temporarily offer services for free and
351+ mark invoice as uncollectible)
352+ See also: https://docs.stripe.com/billing/subscriptions/pause-payment
353+ """
354+ with app .app_context ():
355+ stripe .api_key = get_stripe_secret_key ()
356+ connect_account_id = get_stripe_connect_account_id ()
357+
358+ if subscription_id is None :
359+ log .error ("subscription_id cannot be None" )
360+ return False
361+ try :
362+ stripe_subscription = stripe .Subscription .retrieve (
363+ subscription_id , stripe_account = connect_account_id
364+ )
365+ if stripe_subscription .status != "canceled" :
366+ stripe_pause = stripe .Subscription .modify (
367+ subscription_id ,
368+ stripe_account = connect_account_id ,
369+ pause_collection = {"behavior" : pause_collection_behavior },
370+ )
371+ # filtering for the pause_collection value
372+ stripe_pause_filter = stripe_pause ["pause_collection" ]["behavior" ]
373+
374+ # adding the pause_collection status to the
375+ # stripe_pause_collection column
376+ pause_collection = Subscription .query .filter_by (
377+ stripe_subscription_id = subscription_id
378+ ).first ()
379+
380+ pause_collection .stripe_pause_collection = stripe_pause_filter
381+ database .session .commit ()
382+ log .debug (f"Subscription paused ({ subscription_id } )" )
383+ else :
384+ log .debug (
385+ f"Skipping. Subscription { subscription_id } because it's canceled."
386+ )
387+ except Exception as e :
388+ msg = f"Error pausing subscription ({ subscription_id } )"
389+ log .error (f"{ msg } . { e } " )
390+ raise
391+
392+
393+ @background_task
394+ def do_pause_all_stripe_subscriptions (app = None ):
395+ # For each Subscription object, get it's Stripe subscription
396+ # object and pause it using pause_stripe_subscription.
397+ with app .app_context ():
398+ subscriptions = Subscription .query .all ()
399+ for subscription in subscriptions :
400+ log .debug (f"Attempting to pause subscription { subscription .uuid } " )
401+ stripe_subscription_id = subscription .stripe_subscription_id
402+ try :
403+ do_pause_stripe_subscription_payment_collection (
404+ stripe_subscription_id , app = current_app
405+ )
406+ except Exception as e :
407+ log .error (
408+ f"Error trying to pause subscription { subscription .uuid } . Error: { e } " # noqa: E501
409+ )
410+
411+
337412@admin .route ("/stripe/subscriptions/<subscription_id>/actions/pause" )
338413@login_required
339414def pause_stripe_subscription (subscription_id : str ):
340415 """Pause a Stripe subscription"""
341- stripe .api_key = get_stripe_secret_key ()
342- connect_account_id = get_stripe_connect_account_id ()
343416
344417 if "confirm" in request .args and request .args ["confirm" ] != "1" :
345418 return render_template (
@@ -349,31 +422,24 @@ def pause_stripe_subscription(subscription_id: str):
349422 )
350423 if "confirm" in request .args and request .args ["confirm" ] == "1" :
351424 try :
352- stripe_pause = stripe .Subscription .modify (
353- subscription_id ,
354- stripe_account = connect_account_id ,
355- pause_collection = {"behavior" : "void" },
425+ do_pause_stripe_subscription_payment_collection (
426+ subscription_id , pause_collection_behavior = "void" , app = current_app
356427 )
357- # filtering for the pause_collection value
358- stripe_pause_filter = stripe_pause ["pause_collection" ]["behavior" ]
359-
360- # adding the pause_collection status to the stripe_pause_collection column
361- pause_collection = Subscription .query .filter_by (
362- stripe_subscription_id = subscription_id
363- ).first ()
364-
365- pause_collection .stripe_pause_collection = stripe_pause_filter
366- database .session .commit ()
367-
368428 flash ("Subscription paused" )
369- except Exception as e :
370- msg = "Error pausing subscription"
429+ except Exception :
430+ msg = f "Error pausing subscription ( { subscription_id } ) "
371431 flash (msg )
372- log .error (f"{ msg } . { e } " )
373-
374432 return redirect (url_for ("admin.subscribers" ))
375433
376434
435+ @admin .route ("/stripe/subscriptions/all/actions/pause" )
436+ @login_required
437+ def pause_all_subscribers_subscriptions ():
438+ """Bulk action to pause all subscriptions in the shop"""
439+ do_pause_all_stripe_subscriptions () # Background task
440+ return """All payment collections are being paused in the background. You can move away from this page.""" # noqa: E501
441+
442+
377443@admin .route ("/stripe/subscriptions/<subscription_id>/actions/resume" )
378444@login_required
379445def resume_stripe_subscription (subscription_id ):
@@ -525,7 +591,7 @@ def edit():
525591
526592 Note plans are immutable, when a change is made to plan, its old
527593 plan is archived and a new plan is created with a new uuid. This is to
528- protect data integriry and make sure plan history is retained, via its uuid.
594+ protect data integrity and make sure plan history is retained, via its uuid.
529595 If a user needs to change a subscription, they should change to a different
530596 plan with a different uuid.
531597
@@ -1030,7 +1096,7 @@ def stripe_connect():
10301096 log .error (e )
10311097 account = None
10321098
1033- # Setup Stripe webhook endpoint if it dosent already exist
1099+ # Setup Stripe webhook endpoint if it doesn't already exist
10341100 if account :
10351101 # Attempt to Updates an existing Account Capability to accept card payments
10361102 try :
@@ -1411,11 +1477,23 @@ def subscribers():
14111477 )
14121478
14131479
1480+ @admin .route ("/subscribers/bulk-operations" )
1481+ @login_required
1482+ def subscribers_bulk_operations_index ():
1483+
1484+ num_active_subscribers = get_number_of_active_subscribers ()
1485+ return render_template (
1486+ "admin/subscribers_bulk_operations_index.html" ,
1487+ num_active_subscribers = num_active_subscribers ,
1488+ confirm = request .args .get ("confirm" ),
1489+ )
1490+
1491+
14141492@admin .route ("/recent-subscription-cancellations" )
14151493@login_required
14161494def show_recent_subscription_cancellations ():
14171495 """Get the last 30 days subscription cancellations (if any)
1418- Note: Stripe api only guarentees the last 30 days of events.
1496+ Note: Stripe api only guarantees the last 30 days of events.
14191497 At time of writing this method performs no caching of events,
14201498 see StripeInvoice for possible improvements
14211499 """
@@ -1439,7 +1517,7 @@ def show_recent_subscription_cancellations():
14391517 )
14401518 if person is None :
14411519 log .info (
1442- f"""Person query retruned None- probably archived.\n
1520+ f"""Person query returned None- probably archived.\n
14431521 Skipping Person with uuid { value .data .object .metadata .person_uuid } """
14441522 )
14451523 continue
@@ -1700,7 +1778,7 @@ def add_shop_admin():
17001778 form = AddShopAdminForm ()
17011779 if request .method == "POST" :
17021780 if form .validate_on_submit ():
1703- # Check user dosent already exist
1781+ # Check user doesn't already exist
17041782 email = escape (request .form ["email" ].lower ())
17051783 if User .query .filter_by (email = email ).first () is not None :
17061784 return f"Error, admin with email ({ email } ) already exists."
@@ -1747,7 +1825,7 @@ def delete_admin_confirmation(id: int):
17471825 else :
17481826 User .query .filter_by (id = id ).delete ()
17491827 database .session .commit ()
1750- flash ("Account was deleted succesfully " )
1828+ flash ("Account was deleted successfully " )
17511829
17521830 except Exception as e :
17531831 msg = "Error deleting the admin account"
@@ -1926,7 +2004,7 @@ def rename_shop_post():
19262004
19272005@admin .route ("/announce-stripe-connect" , methods = ["GET" ])
19282006def announce_shop_stripe_connect_ids ():
1929- """Accounce this shop's stripe connect account id(s)
2007+ """Announce this shop's stripe connect account id(s)
19302008 to the STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST
19312009 - stripe_live_connect_account_id
19322010 - stripe_test_connect_account_id
@@ -2162,7 +2240,7 @@ def enable_geo_currency():
21622240@admin .route ("/spamcheck/<string:account_name>" )
21632241@login_required
21642242def check_spam (account_name ) -> int :
2165- """Check if shop name is likley to be spam or not"""
2243+ """Check if shop name is likely to be spam or not"""
21662244 from subscribie .anti_spam_subscribie_shop_names .run import detect_spam_shop_name
21672245
21682246 return str (detect_spam_shop_name (account_name ))
0 commit comments