-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Martijn Smit
committed
Oct 3, 2017
0 parents
commit 5f23073
Showing
22 changed files
with
548 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Mollie addon for Odoo 10© | ||
This is the official Mollie addon for Odoo 10© | ||
|
||
## Installation | ||
For installation instructions please refer to the odoo docs: | ||
http://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html#from-zip-archive-install | ||
|
||
## Configuration | ||
Go to Invoicing section -> Payments -> Payment Acquirers -> Mollie. | ||
Add API Keys (test and/or live) from your Mollie Account. | ||
|
||
![alt text](/images/odoo_configuration.png "Odoo mollie configuration example") | ||
|
||
When Mollie acquirer is configured correctly, you can see Mollie payment option at the time of checkout. | ||
|
||
Shopper will then be redirected to the Mollie payment method selection screen. | ||
|
||
After a succesfull payment, a confirmation is shown to the shopper: | ||
|
||
![alt text](/images/payment_confirmation.png "Odoo mollie payment confirmation") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import models | ||
import controllers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# -*- encoding: utf-8 -*- | ||
{ | ||
'name': 'Mollie acquirer for online payments', | ||
'version': '1.10', | ||
'author': 'BeOpen NV', | ||
'website': 'http://www.beopen.be', | ||
'category': 'eCommerce', | ||
'description': "", | ||
'depends': ['payment','website_sale'], | ||
'data': [ | ||
'views/mollie.xml', | ||
'views/payment_acquirer.xml', | ||
'data/mollie.xml', | ||
], | ||
'installable': True, | ||
'currency': 'EUR', | ||
'images': ['images/main_screenshot.png'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import json | ||
import logging | ||
import requests | ||
import werkzeug | ||
import pprint | ||
|
||
|
||
from odoo import http, SUPERUSER_ID | ||
from odoo.http import request | ||
from odoo.addons.payment.models.payment_acquirer import ValidationError | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class MollieController(http.Controller): | ||
_notify_url = '/payment/mollie/notify/' | ||
_redirect_url = '/payment/mollie/redirect/' | ||
_cancel_url = '/payment/mollie/cancel/' | ||
|
||
@http.route([ | ||
'/payment/mollie/notify'], | ||
type='http', auth='none', methods=['GET']) | ||
def mollie_notify(self, **post): | ||
cr, uid, context = request.cr, SUPERUSER_ID, request.context | ||
request.env['payment.transaction'].sudo().form_feedback(post, 'mollie') | ||
return werkzeug.utils.redirect("/shop/payment/validate") | ||
|
||
@http.route([ | ||
'/payment/mollie/redirect'], type='http', auth="none", methods=['GET']) | ||
def mollie_redirect(self, **post): | ||
cr, uid, context = request.cr, SUPERUSER_ID, request.context | ||
request.env['payment.transaction'].sudo().form_feedback(post, 'mollie') | ||
return werkzeug.utils.redirect("/shop/payment/validate") | ||
|
||
@http.route([ | ||
'/payment/mollie/cancel'], type='http', auth="none", methods=['GET']) | ||
def mollie_cancel(self, **post): | ||
cr, uid, context = request.cr, SUPERUSER_ID, request.context | ||
request.env['payment.transaction'].sudo().form_feedback(post, 'mollie') | ||
return werkzeug.utils.redirect("/shop/payment/validate") | ||
|
||
@http.route([ | ||
'/payment/mollie/intermediate'], type='http', auth="none", methods=['POST'], csrf=False) | ||
def mollie_intermediate(self, **post): | ||
acquirer = request.env['payment.acquirer'].browse(int(post['Key'])) | ||
|
||
url = post['URL'] + "payments" | ||
headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + acquirer._get_mollie_api_keys(acquirer.environment)['mollie_api_key'] } | ||
base_url = post['BaseUrl'] | ||
orderid = post['OrderId'] | ||
description = post['Description'] | ||
currency = post['Currency'] | ||
amount = post['Amount'] | ||
language = post['Language'] | ||
name = post['Name'] | ||
email = post['Email'] | ||
zip = post['Zip'] | ||
address = post['Address'] | ||
town = post['Town'] | ||
country = post['Country'] | ||
phone = post['Phone'] | ||
|
||
payload = { | ||
"description": description, | ||
"amount": amount, | ||
#"webhookUrl": base_url + self._notify_url, | ||
"redirectUrl": "%s%s?reference=%s" % (base_url, self._redirect_url, orderid), | ||
"metadata": { | ||
"order_id": orderid, | ||
"customer": { | ||
"locale": language, | ||
"currency": currency, | ||
"last_name": name, | ||
"address1": address, | ||
"zip_code": zip, | ||
"city": town, | ||
"country": country, | ||
"phone": phone, | ||
"email": email | ||
} | ||
} | ||
} | ||
|
||
mollie_response = requests.post( | ||
url, data=json.dumps(payload), headers=headers).json() | ||
|
||
if mollie_response["status"] == "open": | ||
|
||
payment_tx = request.env['payment.transaction'].sudo().search([('reference', '=', orderid)]) | ||
if not payment_tx or len(payment_tx) > 1: | ||
error_msg = ('received data for reference %s') % (pprint.pformat(orderid)) | ||
if not payment_tx: | ||
error_msg += ('; no order found') | ||
else: | ||
error_msg += ('; multiple order found') | ||
_logger.info(error_msg) | ||
raise ValidationError(error_msg) | ||
payment_tx.write({"acquirer_reference": mollie_response["id"]}) | ||
|
||
payment_url = mollie_response["links"]["paymentUrl"] | ||
return werkzeug.utils.redirect(payment_url) | ||
|
||
return werkzeug.utils.redirect("/") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<data noupdate="1"> | ||
|
||
<record id="payment.payment_acquirer_mollie" model="payment.acquirer"> | ||
<field name="name">Mollie</field> | ||
<field name="image" type="base64" file="payment_mollie_beopen/static/src/img/mollie_icon.png"/> | ||
<field name="provider">mollie</field> | ||
<field name="company_id" ref="base.main_company"/> | ||
<field name="view_template_id" ref="payment_mollie_beopen.mollie_acquirer_button"/> | ||
<field name="environment">test</field> | ||
<field name="pre_msg"><![CDATA[ | ||
<p>You will be redirected to the Mollie website after clicking on payment button.</p>]]></field> | ||
<field name="mollie_api_key_test">mollie api test key</field> | ||
<field name="mollie_api_key_prod">mollie api live key</field> | ||
<field name="module_id" ref="base.module_payment_mollie_beopen"/> | ||
<field name="description" type="html"> | ||
<p> | ||
A payment gateway to accept online payments via various payment methods (check https://www.mollie.com/be/payments/). | ||
</p> | ||
<ul> | ||
<li><i class="fa fa-check"/>eCommerce</li> | ||
</ul> | ||
</field> | ||
</record> | ||
</data> | ||
</odoo> | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import mollie |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
# -*- coding: utf-'8' "-*-" | ||
|
||
import json | ||
import logging | ||
from hashlib import sha256 | ||
import urlparse | ||
import unicodedata | ||
import pprint | ||
import requests | ||
|
||
from odoo import models, fields, api | ||
from odoo.tools.float_utils import float_compare | ||
from odoo.tools.translate import _ | ||
from odoo.addons.payment.models.payment_acquirer import ValidationError | ||
from odoo.tools.safe_eval import safe_eval | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class AcquirerMollie(models.Model): | ||
_inherit = 'payment.acquirer' | ||
# Fields | ||
|
||
provider = fields.Selection(selection_add=[('mollie', 'Mollie')]) | ||
mollie_api_key_test = fields.Char('Mollie Test API key', size=40, required_if_provider='mollie', groups='base.group_user') | ||
mollie_api_key_prod = fields.Char('Mollie Live API key', size=40, required_if_provider='mollie', groups='base.group_user') | ||
|
||
@api.onchange('mollie_api_key_test') | ||
def _onchange_mollie_api_key_test(self): | ||
if self.mollie_api_key_test: | ||
if not self.mollie_api_key_test[:5] == 'test_': | ||
return {'warning': {'title': "Warning", 'message': "Value of Test API Key is not valid. Should begin with 'test_'",}} | ||
|
||
@api.onchange('mollie_api_key_prod') | ||
def _onchange_mollie_api_key_prod(self): | ||
if self.mollie_api_key_prod: | ||
if not self.mollie_api_key_prod[:5] == 'live_': | ||
return {'warning': {'title': "Warning", 'message': "Value of Live API Key is not valid. Should begin with 'live_'",}} | ||
|
||
def _get_mollie_api_keys(self, environment): | ||
keys = {'prod': self.mollie_api_key_prod, | ||
'test': self.mollie_api_key_test | ||
} | ||
return {'mollie_api_key': keys.get(environment, keys['test']), } | ||
|
||
def _get_mollie_urls(self, environment): | ||
""" Mollie URLS """ | ||
url = { | ||
'prod': 'https://api.mollie.nl/v1/', | ||
'test': 'https://api.mollie.nl/v1/', } | ||
|
||
return {'mollie_form_url': url.get(environment, url['test']), } | ||
|
||
@api.multi | ||
def mollie_form_generate_values(self, values): | ||
self.ensure_one() | ||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') | ||
currency = self.env['res.currency'].sudo().browse(values['currency_id']) | ||
language = values.get('partner_lang') | ||
name = values.get('partner_name') | ||
email = values.get('partner_email') | ||
zip = values.get('partner_zip') | ||
address = values.get('partner_address') | ||
town = values.get('partner_city') | ||
country = values.get('partner_country') and values.get('partner_country').code or '' | ||
phone = values.get('partner_phone') | ||
|
||
amount = values['amount'] | ||
mollie_key = getattr(self, 'id') | ||
|
||
mollie_tx_values = dict(values) | ||
mollie_tx_values.update({ | ||
'OrderId': values['reference'], | ||
'Description': values['reference'], | ||
'Currency': currency.name, | ||
'Amount': amount, | ||
'Key': mollie_key, #self._get_mollie_api_keys(self.environment)['mollie_api_key'], | ||
'URL' : self._get_mollie_urls(self.environment)['mollie_form_url'], | ||
'BaseUrl': base_url, | ||
'Language': language, | ||
'Name': name, | ||
'Email': email, | ||
'Zip': zip, | ||
'Address': address, | ||
'Town': town, | ||
'Country': country, | ||
'Phone': phone | ||
}) | ||
|
||
return mollie_tx_values | ||
|
||
@api.multi | ||
def mollie_get_form_action_url(self): | ||
self.ensure_one() | ||
return "/payment/mollie/intermediate" | ||
|
||
|
||
class TxMollie(models.Model): | ||
_inherit = 'payment.transaction' | ||
|
||
@api.multi | ||
def _mollie_form_get_tx_from_data(self, data): | ||
reference = data.get('reference') | ||
payment_tx = self.search([('reference', '=', reference)]) | ||
|
||
if not payment_tx or len(payment_tx) > 1: | ||
error_msg = _('received data for reference %s') % (pprint.pformat(reference)) | ||
if not payment_tx: | ||
error_msg += _('; no order found') | ||
else: | ||
error_msg += _('; multiple order found') | ||
_logger.info(error_msg) | ||
raise ValidationError(error_msg) | ||
|
||
return payment_tx | ||
|
||
@api.multi | ||
def _mollie_form_get_invalid_parameters(self, data): | ||
invalid_parameters = [] | ||
|
||
return invalid_parameters | ||
|
||
@api.multi | ||
def _mollie_form_validate(self, data): | ||
reference = data.get('reference') | ||
|
||
acquirer = self.acquirer_id | ||
|
||
tx = self._mollie_form_get_tx_from_data(data) | ||
|
||
transactionId = tx['acquirer_reference'] | ||
|
||
_logger.info('Validated transfer payment for tx %s: set as pending' % (reference)) | ||
mollie_api_key = acquirer._get_mollie_api_keys(acquirer.environment)['mollie_api_key'] | ||
url = "%s/payments" % (acquirer._get_mollie_urls(acquirer.environment)['mollie_form_url']) | ||
|
||
payload = { | ||
"id": transactionId | ||
} | ||
if acquirer.environment == 'test': | ||
payload["testmode"] = True | ||
|
||
headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + mollie_api_key} | ||
|
||
mollie_response = requests.get( | ||
url, data=json.dumps(payload), headers=headers).json() | ||
|
||
if self.state == 'done': | ||
_logger.info('Mollie: trying to validate an already validated tx (ref %s)', reference) | ||
return True | ||
|
||
data_list = mollie_response["data"] | ||
data = {} | ||
status = 'undefined' | ||
mollie_reference = '' | ||
if len(data_list) > 0: | ||
data = data_list[0] | ||
|
||
if "status" in data: | ||
status = data["status"] | ||
if "id" in data: | ||
mollie_reference = data["id"] | ||
|
||
if status == "paid": | ||
vals = { | ||
'state': 'done', | ||
'date_validate': fields.datetime.strptime(data["paidDatetime"].replace(".0Z", ""), "%Y-%m-%dT%H:%M:%S"), | ||
'acquirer_reference': reference, | ||
} | ||
if mollie_reference and self.partner_id and self.type == 'form': | ||
payment_token = self.env['payment.token'].create({ | ||
'partner_id': self.partner_id.id, | ||
'acquirer_id': self.acquirer_id.id, | ||
'acquirer_ref': mollie_reference, | ||
'name': data["method"] | ||
}) | ||
vals.update(payment_token_id=payment_token.id) | ||
self.write(vals) | ||
if self.callback_eval: | ||
safe_eval(self.callback_eval, {'self': self}) | ||
return True | ||
elif status in ["cancelled", "expired", "failed"]: | ||
self.write({ | ||
'state': 'cancel', | ||
'acquirer_reference': mollie_reference, | ||
}) | ||
return False | ||
elif status in ["open", "pending"]: | ||
self.write({ | ||
'state': 'pending', | ||
'acquirer_reference': mollie_reference, | ||
}) | ||
return False | ||
else: | ||
self.write({ | ||
'state': 'error', | ||
'acquirer_reference': mollie_reference, | ||
}) | ||
return False | ||
|
||
class PaymentToken(models.Model): | ||
_inherit = 'payment.token' | ||
|
||
def mollie_create(self, values): | ||
return { | ||
'acquirer_ref': values['acquirer_ref'], | ||
'name': values['name'] | ||
} | ||
|
||
|
||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.