diff --git a/NEWS b/NEWS index 3172481..8405132 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +1.3.19: May 8, 2008 + * Support for yearly subscriptions. + 1.3.18: May 7, 2008 * No longer showing "settings" link unless you're viewing your wiki. * In account settings note, now showing link to upgrade/downgrade/cancel. diff --git a/config/Common.py b/config/Common.py index 2a2d97b..a82d1c3 100644 --- a/config/Common.py +++ b/config/Common.py @@ -30,24 +30,33 @@ settings = { "storage_quota_bytes": 30 * MEGABYTE, "notebook_collaboration": False, "fee": None, + "yearly_fee": None, }, { "name": "basic", "storage_quota_bytes": 250 * MEGABYTE, "notebook_collaboration": True, "fee": 5, + "yearly_fee": 50, "button": """ """, + "yearly_button": + """ + """, }, { "name": "standard", "storage_quota_bytes": 500 * MEGABYTE, "notebook_collaboration": True, "fee": 9, + "yearly_fee": 90, "button": """ """, + "yearly_button": + """ + """, }, # { # "name": "premium", diff --git a/controller/Users.py b/controller/Users.py index a52911d..7a78f3a 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -1082,6 +1082,9 @@ class Users( object ): # verify item_number plan_index = params.get( u"item_number" ) + if plan_index == None: + return dict() # ignore this transaction if there's no item number + try: plan_index = int( plan_index ) except ValueError: @@ -1092,13 +1095,14 @@ class Users( object ): # verify mc_gross rate_plan = self.__rate_plans[ plan_index ] fee = u"%0.2f" % rate_plan[ u"fee" ] + yearly_fee = u"%0.2f" % rate_plan[ u"yearly_fee" ] mc_gross = params.get( u"mc_gross" ) - if mc_gross and mc_gross != fee: + if mc_gross and mc_gross not in ( fee, yearly_fee ): raise Payment_error( u"invalid mc_gross", params ) # verify mc_amount3 mc_amount3 = params.get( u"mc_amount3" ) - if mc_amount3 and mc_amount3 != fee: + if mc_amount3 and mc_amount3 not in ( fee, yearly_fee ): raise Payment_error( u"invalid mc_amount3", params ) # verify item_name @@ -1112,8 +1116,12 @@ class Users( object ): # verify period3 period3 = params.get( u"period3" ) - if period3 and period3 != u"1 M": # one-month subscription - raise Payment_error( u"invalid period3", params ) + if mc_amount3 == yearly_fee: + if period3 and period3 != u"12 M": # one-year subscription + raise Payment_error( u"invalid period3", params ) + else: + if period3 and period3 != u"1 M": # one-month subscription + raise Payment_error( u"invalid period3", params ) params[ u"cmd" ] = u"_notify-validate" encoded_params = urllib.urlencode( params ) diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 950f402..7fc8ade 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -405,14 +405,18 @@ class Test_controller( object ): u"storage_quota_bytes": 1337 * 10, u"notebook_collaboration": True, u"fee": 1.99, + u"yearly_fee": 19.90, u"button": u"[subscribe here user %s!] button", + u"yearly_button": u"[yearly subscribe here user %s!] button", }, { u"name": "extra super", u"storage_quota_bytes": 31337 * 1000, u"notebook_collaboration": True, u"fee": 9.00, + u"yearly_fee": 90.00, u"button": u"[or here user %s!] button", + u"yearly_button": u"[yearly or here user %s!] button", }, ], }, diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index c2a89f3..fef267f 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -2501,6 +2501,25 @@ class Test_users( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.rate_plan == 0 + def test_paypal_notify_payment_yearly( self ): + data = dict( self.PAYMENT_DATA ) + data[ u"custom" ] = self.user.object_id + data[ u"payment_gross" ] = u"90.00" + data[ u"mc_gross" ] = u"90.00" + result = self.http_post( "/users/paypal_notify", data ); + + assert len( result ) == 1 + assert result.get( u"session_id" ) + assert Stub_urllib2.result == u"VERIFIED" + assert Stub_urllib2.headers.get( u"Content-type" ) == u"application/x-www-form-urlencoded" + assert Stub_urllib2.url.startswith( "https://" ) + assert u"paypal.com" in Stub_urllib2.url + assert Stub_urllib2.encoded_params + + # being notified of a mere payment should not change the user's rate plan + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 0 + def test_paypal_notify_payment_invalid( self ): data = dict( self.PAYMENT_DATA ) data[ u"custom" ] = self.user.object_id @@ -2544,6 +2563,14 @@ class Test_users( Test_controller ): assert result.get( u"error" ) + def test_paypal_notify_payment_missing_item_number( self ): + data = dict( self.PAYMENT_DATA ) + data[ u"custom" ] = self.user.object_id + result = self.http_post( "/users/paypal_notify", data ); + + assert len( result ) == 1 + assert result.get( u"session_id" ) + def test_paypal_notify_payment_incorrect_gross( self ): data = dict( self.PAYMENT_DATA ) data[ u"custom" ] = self.user.object_id @@ -2785,6 +2812,25 @@ class Test_users( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.rate_plan == 1 + def test_paypal_notify_signup_yearly( self ): + data = dict( self.SUBSCRIPTION_DATA ) + data[ u"custom" ] = self.user.object_id + data[ u"amount3" ] = u"90.00" + data[ u"mc_amount3" ] = u"90.00" + data[ u"period3" ] = u"12 M" + result = self.http_post( "/users/paypal_notify", data ); + + assert len( result ) == 1 + assert result.get( u"session_id" ) + assert Stub_urllib2.result == u"VERIFIED" + assert Stub_urllib2.headers.get( u"Content-type" ) == u"application/x-www-form-urlencoded" + assert Stub_urllib2.url.startswith( "https://" ) + assert u"paypal.com" in Stub_urllib2.url + assert Stub_urllib2.encoded_params + + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 1 + def test_paypal_notify_signup_invalid( self ): data = dict( self.SUBSCRIPTION_DATA ) data[ u"custom" ] = self.user.object_id @@ -2888,6 +2934,26 @@ class Test_users( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.rate_plan == 0 + def test_paypal_notify_signup_yearly_period3_with_monthly_amount( self ): + data = dict( self.SUBSCRIPTION_DATA ) + data[ u"custom" ] = self.user.object_id + data[ u"period3" ] = u"12 M" + result = self.http_post( "/users/paypal_notify", data ); + + assert result.get( u"error" ) + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 0 + + def test_paypal_notify_signup_yearly_amount_with_monthly_period3( self ): + data = dict( self.SUBSCRIPTION_DATA ) + data[ u"custom" ] = self.user.object_id + data[ u"mc_amount3" ] = u"19.90" + result = self.http_post( "/users/paypal_notify", data ); + + assert result.get( u"error" ) + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 0 + def test_paypal_notify_signup_missing_custom( self ): data = dict( self.SUBSCRIPTION_DATA ) result = self.http_post( "/users/paypal_notify", data ); @@ -2952,6 +3018,29 @@ class Test_users( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.rate_plan == 1 + def test_paypal_notify_modify_yearly( self ): + self.user.rate_plan = 2 + user = self.database.save( self.user ) + + data = dict( self.SUBSCRIPTION_DATA ) + data[ u"txn_type" ] = u"subscr_modify" + data[ u"custom" ] = self.user.object_id + data[ u"amount3" ] = u"90.00" + data[ u"mc_amount3" ] = u"90.00" + data[ u"period3" ] = u"12 M" + result = self.http_post( "/users/paypal_notify", data ); + + assert len( result ) == 1 + assert result.get( u"session_id" ) + assert Stub_urllib2.result == u"VERIFIED" + assert Stub_urllib2.headers.get( u"Content-type" ) == u"application/x-www-form-urlencoded" + assert Stub_urllib2.url.startswith( "https://" ) + assert u"paypal.com" in Stub_urllib2.url + assert Stub_urllib2.encoded_params + + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 1 + def test_paypal_notify_modify_invalid( self ): self.user.rate_plan = 2 user = self.database.save( self.user ) @@ -3179,6 +3268,29 @@ class Test_users( Test_controller ): user = self.database.load( User, self.user.object_id ) assert user.rate_plan == 0 + def test_paypal_notify_cancel_yearly( self ): + self.user.rate_plan = 1 + user = self.database.save( self.user ) + + data = dict( self.SUBSCRIPTION_DATA ) + data[ u"txn_type" ] = u"subscr_cancel" + data[ u"custom" ] = self.user.object_id + data[ u"amount3" ] = u"90.00" + data[ u"mc_amount3" ] = u"90.00" + data[ u"period3" ] = u"12 M" + result = self.http_post( "/users/paypal_notify", data ); + + assert len( result ) == 1 + assert result.get( u"session_id" ) + assert Stub_urllib2.result == u"VERIFIED" + assert Stub_urllib2.headers.get( u"Content-type" ) == u"application/x-www-form-urlencoded" + assert Stub_urllib2.url.startswith( "https://" ) + assert u"paypal.com" in Stub_urllib2.url + assert Stub_urllib2.encoded_params + + user = self.database.load( User, self.user.object_id ) + assert user.rate_plan == 0 + def test_paypal_notify_cancel_invalid( self ): self.user.rate_plan = 1 user = self.database.save( self.user ) diff --git a/view/Upgrade_page.py b/view/Upgrade_page.py index d6ce959..6dadfa5 100644 --- a/view/Upgrade_page.py +++ b/view/Upgrade_page.py @@ -137,7 +137,12 @@ class Upgrade_page( Product_page ): P( Table( - self.fee_row( rate_plans, user, include_blank = False ), + Tr( Td( + u"Get two months free with a yearly subscription!", + class_ = u"upgrade_subtitle", + colspan = u"3", + ), colspan = u"3" ), + self.fee_row( rate_plans, user, include_blank = False, yearly = True ), Tr( [ Td( plan[ u"storage_quota_bytes" ] // MEGABYTE, " MB", @@ -164,20 +169,26 @@ class Upgrade_page( Product_page ): ), ) - def fee_row( self, rate_plans, user, include_blank = True ): + def fee_row( self, rate_plans, user, include_blank = True, yearly = False ): return Tr( include_blank and Th( u" " ) or None, [ Th( plan[ u"name" ].capitalize(), plan[ u"fee" ] and Div( - Span( + yearly and Span( + u"$%s" % plan[ u"yearly_fee" ], + Span( u"/year", class_ = u"month_text" ), + class_ = u"price_text", + separator = u"", + ) or Span( u"$%s" % plan[ u"fee" ], Span( u"/month", class_ = u"month_text" ), class_ = u"price_text", separator = u"", ), user and user.username not in ( u"anonymous", None ) and user.rate_plan != index \ - and plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id or None, + and yearly and ( plan.get( u"yearly_button" ).strip() and plan.get( u"yearly_button" ) % user.object_id or None ) or \ + ( plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id or None ), ) or None, ( not user or user.username in ( u"anonymous", None ) ) and Div( A(