How to separate subscription and common checkout webhook in stripe? - python

I have made a webhook (flask) for stripe.
My app has both one time payment and subscription.
I would like to know how can I detect if it's normal checkout or subscription?
This is how I am separating now, but I think it's not recommended way.
I am checking stripe_subscription_id and if exist, I considered it as subscription.
(charge.succeeded is called for subscription succeeded also)
#api.route('/stripe/webhook', methods=['GET', 'POST'])
def webhook():
event = None
payload = request.data
try:
event = json.loads(payload)
except Exception as e:
print('Webhook error while parsing basic request.' + str(e))
if not event:
return jsonify({'status': 'failed'}), 400
# one time payout
if event['type'] == 'charge.succeeded':
event_intent = event['data']['object']
payment_intent = event_intent['payment_intent']
# payment
if event['type'] == 'checkout.session.completed':
checkout_session = event['data']['object']
stripe_subscription_id = checkout_session['subscription']
# customer_email = checkout_session['customer_email']
if stripe_subscription_id:
# Create a new subscription and mark it as paid this month.
customer_subscription_mark_paid(checkout_session['id'], stripe_subscription_id)
elif event['type'] == 'invoice.paid':
invoice = event['data']['object']
stripe_subscription_id = invoice['subscription']
if stripe_subscription_id:
# Check if this is the first invoice or a later invoice in the
# subscription lifecycle.
first_invoice = invoice['billing_reason'] == 'subscription_create'
# You already handle marking the first invoice as paid in the
# `checkout.session.completed` handler.
# Only use this for the 2nd invoice and later, so it doesn't conflict.
if not first_invoice:
# Mark the subscription as paid.
customer_subscription_mark_paid(None, stripe_subscription_id)
elif event['type'] == 'invoice.payment_failed':
invoice = event['data']['object']
stripe_subscription_id = invoice['subscription']
if stripe_subscription_id:
customer_subscription_mark_past_due(None, stripe_subscription_id)

Checking to see if the subscription field is filled is fine, but to be extra sure you can check the contents of the mode field.

Related

How i can to prevent to create multiple instance while multi request in django?

Hi everyone i have project include about payment function between user and user. But i have a problem when many user buy a product.the many payment object will created more than product avaliable. How i can to solve this ?
products_avaliable = Product.objects.filter(on_sell=true)
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
payment.save()
product.on_sell = False
product.save()
when add deley
products_avaliable = Product.objects.filter(on_sell=true)
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
payment.save()
time.sleep(10) # simulate to slow server or many request
product.on_sell = False
product.save()
when i try to time.delay before payment create (simulate when server to slow or may request by user) the payment will create many object
You can check if the object is on_sell before committing to the DB:
for product in products_avaliable:
payment = Payment()
payment.buyer = ....
payment.seller = product.owner
product.on_sell = False
product.refresh_from_db() # update the object
if product.on_sell:
payment.save()
product.save()
else:
...

How to process webhook request coming from a 3rd party application?

