Payment subscription REST api for customers:
- FxA, Firefox Accounts
Scott Idler Stewart Henderson Marty Ballard
DEPRECATED - payment subscription REST api for customers
License: Mozilla Public License 2.0
Payment subscription REST api for customers:
Scott Idler Stewart Henderson Marty Ballard
When I submit a known bad card to update payment for a customer, we get a response with a generic error like so:
{"message": "Request req_igSOxiiS3X3LWv: Your card has insufficient funds."}
We'd like to display some more tailored messages to the end-user and parsing this message for what happened may be difficult.
Could we maybe get some additional Stripe API error properties like type
, code
, decline_code
, and message
passed along in the subhub error response?
Here in subhub/api/payments.py
the result of stripe.Subscription.create
is discarded and the Customer is fetched to respond with the full current list of subscriptions:
stripe.Subscription.create(customer=customer.id, items=[{"plan": data["plan_id"]}])
updated_customer = stripe.Customer.retrieve(customer.id)
return create_return_data(updated_customer["subscriptions"]), 201
But, in order to create a corresponding subscription record over on FxA, it might be better to return just the new subscription details returned by stripe.Subscription.create
.
Otherwise, we'll need to do a comparison between the subhub & FxA subscription lists for the user and add/remove based on the difference. (Of course, now that I think about it, maybe a "sync" algorithm run at significant points wouldn't be a bad idea to keep FxA consistent with subhub & stripe.)
Pynamo has a low level API that provides direct delete calls:
https://pynamodb.readthedocs.io/en/latest/low_level.html#modifying-items
This should be used in db.py
to avoid having to fetch an item just to delete it.
I was working through writing some stub code in advance of subhub availability.
Could use some tweaks & additional specs in the API YAML. I was considering adding some suggestions to the YAML in a PR myself, but wanted to get this list out first:
Authorization: Bearer 39245792345
header? Hawk auth?GET /customer/{uid}/subscriptions
subscription_id
to the Subscriptions
schema? Need that for referring to specific subscriptions from FxA404 Not Found
"No subscriptions for this customer." Is there a difference between a GET
for a uid
that's never had a customer created before - versus a user who once had subscriptions but cancelled them all? Are these cases both 404s?POST /customer/{uid}/subscriptions
201 Created
response - ideally one that contains subscription_id
? Maybe one of the elements from the Subscriptions
schema with subscription_id
added?pmt_token
plan_id
GET /plans
api_token
parameter - is this meant to be the general Authorization
header for all requests?appliation/json
content typeDELETE /customer/{uid}/subscriptions/{sub_id}
201 Created
response body? Or maybe status 204 No Content
for success?GET /customer/{uid}
200 OK
response body with customer data?POST /customer/{uid}
pmt_token
200 OK
response body?We tried to do this during PR #297 but couldn't get it to work quickly. This is a cleanup task for that effort.
There's no apparent API endpoint at the moment to determine if a customer's source needs to be updated. Either the existing API call to return customer information with payment status should be updated, or a new endpoint that can indicate if the payment source needs updating.
This is used by FxA Account Management to indicate to a user that they must update their payment data.
Should not be sent to Firefox, per @bbangert
https://github.com/mozilla/subhub/blob/master/subhub/hub/stripe/customer.py#L216
If all subscriptions are cancelled, payment sources are removed.
The customer record still exists, though - so, when fetching the customer data, it looks like an exception is thrown related to payment sources here.
127.0.0.1 - - [09/Jun/2019 21:28:44] "GET /v1/customer/93e870bff49b43acb34923bfc840fb55 HTTP/1.1" 500 -
Traceback (most recent call last):
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2309, in __call__
return self.wsgi_app(environ, start_response)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/security.py", line 300, in wrapper
return function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/uri_parsing.py", line 143, in wrapper
response = function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/validation.py", line 347, in wrapper
return function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/parameter.py", line 126, in wrapper
return function(**kwargs)
File "/home/lmorc/devel-local/subhub/subhub/api/payments.py", line 246, in customer_update
return_data = create_update_data(customer)
File "/home/lmorc/devel-local/subhub/subhub/api/payments.py", line 262, in create_update_data
return_data["payment_type"] = customer["sources"]["data"][0]["funding"]
IndexError: list index out of range
Generally, we treat HTTP responses with 5xx status codes as an unrecoverable server-level error - and not as something we can report to the user as actionable. (e.g. please try again with a different card)
When I try submitting a subscription request with a token from a test card known to cause a card_declined error, I get a 500 status code from subhub when I expect a 402 status code.
Edit: Looks like maybe some profiling annotations are causing trouble?
Trying to make a GET request to /v1/customer/{uid}
and I'm getting a 500 error in our deployed FxA dev instance. I can reproduce locally and get this exception stack trace:
172.17.0.1 - - [15/Jul/2019 22:29:17] "GET /v1/customer/c4279736650649d6801bf291a2a40341 HTTP/1.1" 500 -
Traceback (most recent call last):
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
return self.wsgi_app(environ, start_response)
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
response = self.handle_exception(e)
File "/app/venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
reraise(exc_type, exc_value, tb)
File "/app/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/app/venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/app/venv/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/app/venv/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/app/venv/lib/python3.7/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/app/venv/lib/python3.7/site-packages/connexion/decorators/security.py", line 299, in wrapper
return function(request)
File "/app/venv/lib/python3.7/site-packages/connexion/decorators/uri_parsing.py", line 143, in wrapper
response = function(request)
File "/app/venv/lib/python3.7/site-packages/connexion/decorators/validation.py", line 347, in wrapper
return function(request)
File "/app/venv/lib/python3.7/site-packages/connexion/decorators/parameter.py", line 126, in wrapper
return function(**kwargs)
File "/app/subhub/tracing.py", line 81, in timer
return function(*args, **kwargs)
TypeError: customer_update() got an unexpected keyword argument 'user'
In using GET /v1/customer/:uid/subscriptions
, we ran into this:
In the swagger YAML, the response is specified as Subscriptions
, described as type array
As implemented, the response is an object { subscriptions: [] }
rather than a bare array.
We're rolling out a fix on the FxA side to account for the current implementation, so it's probably best to leave that as-is and update the YAML.
After moving all of our tests to mocks, our code still requires the STRIPE_API_KEY to run out tests. Fix this and remove this dependency. Once it has been removed, communicate that the doit task that checks for the value can be removed to the SRE.
Multiple functions throughout subhub use list
as the type for a parameter or result. They should however be using the typing
modules List
with the type of list it is:
https://docs.python.org/3/library/typing.html#type-aliases
For example, one function returns a list of subscriptions, which should be written as:
from typing import List
def somefunc(...args) -> List[stripe.Subscription]:
....
mypy and PyCharm will then work correctly with the type information.
Please document the possible set of error codes returned when a subscription fails.
Based on #81, it appears that the stripe error info is directly passed through, but given that subhub is a payment abstraction layer, I could see this changing over time.
This could simply be added to the openAPI yaml as an enum of possible values, and we could generate documentation from there.
Now that we have a way to cancel a subscription at period end, we also need a way to reactivate a subscription that's been cancelled but hasn't yet ended.
Related: mozilla/fxa#981
Add required items to subscriptions return data for clients to easier validate return data is correct and compatible.
https://github.com/mozilla/subhub/blob/master/subhub/subhub_api.yaml#L438
Starting to do some work around handling & displaying errors and ran into this:
When attempting to create the first subscription for a new customer, and the payment source results in a card declined error (e.g. using testing CC 4000 0000 0000 9979), the API responds with a 500 Server Error containing an exception stack trace.
Looks like when a customer is initially created, a card error is not expected?
127.0.0.1 - - [22/May/2019 17:11:11] "POST /v1/customer/2fbc85f877ca4364a5871136d557167d/subscriptions HTTP/1.1" 500 -
Traceback (most recent call last):
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/security.py", line 300, in wrapper
return function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/uri_parsing.py", line 143, in wrapper
response = function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/validation.py", line 172, in wrapper
response = function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/validation.py", line 347, in wrapper
return function(request)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/connexion/decorators/parameter.py", line 126, in wrapper
return function(**kwargs)
File "/home/lmorc/devel-local/subhub/subhub/api/payments.py", line 33, in subscribe_to_plan
origin_system=data["orig_system"],
File "/home/lmorc/devel-local/subhub/subhub/customer.py", line 69, in existing_or_new_customer
subhub_accouunt, user_id, email, source_token, origin_system
File "/home/lmorc/devel-local/subhub/subhub/customer.py", line 40, in create_customer
metadata={"userid": user_id},
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/stripe/api_resources/abstract/createable_api_resource.py", line 22, in create
response, api_key = requestor.request("post", url, params, headers)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/stripe/api_requestor.py", line 121, in request
resp = self.interpret_response(rbody, rcode, rheaders)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/stripe/api_requestor.py", line 372, in interpret_response
self.handle_error_response(rbody, rcode, resp.data, rheaders)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/stripe/api_requestor.py", line 151, in handle_error_response
raise err
stripe.error.CardError: Request req_kN6nw8Sf9PsASl: Your card was declined.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1974, in make_response
rv = self.response_class.force_type(rv, request.environ)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 269, in force_type
response = BaseResponse(*_run_wsgi_app(response, environ))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 26, in _run_wsgi_app
return _run_wsgi_app(*args)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/test.py", line 1119, in run_wsgi_app
app_rv = app(environ, start_response)
TypeError: 'dict' object is not callable
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2309, in __call__
return self.wsgi_app(environ, start_response)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1982, in make_response
reraise(TypeError, new_error, sys.exc_info()[2])
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/_compat.py", line 34, in reraise
raise value.with_traceback(tb)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/flask/app.py", line 1974, in make_response
rv = self.response_class.force_type(rv, request.environ)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 269, in force_type
response = BaseResponse(*_run_wsgi_app(response, environ))
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/wrappers/base_response.py", line 26, in _run_wsgi_app
return _run_wsgi_app(*args)
File "/home/lmorc/devel-local/subhub/subhub/.venv/lib/python3.7/site-packages/werkzeug/test.py", line 1119, in run_wsgi_app
app_rv = app(environ, start_response)
TypeError: 'dict' object is not callable
The view function did not return a valid response. The return type must be a string, tuple, Response instance, or WSGI callable, but it was a dict.
Have an active subscription.
A mail is sent, informing the user about the resubscription.
The mail is not sent.
I used this email for subscribing: [email protected].
Serverless: Tracing DISABLED for function "dev-fxa-sub"
Serverless: Tracing DISABLED for function "dev-fxa-hub"
Serverless: Tracing DISABLED for function "dev-fxa-mia"
Since Issue #70 and then PR #81, I'm seeing code
and message
added to errors relayed from Stripe.
I think we'll also need more things like type
and decline_code
if available. We can display the message
for now, but we will need the more abstract codes to select localized messages when we eventually add more locales than en-US.
Looks like this property is appearing now! But, it seems strange that it's giving a 201 Created response for GET /customer/{uid}/subscriptions
- maybe that's a separate issue? (Edit: Yeah, looks like this 201
should probably be a 200
?)
➜ subhub git:(master) ✗ curl -sD - -X GET --header 'Accept: application/json' --header 'Authorization: abcde' 'http://127.0.0.1:8012/v1/customer/9f960ea24a194d67ad62ee57faeb1308/subscriptions'
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 337
Access-Control-Allow-Origin: *
Server: Werkzeug/0.15.2 Python/3.7.1
Date: Tue, 11 Jun 2019 22:09:21 GMT
{
"subscriptions": [
{
"cancel_at_period_end": true,
"current_period_end": 1562882826,
"current_period_start": 1560290826,
"ended_at": null,
"plan_id": "plan_F4bof27uz71Vk7",
"plan_name": "123Done Pro Monthly",
"status": "active",
"subscription_id": "sub_FEleCnZNUg1ZDb"
}
]
}
Originally posted by @lmorchard in #67 (comment)
While working to troubleshoot an issue adjacent to zip code validation, I noticed that subhub was logging this error while attempting to fetch customer data:
{"message": "RETRIEVE STRIPE CUSTOMER ERROR", "lineno": 168, "pathname": "/subhub/sub/shared/vendor.py", "levelname": "ERROR", "threadName": "Thread-31", "error": "Request req_fF3sb9JkiNGkbd: No such customer: ch_1FYh1KEOSeHhIAfQY32wfWhV", "logger": "__main__", "timestamp": "2019-10-28T23:26:25.579941Z"}
The ch_
prefix on ch_1FYh1KEOSeHhIAfQY32wfWhV
looks like it's a Charge ID and not a Customer ID.
Digging around, I noticed this line in create_update_data
in src/sub/payments.py
:
intents = vendor.retrieve_stripe_customer(invoice["charge"])
Seems like this should be more like this line?
intents = vendor.retrieve_stripe_charge(invoice["charge"])
If subhub can't connect to Stripe or AWS for any reason, Subscription and Customer API calls will fail. Will 500 errors be returned in that case? It would be good to document this in the swagger/openapi spec.
From the YAML, it looks like GET /customer/{uid}
should include cancel_at_period_end
in the listed subscriptions. This property is missing from the implementation, though.
This was filed over in the guardian website repo by @rbillings.
After signing up for Guardian, the Thank You for Subscribing email went directly into my Spam folder [although the payment one went through]. Not sure if there's a way to avoid this, but I wouldn't have seen it as a user.
Currently FxA displays the plan id of the selected product, an issue has been raised on the FxA side to determine if this is the correct field to show and if not, what is the correct field or combinations of fields to show to a user during checkout.
mozilla/fxa#2337
This was filed over in the guardian website repo by @rbillings.
Payment received email [Sandbox: Firefox Guardian (Monthly) monthly payment received] includes a bad link to update your email preferences. The link goes to:
https://www.mozilla.org/newsletter/existing/%%Token__c%%/
This was filed over in the guardian website repo by @rbillings. I'm guessing this is a simple configuration update on the product name?
The email for payment received has this title:
Sandbox: Firefox Guardian (Monthly) monthly payment receivedSuggest removing the second "monthly" or changing the title of the product to remove "Monthly".
User is logged in in the Guardian VPN homepage and he does have an active subscription.
https://stage.guardian.nonprod.cloudops.mozgcp.net/vpn
The user is not billed again so no Payment Received email is sent to the user.
User is logged in in the Guardian VPN homepage and he does not have an active subscription.
https://stage.guardian.nonprod.cloudops.mozgcp.net/vpn
This might be an edge case, but:
Over in FxA, we cancel all subscriptions when a user is deleted. But, we can't actually delete the customer record in subhub.
So, if someone signs up again on FxA with the same email address, subhub & Stripe still retain the old customer UID and attempts to subscribe from the new account will fail.
I think we'd need to delete the customer upon deleting the FxA account. (Also guessing if we did that, we wouldn't need to cancel the subscriptions separately.)
User has an active subscription
Per #178, the spec has not matched the code at times. This is because the strict_validation
argument passed in app.py
applies only to the request validation. To validate the outgoing responses for correctness, validate_responses
must be set per https://connexion.readthedocs.io/en/latest/response.html#response-validation
The Plan schema in YAML specifies a nickname
property. But, the API implementation of GET /plans
uses plan_name
and product_name
instead.
Also, the Subscriptions schema and API implementation for customer data still uses nickname
for the Plan nickname.
Could we get all the above (i.e. Plan, Subscription, and Customer schemas) to consistently use plan_name
and product_name
?
The following fields must be removed from the payload sent to FxA for a new subscrption:
brand, cancel_at, cancel_at_period_end, canceled_at, charge, created, currency, current_period_end, current_period_start, customer_id, event_id, event_type, invoice_id, invoice_number, last4, nickname, plan_amount, subscription_id
File:
src/hub/vendor/customer.py 78-157
Cancellation of subscription should be at the end of the existing period. Also should not delete subscription.
month day, year
" box.I'm not sure of the status of the feature, but this ticket is to verify the status/implementation. In FxA we are checking for incoming updates (via SQS) on subscription status, implemented in mozilla/fxa#1122
We expect the message to match this schema: https://github.com/mozilla/fxa/blob/master/packages/fxa-auth-server/lib/subhub/updates.js#L10-L19
The find_newest_subscription
function in payments.py is missing a return type annotation. It would've helped in this case to catch a failed expectation. The function on its own is capable of returning None
(if subscriptions is empty), or a dict with a data
key.
At the moment, there's only a single calling context, that typically guarantees there will be at least one subscription (as it creates one), but if it is not created, then the function would return None
. This is a problem because the caller of the function proceeds with the assumption that the call will always return a dict with a data
key which is used by create_return_data
. create_return_data
requires a dict to be passed in with a data
key or it will throw a KeyError.
In the future, to make some of these errors less likely, there's a TypedDict PEP which would be used to ensure this isn't done.
Alternatively, the find_newest_subscription
function could just return a list of subscriptions, and it would be empty in the event there is none. The create_return_data
could then be updated to not use a data
key in a dict.
To run subhub locally for FxA, we need to know how to run it locally. The README or some other document should indicate the system dependencies, requirements, and steps needed for OSX/Linux to have a working subhub locally.
Looks like we added tests as a prerequisite to starting up the local server.
But, when I run it locally with my own personal Stripe key to play with the admin panel, those tests fail.
So, I can't start the local server unless I comment out that pre-req in dodo.py
It would be useful to use stripe-mock as an optional backend for local subhub testing. This would make it easier for devs to test, enable contributors to get involved, and shorten local test running time.
https://github.com/mozilla/subhub/blob/master/subhub/tests/unit/stripe/utils.py#L17-L22
Setting arguments using mutable default value is a bug:
https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html
All of the Python files have had headers inserted that are unnecessary or odd to include. The first one:
#!/usr/bin/env python3
Is used for running a Python file as a script, and Python library source files shouldn't have this added as they're not directly executed as a script.
The second:
# -*- coding: utf-8 -*-
is used when including unicode characters in a Python 2 source file. SubHub is entirely Python 3, which is unicode by default so this shouldn't be included.
Stripe's error API includes a message attribute that is usable for display to the user regarding what went wrong. This message if available should be extracted from the error and included as a separate field in the error JSON response so that FxA can pass it to the user-agent for display.
Have a account with an active subscription.
The brand is displayed as "Premium Services".
The brand is displayed as "Mozilla Enterprise".
The issue is reproducible for any of the support categories.
User is logged in in the Guardian VPN homepage and he does not have an active subscription.
https://stage.guardian.nonprod.cloudops.mozgcp.net/vpn
In the email the next invoice date is tomorrow (10/10.2019)
In the email the current invoice is having the present day date (10/9/2019 ) and the next invoice date is 2 days from now (10/11/2019)
Please view this screenshot containing the new subscription email:
Also in the Subscriptions page the date for the next payment is tommrow (10/10/2019) so I would expect the invoice to be sent in the same date.
Please view this screenshot from the Subscription page:
We're finding incorrect close dates on subscription records in Salesforce. When troubleshooting email sends, we noticed that the close date on the following subscriptions was incorrectly set. Can we verify the dates we're sending for the subscriptions and/or involve basket if necessary?
Charge processed on 10/9/19, close date is 10/7/19
Invoice id: in_1FRnRAKb9q6OnNsLXKZLSYvo
charge id: ch_1FRoQfKb9q6OnNsLoLoVycWL
subscription id: sub_FwyQnMNwiIqpPQ
Charge processed on 10/8/19, close date is 10/7/19
Invoice id: in_1FRQxYKb9q6OnNsLvscz8GyX
charge id: ch_1FRRw3Kb9q6OnNsLkfZNRdN0
subscription id: sub_FwyQnMNwiIqpPQ
After a zendesk agent marks a support ticket as "Solved", I would expect to send an email notification to the person who filed the support request. That is currently not happening, the support requester receives notifications only when the ticket was filed and when there are comments added to it.
User should receive an email notification saying the status of the ticket was changed to "Solved"
No email notification for the ticket status change is received
Ubuntu 16.04, Firefox 69.0.3
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.