Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion askell/webhook_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ def payment_created(request, event, data):
def payment_changed(request, event, data):
from .models import Payment
if event == 'payment.changed':
payment = Payment.objects.get(uuid=data['uuid'])
# payment.changed can arrive before payment.created has been handled,
# e.g. when a stored card is charged and settles right away.
payment_data = {key: data[key] for key in Payment.KEYS_TO_COPY}
payment, _ = Payment.objects.get_or_create(uuid=data['uuid'], defaults=payment_data)
for attr, val in data.items():
setattr(payment, attr, val)
payment.save()
Expand Down
2 changes: 1 addition & 1 deletion release.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
set -eu

VERSION="0.1.25"
VERSION="0.1.26"

python3 -m twine check "dist/django_askell-${VERSION}"*
python3 -m twine upload "dist/django_askell-${VERSION}"*
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

setup(
name='django-askell',
version='0.1.25',
version='0.1.26',
packages=find_packages(exclude=['test_project', 'test_project.*']),
include_package_data=True,
license='MIT',
Expand Down
42 changes: 42 additions & 0 deletions test_project/test_app/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import base64
import hashlib
import hmac
import http.client
import json
from unittest.mock import patch

from django.conf import settings
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
Expand Down Expand Up @@ -180,3 +185,40 @@ def test_webhook_payment_settled(self):
self.assertEqual(payment_count_before, payment_count_after)
payment.refresh_from_db()
self.assertTrue(payment.state == "settled")

def post_webhook(self, event, data):
body = json.dumps({'event': event, 'ref': None, 'sender': 'tumi@overcast.is', 'data': data}).encode()
digest = hmac.new(settings.ASKELL_WEBHOOK_SECRET.encode(), body, hashlib.sha512).digest()
headers = {
'Hook-Hmac': base64.b64encode(digest).decode(),
'Hook-Event': event,
}
return self.client.post('/askell/webhook/', data=body, headers=headers, content_type='application/json')

def test_webhook_payment_changed_before_payment_created(self):
# A payment charged straight away can settle before its payment.created
# webhook has been handled, so payment.changed may arrive first.
data = {
"uuid": "5f0c1e2a-7d4b-4c55-9a51-3f0f5b0c2d11",
"amount": "2000.0000",
"currency": "ISK",
"description": "wesd",
"reference": "q324erdf2",
"state": "settled",
"created_at": "2024-02-16T12:52:36.334898Z",
"updated_at": "2024-02-16T12:52:37.450551Z",
"transactions": [],
}
response = self.post_webhook('payment.changed', data)
self.assertEqual(response.status_code, 200)

payment = Payment.objects.get(uuid=data['uuid'])
self.assertEqual(payment.state, 'settled')
self.assertEqual(payment.reference, 'q324erdf2')

# The late payment.created must not reset the payment to pending.
response = self.post_webhook('payment.created', dict(data, state='pending'))
self.assertEqual(response.status_code, 200)

payment.refresh_from_db()
self.assertEqual(payment.state, 'settled')
Loading