I need help to evaluate weather i am doing it right or is there a better way, the scenario is an 3rd party application is sending an webhook request after a successful payment but the problem is that sometimes this application may send the same notification more than once.so it is recommended to ensure that implementation of the webhook is idempotent.so steps that i am implementing for this are
if signature is correct (assume it is corect),Find orders record in the database using orderId in the request params.
Please note: orderId in request params is payment_gateway_order_identifier in orders table.
if txStatus = 'SUCCESS' AND haven't already processed COLLECTION payment for this same order,
Create payments record.
201 response with nothing in the response body.
else
201 response with nothing in the response body.
else
422 response with {message: "Signature is incorrect"} in response body
views.py
#api_view(['POST'])
def cashfree_request(request):
if request.method == 'POST':
data=request.POST.dict()
payment_gateway_order_identifier= data['orderId']
amount = data['orderAmount']
transaction_status = data['txStatus']
signature = data['signature']
if(computedsignature==signature): #assume it to be true
order=Orders.objects.get(
payment_gateway_order_identifier=payment_gateway_order_identifier)
if transaction_status=='SUCCESS':
try:
payment= Payments.objects.get(orders=order)
return Response({"Payment":"Done"},status=status.HTTP_200_OK)
except (Payments.DoesNotExist):
payment = Payments(orders=order,amount=amount,datetime=datetime)
payment.save()
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
models.py
class Orders(models.Model):
id= models.AutoField(primary_key=True)
amount = models.DecimalField(max_digits=19, decimal_places=4)
payment_gateway_order_identifier = models.UUIDField(
primary_key=False,default=uuid.uuid4,editable=False,unique=True)
sussessfull
class Payments(models.Model):
id = models.AutoField(primary_key=True)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=19, decimal_places=4, verbose_name='Price in INR')
datetime = models.DateTimeField(auto_now=False,auto_now_add=False)
This rather belongs to Codereview site. Anyway - you are doing up to 3 consecutive SQL queries, so there's a chance for a race condition. A simple way how to prevent that: use some KV storage like Redis/Memcache as a lock - save the value you're using as a nonce on the start of the function, and delete it on the end.
#api_view(['POST'])
def cashfree_request(request):
data = request.POST.dict()
payment_gateway_order_identifier = data['orderId']
# `nx` will set & return only if the key does not exist
# set a timeout in case it wont reach `delete()` on the end
if not redis.set("lock_%s" % payment_gateway_order_identifier, "1", nx=True, ex=2):
return Response(status=status.HTTP_409_CONFLICT)
amount = data['orderAmount']
transaction_status = data['txStatus']
signature = data['signature']
if computedsignature == signature:
order = Orders.objects.get(payment_gateway_order_identifier=payment_gateway_order_identifier)
if transaction_status == 'SUCCESS':
try:
Payments.objects.get(orders=order)
res = Response({"Payment": "Done"}, status=status.HTTP_200_OK)
except Payments.DoesNotExist:
payment = Payments(orders=order, amount=amount, datetime=datetime)
payment.save()
res = Response(status=status.HTTP_200_OK)
else:
res = Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
# unlock for another request
redis.delete("lock_%s" % payment_gateway_order_identifier)
return res
you dont need if request.method == 'POST': since the code is accessible via POST only anyway, that will make your code less indented.
notice you dont handle the case where transaction_status is not SUCCESS

Python: Bloomberg API is not authorized

