Skip to content

Commit

Permalink
feat: show receipt link to document
Browse files Browse the repository at this point in the history
Add receipt link to a proper financial PDF document.
The receipt link comes from NAU Financial Manager system.
This service proxies the transactions/orders and converts
them to the PDF link from iLink.
#1
fccn/nau-technical#25
  • Loading branch information
igobranco committed Mar 19, 2024
1 parent a4a6daa commit 5162675
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 28 deletions.
9 changes: 8 additions & 1 deletion nau_extensions/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ class NauExtensionsConfig(AppConfig):
plugin_app = {
"url_config": {
"ecommerce": {
"namespace": "nau",
"namespace": "ecommerce_nau_extensions",
}
},
}

def ready(self):
print("Nau extensions loading...")

# Fix the incompatibility of installed packages "rest_framework" and "Markdown":
# md.preprocessors.register(CodeBlockPreprocessor(), 'highlight', 40)
# Disable the rest_framework to use the markdown.
from rest_framework import \
compat # pylint: disable=import-outside-toplevel
compat.md_filter_add_syntax_highlight = lambda md: False

# Register signals
import nau_extensions.signals # pylint: disable=import-outside-toplevel,unused-import
28 changes: 28 additions & 0 deletions nau_extensions/financial_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,31 @@ def send_to_financial_manager_if_enabled(
basket_transaction_integration.response = response_json
basket_transaction_integration.save()
return basket_transaction_integration


def get_receipt_link(order):
"""
Get the Receipt Link from NAU Financial Manager, this will transform the order_number to the receipt link.
"""
site = order.basket.site
if is_financial_manager_enabled(site):
transaction_id = order.basket.order_number
receipt_link_url = _get_financial_manager_setting(site, "receipt-link-url")
if not receipt_link_url.endswith('/'):
receipt_link_url += "/"
receipt_link_url += transaction_id
token = _get_financial_manager_setting(site, "token")
try:
response = requests.post(
receipt_link_url,
headers={"Authorization": token},
timeout=10,
)
except Exception as e: # pylint: disable=broad-except
logger.exception("Error can't get receipt link for transaction_id [%s] error: [%s]", transaction_id, e)
finally:
content = response.content()
logger.info("Get receipt link status: [%d] response: [%s]", response.status_code, content)
if response.status_code == 200:
return content
return None
26 changes: 15 additions & 11 deletions nau_extensions/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-07 15:51+0000\n"
"POT-Creation-Date: 2024-03-19 17:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand All @@ -18,38 +18,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: nau_extensions/models.py:37
#: nau_extensions/models.py:36
#: nau_extensions/templates/nau_extensions/checkout/basket_billing_information/vatin.html:21
msgid "VAT Identification Number (VATIN)"
msgstr ""

#: nau_extensions/models.py:41
#: nau_extensions/models.py:40
msgid ""
"The value-added tax identification number or VAT identification number can "
"be used to identify a business or a taxable person in the European Union."
msgstr ""

#: nau_extensions/models.py:48
#: nau_extensions/models.py:47
msgid "Basket Billing Information"
msgstr ""

#: nau_extensions/models.py:49
#: nau_extensions/models.py:48
msgid "Basket Billing Informations"
msgstr ""

#: nau_extensions/models.py:69
#: nau_extensions/models.py:68
msgid "Incorrect vatin format for country"
msgstr ""

#: nau_extensions/models.py:92
#: nau_extensions/models.py:102
msgid "To be sent"
msgstr ""

#: nau_extensions/models.py:93
#: nau_extensions/models.py:103
msgid "Sent with success"
msgstr ""

#: nau_extensions/models.py:94
#: nau_extensions/models.py:104
msgid "Sent with error"
msgstr ""

Expand Down Expand Up @@ -100,10 +100,14 @@ msgstr ""
msgid "VATIN"
msgstr ""

#: nau_extensions/views.py:151
#: nau_extensions/templates/nau_extensions/checkout/receipt_partial.html:4
msgid "Download receipt"
msgstr ""

#: nau_extensions/views.py:160
msgid "Address saved"
msgstr ""

#: nau_extensions/views.py:189
#: nau_extensions/views.py:198
msgid "VATIN saved"
msgstr ""
Binary file modified nau_extensions/locale/pt_PT/LC_MESSAGES/django.mo
Binary file not shown.
26 changes: 15 additions & 11 deletions nau_extensions/locale/pt_PT/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-07 15:51+0000\n"
"POT-Creation-Date: 2024-03-19 17:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand All @@ -17,12 +17,12 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: nau_extensions/models.py:37
#: nau_extensions/models.py:36
#: nau_extensions/templates/nau_extensions/checkout/basket_billing_information/vatin.html:21
msgid "VAT Identification Number (VATIN)"
msgstr "Número de Identificação Fiscal (NIF)"

#: nau_extensions/models.py:41
#: nau_extensions/models.py:40
msgid ""
"The value-added tax identification number or VAT identification number can "
"be used to identify a business or a taxable person in the European Union."
Expand All @@ -31,27 +31,27 @@ msgstr ""
"ou número de identificação para efeitos de IVA pode ser utilizado para "
"identificar uma empresa ou um sujeito passivo na União Europeia."

#: nau_extensions/models.py:48
#: nau_extensions/models.py:47
msgid "Basket Billing Information"
msgstr "Informação de faturação"

#: nau_extensions/models.py:49
#: nau_extensions/models.py:48
msgid "Basket Billing Informations"
msgstr "Informações de faturação"

#: nau_extensions/models.py:69
#: nau_extensions/models.py:68
msgid "Incorrect vatin format for country"
msgstr "Formato de dados incorreto para o país"

#: nau_extensions/models.py:92
#: nau_extensions/models.py:102
msgid "To be sent"
msgstr "A enviar"

#: nau_extensions/models.py:93
#: nau_extensions/models.py:103
msgid "Sent with success"
msgstr "Enviado com sucesso"

#: nau_extensions/models.py:94
#: nau_extensions/models.py:104
msgid "Sent with error"
msgstr "Enviado com erro"

Expand Down Expand Up @@ -102,10 +102,14 @@ msgstr "País"
msgid "VATIN"
msgstr "NIF"

#: nau_extensions/views.py:151
#: nau_extensions/templates/nau_extensions/checkout/receipt_partial.html:4
msgid "Download receipt"
msgstr "Descarregar recibo"

#: nau_extensions/views.py:160
msgid "Address saved"
msgstr "Endereço guardado"

#: nau_extensions/views.py:189
#: nau_extensions/views.py:198
msgid "VATIN saved"
msgstr "NIF guardado"
26 changes: 26 additions & 0 deletions nau_extensions/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Serializers for data manipulated by ecommerce API endpoints."""

import logging

from nau_extensions.financial_manager import get_receipt_link
from oscar.core.loading import get_model
from rest_framework import serializers

logger = logging.getLogger(__name__)

Order = get_model('order', 'Order')


class OrderReceiptLinkSerializer(serializers.ModelSerializer):
"""Serializer for parsing order data with only the receipt link."""
receipt_link = serializers.SerializerMethodField()

def get_receipt_link(self, obj):
# return "https://ilink.pt/xpto.pdf"
return get_receipt_link(obj)

class Meta:
model = Order
fields = (
'receipt_link',
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{% load i18n %}

<a id="receiptLink" href="#" style="display: none;">
{% trans "Download receipt" as tmsg %}{{ tmsg | force_escape }}
</a>
<script>
function httpGetAsync(theUrl, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(xmlHttp.responseText);
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}

httpGetAsync('/payment/nau_extensions/receipt-link/?order_id={{ order.id }}', function( data ) {
var receiptLink = document.getElementById("receiptLink");

// update the link to element
receiptLink.setAttribute('href', data);

// make receipt link visible
receiptLink.style.display = 'block';

// Declare a fragment:
var fragment = document.createDocumentFragment();

// Append desired element to the fragment:
fragment.appendChild(receiptLink);

// Append fragment to desired element:
document.getElementsByClassName('confirm-message')[0].appendChild(fragment);
});
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from io import StringIO

import mock
from django.core.management import call_command
from django.test import TestCase
Expand Down
48 changes: 47 additions & 1 deletion nau_extensions/tests/test_financial_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import requests
from django.test import override_settings
from nau_extensions.financial_manager import (
send_to_financial_manager_if_enabled, sync_request_data)
get_receipt_link, send_to_financial_manager_if_enabled, sync_request_data)
from nau_extensions.models import (BasketBillingInformation,
BasketTransactionIntegration)
from nau_extensions.tests.factories import MockResponse, create_basket
Expand Down Expand Up @@ -443,3 +443,49 @@ def test_send_to_financial_manager_without_basket_billing_information(self):

self.assertEqual(bti.state, BasketTransactionIntegration.SENT_WITH_SUCCESS)
self.assertEqual(mock_response_json_data, bti.response)

@override_settings(
NAU_FINANCIAL_MANAGER={
"edx": {
"receipt-link-url": "https://finacial-manager.example.com/api/billing/receipt-link/",
"token": "a-very-long-token",
},
},
)
@mock.patch.object(requests, "post", return_value=MockResponse(
json_data="https://example.com/somereceipt.pdf",
status_code=200,
))
def test_get_receipt_link(self, mock_fm_receipt_link):
"""
Test the `get_receipt_link` method.
"""
partner = PartnerFactory(short_code="edX")

site_configuration = SiteConfigurationFactory(partner=partner)
site_configuration.site = SiteFactory(name="openedx")
site = site_configuration.site

course = CourseFactory(
id="course-v1:edX+DemoX+Demo_Course",
name="edX Demonstration Course",
partner=partner,
)
honor_product = course.create_or_update_seat("honor", False, 0)
verified_product = course.create_or_update_seat("verified", True, 10)

owner = UserFactory(email="ecommerce@example.com")

# create an empty basket so we know what it's inside
basket = create_basket(owner=owner, empty=True, site=site)
basket.add_product(verified_product)
basket.add_product(honor_product)
basket.save()

# creating an order will mark the card submitted
order = create_order(basket=basket)

link = get_receipt_link(order)
mock_fm_receipt_link.assert_called_once_with(f"https://finacial-manager.example.com/api/billing/receipt-link/{basket.order_number}", headers={'Authorization': 'a-very-long-token'}, timeout=10)

self.assertEqual(link, "https://example.com/somereceipt.pdf")
41 changes: 41 additions & 0 deletions nau_extensions/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# import mock
# from django.urls import reverse
# from nau_extensions.serializers import OrderReceiptLinkSerializer

# from ecommerce.courses.tests.factories import CourseFactory
# from ecommerce.extensions.test.factories import create_basket, create_order
# from ecommerce.tests.factories import UserFactory
# from ecommerce.tests.testcases import TestCase


# class NAUExtensionsViewTests(TestCase):

# @mock.patch.object(OrderReceiptLinkSerializer, "get_receipt_link")
# def test_view_receipt_link(self, mock_get_receipt_link):
# """
# Test the Receipt Link View
# """
# # create data for test
# course = CourseFactory(id='a/b/c', name='Demo Course', partner=self.partner)
# product = course.create_or_update_seat('test-certificate-type', False, 20)
# basket = create_basket(site=self.site, owner=UserFactory(), empty=True)
# basket.add_product(product)
# basket.save()

# # Save an Order for the Basket, to mock has we already received callback.
# order = create_order(basket=basket)
# order.save()

# # mock the call financial manager that will return the receipt link
# mock_get_receipt_link.return_value = [{
# "https://example.com/receipt-link/somedocument.pdf"
# }]

# response = self.client.get(
# # reverse("ecommerce_nau_extensions:receipt_link_view")+ "?id=" + order.id,
# f"/payment/nau_extensions/receipt-link/?id={order.id}",
# )

# mock_get_receipt_link.assert_called_once_with(order)

# self.assertEqual("https://example.com/receipt-link/somedocument.pdf", response.content)
9 changes: 8 additions & 1 deletion nau_extensions/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf.urls import url
from nau_extensions.views import (
BasketBillingInformationAddressCreateUpdateView,
BasketBillingInformationVATINCreateUpdateView)
BasketBillingInformationVATINCreateUpdateView, ReceiptLinkView)

app_name = "ecommerce_nau_extensions"

Expand All @@ -17,4 +17,11 @@
BasketBillingInformationVATINCreateUpdateView.as_view(),
name="nau-basket-billing-information-vatin",
),

url(
r"receipt-link/$",
ReceiptLinkView.as_view(),
name="receipt_link_view",
),

]
Loading

0 comments on commit 5162675

Please sign in to comment.