Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating Taler. #158

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
29 changes: 28 additions & 1 deletion doc/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ Example::
This backend does not support fraud detection.

Sofort.com
--------
----------

.. class:: payments.sofort.SofortProvider(key, id, project_id[, endpoint='https://api.sofort.com/api/xml'])

Expand Down Expand Up @@ -304,3 +304,30 @@ Example::


This backend does not support fraud detection.

Taler
-----

.. class:: payments.taler.TalerProvider(self, backend_url, address, name, jurisdiction, instance=None)

This backend implements payments using `Taler <https://taler/net/>`_.

:param backend_url: Backend's base URL.
:param instance: Merchant instance.
:param address: Merchant physical address.
:param name: Merchant/shop name.
:param jurisdiction: Jurisdiction where the merchant operates.

Example::

PAYMENT_VARIANTS = {
'taler': ('payments.taler.TalerProvider',
{'backend_url': 'http://backend.test.taler.net/',
'address': 'US',
'jurisdiction': 'US',
'name': 'Taler tester shop'})
}

.. This backend does not report fraud to Saleor, as fraud in
the classical sense is not possible with Taler. Naturally,
protocol failures are detected and reported (to the wallet).
24 changes: 21 additions & 3 deletions payments/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: 2017-03-13 13:13+0100\n"
"POT-Creation-Date: 2017-10-20 14:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -158,11 +158,11 @@ msgstr ""
msgid "We were unable to complete the transaction. Please try again later."
msgstr ""

#: payments/cybersource/forms.py:51
#: payments/cybersource/forms.py:52
msgid "fingerprint"
msgstr ""

#: payments/cybersource/forms.py:68 payments/stripe/forms.py:52
#: payments/cybersource/forms.py:69 payments/stripe/forms.py:52
msgid "This payment has already been processed."
msgstr ""

Expand Down Expand Up @@ -205,6 +205,24 @@ msgstr ""
msgid "Total payment"
msgstr ""

#: payments/taler/__init__.py:89
msgid ""
"Taler wallet disabled, please go back and either enable the wallet or pick "
"another payment method. Please keep the wallet enabled if you pick Taler "
"again!"
msgstr ""

#: payments/taler/__init__.py:117
msgid ""
"Your wallet seems to have been disabled while processing this payment; your "
"coins didn't get spent though. Please go back in the checkout page and "
"restart the payment with the method you desire."
msgstr ""

#: payments/templates/payments/taler_fallback.html:5
msgid "Payment unsuccessful"
msgstr ""

#: payments/utils.py:8
msgid "Month"
msgstr ""
Expand Down
24 changes: 21 additions & 3 deletions payments/locale/it/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: 2017-03-13 13:13+0100\n"
"POT-Creation-Date: 2017-10-20 14:17+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Marco Badan <[email protected]>, 2017\n"
"Language-Team: Italian (https://www.transifex.com/mirumee/teams/34782/it/)\n"
Expand Down Expand Up @@ -179,11 +179,11 @@ msgstr ""
msgid "We were unable to complete the transaction. Please try again later."
msgstr "Non siamo riusciti a completare la transazione. Riprova più tardi."

#: payments/cybersource/forms.py:51
#: payments/cybersource/forms.py:52
msgid "fingerprint"
msgstr "fingerprint"

#: payments/cybersource/forms.py:68 payments/stripe/forms.py:52
#: payments/cybersource/forms.py:69 payments/stripe/forms.py:52
msgid "This payment has already been processed."
msgstr "Questo pagamento è già stato processato."

Expand Down Expand Up @@ -228,6 +228,24 @@ msgstr "controllo frode"
msgid "Total payment"
msgstr "Totale pagamento"

#: payments/taler/__init__.py:89
msgid ""
"Taler wallet disabled, please go back and either enable the wallet or pick "
"another payment method. Please keep the wallet enabled if you pick Taler "
"again!"
msgstr ""

#: payments/taler/__init__.py:117
msgid ""
"Your wallet seems to have been disabled while processing this payment; your "
"coins didn't get spent though. Please go back in the checkout page and "
"restart the payment with the method you desire."
msgstr ""

#: payments/templates/payments/taler_fallback.html:5
msgid "Payment unsuccessful"
msgstr ""

#: payments/utils.py:8
msgid "Month"
msgstr "Mese"
Expand Down
18 changes: 16 additions & 2 deletions payments/locale/ru/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ msgstr ""
msgid "We were unable to complete the transaction. Please try again later."
msgstr "Мы не смогли провести транзакцию. Пожалуйста, попробуйте позже."

#: payments/cybersource/forms.py:51
#: payments/cybersource/forms.py:52
msgid "fingerprint"
msgstr "отпечаток пальца"

#: payments/cybersource/forms.py:68 payments/stripe/forms.py:52
#: payments/cybersource/forms.py:69 payments/stripe/forms.py:52
msgid "This payment has already been processed."
msgstr "Этот платеж уже был обработан."

Expand Down Expand Up @@ -223,6 +223,20 @@ msgstr "проверка транзакции"
msgid "Total payment"
msgstr "Общая сумма"

#: payments/taler/__init__.py:92
msgid ""
"Taler wallet disabled, please go back and either enable the wallet or pick "
"another payment method. Please keep the wallet enabled if you pick Taler "
"again!"
msgstr ""