I am trying to pull data from Bloomberg using Python API. API package comes with example codes and the programs that only requires local host work perfectly. However, the programs that uses other authorization ways are always stuck with the error:
Connecting to port 8194 on localhost
TokenGenerationFailure = {
reason = {
source = "apitkns (apiauth) on ebbdbp-ob-053"
category = "NO_AUTH"
errorCode = 12
description = "User not in emrs userid=NA\mds firm=22691"
subcategory = "INVALID_USER"
}
}
Failed to get token
No authorization
I saw one more person having similar problem but instead of solving it he chose to just use local host. I can't always use localhost because I will have to assist and troubleshoot for other users. So I need a hint how to overcome this error.
My question is how can I set the userid anything other than OS_LOGON which automatically uses the login credentials of my account so that I can use other users' name when needed? I tried to change OS_LOGON with the user name but it didn't work.
The full program I am trying to run is:
"""SnapshotRequestTemplateExample.py"""
from __future__ import print_function
from __future__ import absolute_import
import datetime
from optparse import OptionParser, OptionValueError
import blpapi
TOKEN_SUCCESS = blpapi.Name("TokenGenerationSuccess")
TOKEN_FAILURE = blpapi.Name("TokenGenerationFailure")
AUTHORIZATION_SUCCESS = blpapi.Name("AuthorizationSuccess")
TOKEN = blpapi.Name("token")
def authOptionCallback(_option, _opt, value, parser):
vals = value.split('=', 1)
if value == "user":
parser.values.auth = "AuthenticationType=OS_LOGON"
elif value == "none":
parser.values.auth = None
elif vals[0] == "app" and len(vals) == 2:
parser.values.auth = "AuthenticationMode=APPLICATION_ONLY;"\
"ApplicationAuthenticationType=APPNAME_AND_KEY;"\
"ApplicationName=" + vals[1]
elif vals[0] == "userapp" and len(vals) == 2:
parser.values.auth = "AuthenticationMode=USER_AND_APPLICATION;"\
"AuthenticationType=OS_LOGON;"\
"ApplicationAuthenticationType=APPNAME_AND_KEY;"\
"ApplicationName=" + vals[1]
elif vals[0] == "dir" and len(vals) == 2:
parser.values.auth = "AuthenticationType=DIRECTORY_SERVICE;"\
"DirSvcPropertyName=" + vals[1]
else:
raise OptionValueError("Invalid auth option '%s'" % value)
def parseCmdLine():
"""parse cli arguments"""
parser = OptionParser(description="Retrieve realtime data.")
parser.add_option("-a",
"--ip",
dest="hosts",
help="server name or IP (default: localhost)",
metavar="ipAddress",
action="append",
default=[])
parser.add_option("-p",
dest="port",
type="int",
help="server port (default: %default)",
metavar="tcpPort",
default=8194)
parser.add_option("--auth",
dest="auth",
help="authentication option: "
"user|none|app=<app>|userapp=<app>|dir=<property>"
" (default: %default)",
metavar="option",
action="callback",
callback=authOptionCallback,
type="string",
default="user")
(opts, _) = parser.parse_args()
if not opts.hosts:
opts.hosts = ["localhost"]
if not opts.topics:
opts.topics = ["/ticker/IBM US Equity"]
return opts
def authorize(authService, identity, session, cid):
"""authorize the session for identity via authService"""
tokenEventQueue = blpapi.EventQueue()
session.generateToken(eventQueue=tokenEventQueue)
# Process related response
ev = tokenEventQueue.nextEvent()
token = None
if ev.eventType() == blpapi.Event.TOKEN_STATUS or \
ev.eventType() == blpapi.Event.REQUEST_STATUS:
for msg in ev:
print(msg)
if msg.messageType() == TOKEN_SUCCESS:
token = msg.getElementAsString(TOKEN)
elif msg.messageType() == TOKEN_FAILURE:
break
if not token:
print("Failed to get token")
return False
# Create and fill the authorization request
authRequest = authService.createAuthorizationRequest()
authRequest.set(TOKEN, token)
# Send authorization request to "fill" the Identity
session.sendAuthorizationRequest(authRequest, identity, cid)
# Process related responses
startTime = datetime.datetime.today()
WAIT_TIME_SECONDS = 10
while True:
event = session.nextEvent(WAIT_TIME_SECONDS * 1000)
if event.eventType() == blpapi.Event.RESPONSE or \
event.eventType() == blpapi.Event.REQUEST_STATUS or \
event.eventType() == blpapi.Event.PARTIAL_RESPONSE:
for msg in event:
print(msg)
if msg.messageType() == AUTHORIZATION_SUCCESS:
return True
print("Authorization failed")
return False
endTime = datetime.datetime.today()
if endTime - startTime > datetime.timedelta(seconds=WAIT_TIME_SECONDS):
return False
def main():
"""main entry point"""
global options
options = parseCmdLine()
# Fill SessionOptions
sessionOptions = blpapi.SessionOptions()
for idx, host in enumerate(options.hosts):
sessionOptions.setServerAddress(host, options.port, idx)
sessionOptions.setAuthenticationOptions(options.auth)
sessionOptions.setAutoRestartOnDisconnection(True)
print("Connecting to port %d on %s" % (
options.port, ", ".join(options.hosts)))
session = blpapi.Session(sessionOptions)
if not session.start():
print("Failed to start session.")
return
subscriptionIdentity = None
if options.auth:
subscriptionIdentity = session.createIdentity()
isAuthorized = False
authServiceName = "//blp/apiauth"
if session.openService(authServiceName):
authService = session.getService(authServiceName)
isAuthorized = authorize(authService, subscriptionIdentity,
session, blpapi.CorrelationId("auth"))
if not isAuthorized:
print("No authorization")
return
else:
print("Not using authorization")
.
.
.
.
.
finally:
session.stop()
if __name__ == "__main__":
print("SnapshotRequestTemplateExample")
try:
main()
except KeyboardInterrupt:
print("Ctrl+C pressed. Stopping...")
This example is intended for Bloomberg's BPIPE product and as such includes the necessary authorization code. For this example, if you're connecting to the Desktop API (typically localhost:8194) you would want to pass an auth parameter of "none". Note that this example is for the mktdata snapshot functionality which isn't supported by Desktop API.
You state you're trying to troubleshoot on behalf of other users, presumably traders using BPIPE under their credentials. In this case you would need to create an Identity object to represent that user.
This would be done thusly:
# Create and fill the authorization request
authRequest = authService.createAuthorizationRequest()
authRequest.set("authId", STRING_CONTAINING_USERS_EMRS_LOGON)
authRequest.set("ipAddress", STRING_OF_IP_ADDRESS_WHERE_USER_IS_LOGGED_INTO_TERMINAL)
# Send authorization request to "fill" the Identity
session.sendAuthorizationRequest(authRequest, identity, cid)
Please be aware of potential licensing compliance issues when using this approach as this can have serious consequences. If in any doubt, approach your firm's market data team who will be able to ask their Bloomberg contacts.
Edit:
As asked in the comments, it's useful to elaborate on the other possible parameters for the AuthorizationRequest.
"uuid" + "ipAddress"; this would be the default method of authenticating users for Server API. On BPIPE this would require Bloomberg to explicitly enable it for you. The UUID is the unique integer identifier assigned to each Bloomberg Anywhere user. You can look this up in the terminal by running IAM
"emrsId" + "ipAddress"; "emrsId" is a deprecated alias for "authId". This shouldn't be used anymore.
"authId" + "ipAddress"; "authId" is the String defined in EMRS (the BPIPE Entitlements Management and Reporting System) or SAPE (the Server API's equivalent of EMRS) that represents each user. This would typically be that user's OS login details (e.g. DOMAIN/USERID) or Active Directory property (e.g. mail -> blah#blah.blah)
"authId" + "ipAddress" + "application"; "application" is the application name defined on EMRS/SAPE. This will check to see whether the user defined in authId is enabled for the named application on EMRS. Using one of these user+app style Identity objects in requests should record usage against both the user and application in the EMRS usage reports.
"token"; this is the preferred approach. Using the session.generateToken functionality (which can be seen in the original question's code snippet) will result in an alphanumeric string. You'd pass this as the only parameter into the Authorization request. Note that the token generation system is virtualization-aware; if it detects it's running in Citrix or a remote desktop it will report the IP address of the display machine (or one hop towards where the user actually is).

Django: Refactoring the "right" way

My DetailView currently gets longer and longer. And therefore I am trying to learn more about some better practices to shorten it again. Right now, I am looking into the def post() part. The full code is even longer. My best idea currently is to create a utils.py in the same folder and take out the code snippets inside the if else statements. Is that best practice or would you do it in a different way?
class EventDetail(DetailView):
[...]
#transaction.atomic
def post(self, request, *args, **kwargs):
# Discount code POST
if self.discount_form.is_valid():
discount_code = self.discount_form.cleaned_data['code']
request.session[discount_cookie_name(request.event)] = discount_code
return redirect(reverse_obj(self.request.event, 'events:detail'))
# Consent form POST
elif self.consent_form.is_valid():
# Check if email entry exists
contact = request.event.organizer.contacts.filter(
email=self.consent_form.cleaned_data['email']
).first()
success_message = _("You successfully signed up to receive event updates.")
if contact:
# Check if current event is allocated to entered email.
event_entry = contact.contact_relation.filter(
event=request.event
).first()
if event_entry:
event_entry.lead = True
event_entry.save(update_fields=['lead'])
messages.success(request, success_message)
else:
# Email entry exists, but event allocation must be added.
ContactRelation.objects.create(
lead=True, contact=contact, event=request.event
)
messages.success(request, success_message)
# Create new email entry and allocate event.
else:
instance = self.consent_form.save(commit=False)
instance.organizer = request.event.organizer
instance.save()
instance.contact_relations.add(
request.event, through_defaults={'lead': True}
)
messages.success(request, success_message)
return redirect(reverse_obj(self.request.event, 'events:detail'))
elif self.formset.is_valid():
# Check if the user already has an order_reference in the session
old_order_reference = request.session.get('order_reference')
if old_order_reference is not None:
ReservedItem.objects.filter(
order_reference=old_order_reference
).delete()
# Generate a new order_reference and save it in the session
order_reference = unique_order_reference_generator()
request.session['order_reference'] = order_reference
for form in self.formset:
quantity = form.cleaned_data['quantity']
qty = 0 if not quantity else quantity
if qty > 0:
reserved_item = form.save(commit=False)
ticket = form.cleaned_data['ticket']
reserved_item.order_reference = order_reference
reserved_item.ticket_name = ticket.name
if ticket.tax:
reserved_item.ticket_tax = ticket.tax
reserved_item.tax_name = ticket.tax.name
reserved_item.tax_percentage = ticket.tax.percentage
# Prevent AttributeError
discount_obj = None
if self.discount_code:
discount_obj = self.discount_code
if self.social_ticketing_referral:
# That part is not inside generate_reserved_discount_items
# as the influencer is still getting points. Even, if the ticket
# has no discount.
reserved_item.social_ticketing_referral = (
self.social_ticketing_referral.ambassador
)
# Add points earned per ticket
reserved_item.social_ticketing_unit_points_earned = (
ticket.points.points
if self.social_ticketing.is_point_mode_manual()
else monetary_amount_to_points(
ticket.price_gross, request.event.currency
)
)
# Check if Social Ticketing has incentive discount (not talking point).
# That also means self.discount_code doesn't apply
if self.social_ticketing.is_incentive_discount():
discount_obj = self.social_ticketing.discount
reserved_item.discount_code = (
self.social_ticketing_referral.code
)
if discount_obj:
generate_reserved_discount_items(
reserved_item=reserved_item,
discount=discount_obj,
ticket=ticket,
quantity=quantity,
)
# Save reserved item
reserved_item.save()
return redirect('checkout:checkout', step='start')
return super().get(request, *args, **kwargs)

Consolidation of query (task) in Django

I have the following task. This task can take a few seconds to complete.
How can make the task below make trips and run faster?
class SendMessage(Task):
name = "Sending SMS"
max_retries = 10
default_retry_delay = 3
def run(self, message_id, gateway_id=None, **kwargs):
logging.debug("About to send a message.")
# Because we don't always have control over transactions
# in our calling code, we will retry up to 10 times, every 3
# seconds, in order to try to allow for the commit to the database
# to finish. That gives the server 30 seconds to write all of
# the data to the database, and finish the view.
try:
message = Message.objects.get(pk=message_id)
except Exception as exc:
raise SendMessage.retry(exc=exc)
if not gateway_id:
if hasattr(message.billee, 'sms_gateway'):
gateway = message.billee.sms_gateway
else:
gateway = Gateway.objects.all()[0]
else:
gateway = Gateway.objects.get(pk=gateway_id)
# Check we have a credits to sent me message
account = Account.objects.get(user=message.sender)
# I'm getting the non-cathed version here, check performance!!!!!
if account._balance() >= message.length:
response = gateway._send(message)
if response.status == 'Sent':
# Take credit from users account.
transaction = Transaction(
account=account,
amount=- message.charge,
description="Debit: SMS Sent",
)
transaction.save()
message.billed = True
message.save()
else:
pass
logging.debug("Done sending message.")

Categories