Hello ive been having problems with web hooks in django i need a way to notify a user that he/she has made a successful payment am notified about a successful web hook from adding a URL to my processor that sends a payment successful request to my web hook how can i show this to the user to notify if its successful
my code is: this is where the user sends checkout details to my server
hopping to add a waiting spinner that resolves to a tick or an X depending if the user has paid
<script>
document.getElementById("checkout-btn").addEventListener('click', analyzeText());
function analyzeText(){
var wallet = document.getElementById('wallet').value;
var phone = document.getElementById('phone').value;
var order_id = document.getElementById('order_id').value;
$.ajax({
type: "POST",
url: "{% url 'orders:Checkout' %}", /* Call python function in this script */
data: {csrfmiddlewaretoken: '{{ csrf_token }}',
wallet: wallet,
phone:phone,
order_id:order_id,
}, /* Passing the text data */
success: callback
});
}
function callback(response){
console.log(response);
}
</script>
the view this is sent to does this and its basically just calling the checkout API which now deals with the checkout if succesful or failed a webhook should be sent to my callback url
def processOrder(request):
def spektraCheckoutLink(amount, reference, transaction_id, order_id):
url = "https://api.spektra.co/api/v1/payments/pay-in"
# request payload
# spektraAccountName is Optional, used for subaccount-specific payments
payload = {
"amount": amount,
"account": phone,
"currency": "KES",
"description": reference,
}
# pass authorization token obtained during authentication in headers
authcode = str(auth())
headers = {
'Content-Type': "application/json",
'Authorization': "Bearer " + authcode
}
response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
# Print response data
print(response.json())
data = response.json()
return True
if request.method == "POST":
global order_id
try:
order_id = request.POST['order_id']
except Exception as e:
print(e)
print(traceback.format_exc())
raise HttpResponseServerError()
order = Order.objects.get(id=order_id)
try:
wallet = request.POST['wallet']
order.wallet = wallet
except Exception as e:
print(e)
print(traceback.format_exc())
raise HttpResponseServerError()
try:
phone = request.POST['phone']
order.phone = phone
except Exception as e:
print(e)
print(traceback.format_exc())
raise HttpResponseServerError()
order.save()
try:
current_user = get_user_model().objects.get(id=request.user.id)
Transaction.objects.get_or_create(user=current_user, order=order, amount=order.price, phone=order.phone)
trans = Transaction.objects.get(order=order)
link = spektraCheckoutLink(order.price, order.order_reference, trans.id, order.id)
return redirect(link)
except Exception as e:
print(e)
print(traceback.format_exc())
# redirect to 404
raise HttpResponseServerError()
what i don't know is how i'm going to change the spinner status on the users front end when the callback arrives to notify them if the transaction was successful or not
this is my webhook view which updates the order to paid my question is how can i notify the user in his browser that the payment is complete i dont know how to do it
def processOrderStatus(request):
# updates transaction number and status
data = json.loads(request.body)
order = Order.objects.get(phone=data['account'])
order.order_status = order.PAID
order.save
id = request.user.id
return redirect('orders:Orders', user_id=id)
Knowing your needs I will try to put you on a couple of possible ways to accomplish what you need, the first one is using a javascript interval with and ajax call to get the order status every 5 seconds, the second approach will use a long-polling strategy.
The following code must be adapted to your needs and it's not tested, take it as a guide.
AJAX requests using an interval of 5 seconds
Create a view in your django app which will receive an order PK and return it's status in a json like
{
'pk': XX,
'status': ['processing_payment', 'paid', 'refused', ...]
}
(Adjust the status values on your own)
Python view
class OrderStatusView(View):
""" just return a json with the current status of the order """
def get(self, request, *args, **kwargs):
order_pk = kwargs.get('pk')
order = Order.objects.get(pk=order_pk)
ret_values = {'pk': order.pk, 'status': order.status}
return HttpResponse(json.dumps(ret_values))
The javascript part
var check_order_status = function (order_pk) {
$.ajax({
url: '/ajax/order/' + order_pk + '/status/',
dataType: 'json',
success: function (data) {
switch (data['status']) {
case 'processing_payment':
// just keep the spinner on
break;
case 'paid':
// Change the spinner to a green check or whatever you prefer
// Stop sending requests
clearInterval(order_status_interval);
break;
case 'refused':
// Change the spinner to a red cross or whatever fit better to your app
// Stop sending requests
clearInterval(order_status_interval);
break;
}
},
error: function (jqXHR, textStatus, errorThrown) {}
});
};
$(function () {
var order_status_interval = setInterval(function() { check_order_status(ORDER_PK); }, 5000);
});
As you can see we will send a request to the server every 5 seconds, if it returns an 'ending' status like paid or refused you should update your layout and not do further requests, otherwise, keep looking for a change.
Long-polling
Using a long-polling strategy will be similar but instead of sending a request to your backend every 5 seconds, your view should implement a sleep time and check for a status change keeping the thread with the client open, if a timeout is raised, the front-end must re-send the request again.
Your view shold look something like this
class OrderStatusView(View):
""" Wait until order status changes to paid or refused to long poll for a change """
def get(self, request, *args, **kwargs):
order_pk = kwargs.get('pk')
order = Order.objects.get(pk=order_pk)
while order.status not in ['paid', 'refused']:
time.sleep(5) # sleep for 5 seconds
order = Order.objects.get(pk=order_pk)
# order status is a final one
ret_values = {'pk': order.pk, 'status': order.status}
return HttpResponse(json.dumps(ret_values))
Your javascript code should control if a timeout is returned, in that case, do a new request.
var check_order_status = function (order_pk) {
$.ajax({
url: '/ajax/order/' + order_pk + '/status/',
dataType: 'json',
success: function (data) {
switch (data['status']) {
case 'processing_payment':
// just keep the spinner on
break;
case 'paid':
// Change the spinner to a green check or whatever you prefer
// Stop sending requests
clearInterval(order_status_interval);
break;
case 'refused':
// Change the spinner to a red cross or whatever fit better to your app
// Stop sending requests
clearInterval(order_status_interval);
break;
}
},
error: function (jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
// timeout http codes
// 408 Request timeout
// 504 Gateway timeout
case 408:
case 504:
check_order_status(ORDER_PK);
break;
default:
// Throw some error
}
}
});
};
$(function () {
check_order_status(ORDER_PK);
});
Keep in mind that this approach can consume lot's of threads in high traffic conditions which can, at some point, affect your server performance.
WebSockets
The most over-head option will be to implement websockets using django-channels or centrifugo but I will not give it a try just for this.
THE SIMPLEST ONE
Since you don't know when your webhook will be called, consider to just send an email to your user notifying the payment result and with a CTA to the order details, I think that this is the easiest way to keep you user informed.
Hope this puts U on the right way
Related
Hello I have a question and a doubt, I am in a view, when I give a button I do the following code
function pagar_recibo(id) {
$.ajax ({
delay: 250,
type: 'GET',
url: '/general/recibo/add',
data: {
'id': id,
'action_al': 'add_alumnos',
},
});
};
This button is a href that goes to another url, that is to say it makes another request to the server with GET then in the new view in the get_context_data I have the following
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['arg'] = json.dumps(self.alum())
# context['arg'] = self.alum()
return context
and the function is as follows
def alum(self, **kwargs):
data = []
try:
action = self.request.GET.get('action_al', False)
print(action)
if action == False:
return
elif action == 'add_alumnos':
data = []
id = int(self.request.GET['id'])
Alumnos = Alumno.objects.filter(pk=id)
for i in Alumnos:
item = i.toJSON()
item['text'] = i.get_full_name()
data.append(item)
print(data)
except Exception as e:
data['error'] = str(e)
return data
This is how I pick it up in the template
window.data = {{ arg|safe }};
The problem is that one GET request is empty, the url change and the other is the ajax request with the given key and value, but I always get empty windows data, how could I fix that?
It could be done with a POST request? then how do I collect the value in the js file?
Please I would need help. Regards
I am building a code editor using the Hackerearth API.I have made the code to send a asynchronous API Request as it would speed up performance and reduce waiting time.
I referred their docs about sending an async request.I need to specify a callback url.Currently my project is running locally.So I couldn't figure out how to specify the callback URL and render out the Response from that callback URL.The logic to process the response received at the callback url is also specified in their docs.
def compileCode(request):
if request.is_ajax():
source = request.POST.get('source')
lang = request.POST.get('lang')
client_secret = settings.CLIENT_SECRET
data = {
"client_secret": client_secret,
"async": 1,
'id': 123,
'callback': **what to do here**,
"source": source,
"lang": lang,
}
res = requests.post(RUN_URL, data=data)
return JsonResponse(res.json(), safe=False)
return HttpResponseBadRequest()
Code to process the Response from the callback URL
def api_response(request):
payload = request.POST.get('payload', '')
payload = json.loads(payload)
run_status = payload.get('run_status')
o = run_status['output']
return HttpResponse('API Response Recieved!')
Any help is welcome :)
A callback URL "calls back" a web address rather than a bit of code and it can be invoked by API method, you can call after it's done. That URL can be anything. It doesn't have to be a static URL. Often it is a script to perform certain functions.
As here you don't need to perform anything after receiving results.
You don't need to pass the callback url, it will work even without it.
I made it work by just passing by below code.
RUN_URL = "https://api.hackerearth.com/v3/code/run/"
CLIENT_SECRET = 'your-client-secret-from-hackerearth'
data = {
'client_secret': CLIENT_SECRET,
'async': 1,
'source': source,
'lang': lang,
}
r = requests.post(RUN_URL, data=data)
I'm trying to implement HTTP long polling for a web request, but can't seem to find a suitable example in the Channels documentation, everything is about Web Sockets.
What I need to do when consuming the HTTP message is either:
wait for a message on a Group that will be sent when a certain model is saved (using signals probably)
wait for a timeout, if no message is received
and then return something to the client.
Right now I have the code that can be seen in the examples:
def http_consumer(message):
# Make standard HTTP response - access ASGI path attribute directly
response = HttpResponse("Hello world! You asked for %s" % message.content['path'])
# Encode that response into message format (ASGI)
for chunk in AsgiHandler.encode_response(response):
message.reply_channel.send(chunk)
So I have to return something in this http_consumer that will indicate that I have nothing to send, for now, but I can't block here. Maybe I can just not return anything? And then I have to catch the new message on a specific Group, or reach the timeout, and send the response to the client.
It seems that I will need to store the message.reply_channel somewhere so that I can later respond, but I'm at a loss as to how to:
catch the group message and generate the response
generate a response when no message was received (timeout), maybe the delay server can work here?
So, the way I ended up doing this is described below.
In the consumer, if I find that I have no immediate response to send, I will store the message.reply_channel on a Group that will be notified in the case of relevant events, and schedule a delayed message that will be triggered when the max time to wait is reached.
group_name = group_name_from_mac(mac_address)
Group(group_name).add(message.reply_channel)
message.channel_session['will_wait'] = True
delayed_message = {
'channel': 'long_polling_terminator',
'content': {'mac_address': mac_address,
'reply_channel': message.reply_channel.name,
'group_name': group_name},
'delay': settings.LONG_POLLING_TIMEOUT
}
Channel('asgi.delay').send(delayed_message, immediately=True)
Then, two things can happen. Either we get a message on the relevant Group and a response is sent early, or the delayed message arrives signalling that we have exhausted the time we had to wait and must return a response indicating that there were no events.
In order to trigger the message when a relevant event occurs I'm relying on Django signals:
class PortalConfig(AppConfig):
name = 'portal'
def ready(self):
from .models import STBMessage
post_save.connect(notify_new_message, sender=STBMessage)
def notify_new_message(sender, **kwargs):
mac_address = kwargs['instance'].set_top_box.id
layer = channel_layers['default']
group_name = group_name_from_mac(mac_address)
response = JsonResponse({'error': False, 'new_events': True})
group = Group(group_name)
for chunk in AsgiHandler.encode_response(response):
group.send(chunk)
When the timeout expires, I get a message on the long_polling_terminator channel and I need to send a message that indicates that there are no events:
def long_polling_terminator(message):
reply_channel = Channel(message['reply_channel'])
group_name = message['group_name']
mac_address = message['mac_address']
layer = channel_layers['default']
boxes = layer.group_channels(group_name)
if message['reply_channel'] in boxes:
response = JsonResponse({'error': False, 'new_events': False})
write_http_response(response, reply_channel)
return
The last thing to do is remove this reply_channel from the Group, and I do this in a http.disconnect consumer:
def process_disconnect(message, group_name_from_mac):
if message.channel_session.get('will_wait', False):
reply_channel = Channel(message['reply_channel'])
mac_address = message.channel_session['mac_address']
group_name = group_name_from_mac(mac_address)
Group(group_name).discard(reply_channel)
I'm using AngularJS for the front-end and Django for the backend of a web app I'm working on. Right now I'm working on logging in users and I'm having a strange problem. Heres the relevant Angular code:
app.factory('AuthService', ["$http", "$q", "Session", "URL", function($http, $q, Session, URL) {
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post(URL.login, credentials)
.then(function(data, status, headers, config) {
data=data.data; //WHY DOES THIS WORK?
if (data.success == true) {
alert("logged in");
Session.create(credentials.username, data.api_key);
deferred.resolve();
}
else {
deferred.reject("Login failed!");
}
}, function(data, status, headers, config) {
deferred.reject("Login failed!");
});
return deferred.promise
},
And here is the corresponding Django view:
def login_user(request):
'''
Given a username and password, returns the users API key.
'''
if request.method == 'POST':
username = request.POST.get('username',None)
password = request.POST.get('password',None)
user = authenticate(username=username,password=password)
if user is not None:
api_key = ApiKey.objects.get(user=user)
response_data = {}
response_data["api_key"] = str(api_key).split(" ")[0]
response_data["success"] = True
return HttpResponse(json.dumps(response_data), content_type="application/json")
else:
return HttpResponse(json.dumps({"username":username,"success":False}),content_type="application/json")
return HttpResponseBadRequest()
When the user logs in a POST request is sent and handled by the above Django code. The response is then picked up by the AngularJS code above. As you can see the then() method in the Angular code takes the usual four parameters: data, status, config and headers. I expect to see data contain the dictionary output from the Django code, appropriately serialized into a JSON object.
However what happens is that the only parameter of the then() method which is not undefined is data, and this contains EVERYTHING; headers, data, status code,etc.
The line commented 'WHY DOES THIS WORK' fixes the problem, by accessing the data inside. However, I want to know why this is happening and if there is any way to avoid this. My best guess is that it has something to do with the way Django serializes a response but I'm not sure.
I'm using Django 1.6.5.
That is actually how Angular promises work according to the docs. Here is the relevant quote.
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response. See the API signature and type info below for more details.
The emphasis was mine.
I'm trying to write a unit test that posts a mock event to my stripe webhook.
I went and pulled an event from my logs and tried sending that with the test mode enabled, but I (somewhat predictably) got an error:
a similar object exists in live mode, but a test mode key was used to make this request.
Fair enough. So how do I create a mock event that I can actually send to my webhook and get it processed correctly?
Here's my current test:
class StripeTest(TestCase):
def setUp(self):
self.client = Client()
def test_receiving_a_callback(self):
with open('donate/test_assets/stripe_event.json', 'r') as f:
stripe_event = simplejson.load(f)
self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(stripe_event),
content_type='application/json')
The solution is to create your own mock data. In the code below we create a test payment by creating a stripe token, then submitting it via the front end (at the /donate/ endpoint).
Once the front end has worked properly, you can get the event from stripe and then send it to your development machine's webhook endpoint.
This is more work than I expected, and I don't love that my tests are hitting the network, but it seems to be a decent solution. I feel a lot more confident about my payments than before.
def test_making_a_donation_and_getting_the_callback(self):
"""These two tests must live together because they need to be done sequentially.
First, we place a donation using the client. Then we send a mock callback to our
webhook, to make sure it accepts it properly.
"""
stripe.api_key = settings.STRIPE_SECRET_KEY
# Create a stripe token (this would normally be done via javascript in the front
# end when the submit button was pressed)
token = stripe.Token.create(
card={
'number': '4242424242424242',
'exp_month': '6',
'exp_year': str(datetime.today().year + 1),
'cvc': '123',
}
)
# Place a donation as an anonymous (not logged in) person using the
# token we just got
r = self.client.post('/donate/', data={
'amount': '25',
'payment_provider': 'cc',
'first_name': 'Barack',
'last_name': 'Obama',
'address1': '1600 Pennsylvania Ave.',
'address2': 'The Whitehouse',
'city': 'DC',
'state': 'DC',
'zip_code': '20500',
'email': 'barack#freelawproject.org',
'referrer': 'footer',
'stripeToken': token.id,
})
self.assertEqual(r.status_code, 302) # 302 because we redirect after a post.
# Get the stripe event so we can post it to the webhook
# We don't know the event ID, so we have to get the latest ones, then filter...
events = stripe.Event.all()
event = None
for obj in events.data:
if obj.data.object.card.fingerprint == token.card.fingerprint:
event = obj
break
self.assertIsNotNone(event, msg="Unable to find correct event for token: %s" % token.card.fingerprint)
# Finally, we can test the webhook!
r = self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(event),
content_type='application/json')
# Does it return properly?
self.assertEqual(r.status_code, 200)