#: payments/taler/__init__.py:120
msgid ""
"Your wallet seems to have been disabled while processing this payment; your "
"coins didn't get spent though. Please go back in the checkout page and "
"restart the payment with the method you desire."
msgstr ""

#: payments/utils.py:8
msgid "Month"
msgstr "Месяц"
Expand Down
159 changes: 159 additions & 0 deletions payments/taler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# This file is part of DJANGO-PAYMENTS
# (C) 2017 Taler Systems SA
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# @author Marcello Stanisci

from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
import json
from django.shortcuts import redirect
from ..core import BasicProvider, get_base_url
from .. import RedirectNeeded, PaymentStatus
import re
from django.http import HttpResponse, JsonResponse
import requests
try:
from urllib.parse import urljoin
except ImportError:
from urlparse import urljoin
import logging
from django.conf import settings
from .amount import Amount, BadAmount

logger = logging.getLogger(__name__)

class TalerProvider(BasicProvider):
'''
GNU Taler payment provider
'''

def __init__(self, backend_url, address, name, jurisdiction, instance=None):

# The backend URL is served by the C backend, it is
# used to sign data coming from the frontend and to
# communicate with the exchange.
# Its URL must end with a slash, like 'http://backend.demo.taler.net/'.
self.backend_url = backend_url
# Token which identifies this frontend to the backend. In fact,
# any backend can support multiple frontends, and 'None' is the
# default one.
self.instance = instance
# Physical address and jurisdiction, and shop name
self.address = address
self.name = name
self.jurisdiction = jurisdiction
super(TalerProvider, self)

# This function gets called when the user chooses the
# payment method to use. It will redirect the user to
# the page returning 402+contract_url.
def get_form(self, payment, data=None):
raise RedirectNeeded(self.get_return_url(payment))

# Design note: in django-payments, there is usually ONE URL that
# processes the payment, thus decisions are taken on the basis of
# the _state_ of the payment, rather than a particular endpoint.
# For example, it is perfectly normal having fulfillment and pay
# URL being the same thing.

def process_data(self, payment, request):

# Very first branch taken. It returns the 402 status
# plus the contract generation URL.
if payment.status == PaymentStatus.WAITING:
wallet_not_found_msg = _('Taler wallet disabled. Please' \
' either enable the wallet or pick another payment method.')
data = render_to_string('payments/taler_fallback.html',
{'msg': wallet_not_found_msg})
response = HttpResponse(data, status=402)

response['X-Taler-Contract-Url'] = self.get_return_url(payment)
payment.change_status(PaymentStatus.INPUT)
return response

# Listen for contract generation.
if payment.status == PaymentStatus.INPUT:
try:
total_amount = Amount.parse('%s:%0.2f' % (payment.currency, payment.total)).dump()
order = {
'summary': payment.message,
'nonce': request.GET.get('nonce'),
'amount': total_amount,
'products': [{'description': payment.description,
'quantity': 1,
'product_id': 0,
'price': total_amount}],
'fulfillment_url': self.get_return_url(payment),
'pay_url': self.get_return_url(payment),
'merchant': {
'instance': self.instance,
'address': self.address,
'name': self.name,
'jurisdiction': self.jurisdiction},
'extra': {}}
except BadAmount as e:
logger.error('Malformed amount: %s' % e.faulty_str)
data = {'error': _('Internal error generating contract'),
'detail': _('Could not parse amount')}
return JsonResponse(data, status=500)

try:
r = requests.post(urljoin(self.backend_url, 'proposal'),
json={'order': order})
except requests.RequestException as e:
logger.error(e)
return JsonResponse({'error': 'Internal server error',
'detail': 'Could not reach the backend'}, status=500)
if r.status_code == 200:
payment.change_status(PaymentStatus.PREAUTH)
return JsonResponse(r.json(), status=r.status_code)

# This is responsible to both execute the payment and receive it.
# When the wallet attempts to GET it, it returns the 402 which
# executes the payment, whereas POSTing to it triggers the /pay behaviour.
if payment.status == PaymentStatus.PREAUTH:
if request.method == 'POST':
try:
r = requests.post(urljoin(self.backend_url, 'pay'),
json=json.loads(request.body.decode('utf-8')))
except requests.RequestException as e:
logger.error(e)
return JsonResponse({'error': 'Internal server error',
'detail': 'Could not reach the backend'}, status=500)
if r.status_code == 200:
payment.change_status(PaymentStatus.CONFIRMED)
return JsonResponse(r.json(), status=r.status_code)

if request.method == 'GET':
wallet_not_found_msg = _('Taler wallet disabled. Please' \
' either enable the wallet or pick another payment method.')
data = render_to_string('payments/taler_fallback.html',
{'msg': wallet_not_found_msg})
response = HttpResponse(data, status=402)
response['X-Taler-Contract-Url'] = self.get_return_url(payment)
response['X-Taler-Offer-Url'] = get_base_url()
return response

# Taken when the payment has gone through, redirect to persistent
# fulfillment page.
if payment.status == PaymentStatus.CONFIRMED:
# returns absolute url
return redirect(payment.get_success_url())

# This should _never_ happen
return JsonResponse({'error': 'Internal server error',
'detail': 'Unknown payment status!'}, status=500)
Loading