summaryrefslogtreecommitdiff
path: root/addons/pos_adyen/static/src/js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/pos_adyen/static/src/js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/pos_adyen/static/src/js')
-rw-r--r--addons/pos_adyen/static/src/js/models.js8
-rw-r--r--addons/pos_adyen/static/src/js/payment_adyen.js325
2 files changed, 333 insertions, 0 deletions
diff --git a/addons/pos_adyen/static/src/js/models.js b/addons/pos_adyen/static/src/js/models.js
new file mode 100644
index 00000000..c022ea7b
--- /dev/null
+++ b/addons/pos_adyen/static/src/js/models.js
@@ -0,0 +1,8 @@
+odoo.define('pos_adyen.models', function (require) {
+var models = require('point_of_sale.models');
+var PaymentAdyen = require('pos_adyen.payment');
+
+models.register_payment_method('adyen', PaymentAdyen);
+models.register_payment_method('odoo_adyen', PaymentAdyen);
+models.load_fields('pos.payment.method', 'adyen_terminal_identifier');
+});
diff --git a/addons/pos_adyen/static/src/js/payment_adyen.js b/addons/pos_adyen/static/src/js/payment_adyen.js
new file mode 100644
index 00000000..9c9c4316
--- /dev/null
+++ b/addons/pos_adyen/static/src/js/payment_adyen.js
@@ -0,0 +1,325 @@
+odoo.define('pos_adyen.payment', function (require) {
+"use strict";
+
+var core = require('web.core');
+var rpc = require('web.rpc');
+var PaymentInterface = require('point_of_sale.PaymentInterface');
+const { Gui } = require('point_of_sale.Gui');
+
+var _t = core._t;
+
+var PaymentAdyen = PaymentInterface.extend({
+ send_payment_request: function (cid) {
+ this._super.apply(this, arguments);
+ this._reset_state();
+ return this._adyen_pay();
+ },
+ send_payment_cancel: function (order, cid) {
+ this._super.apply(this, arguments);
+ // set only if we are polling
+ this.was_cancelled = !!this.polling;
+ return this._adyen_cancel();
+ },
+ close: function () {
+ this._super.apply(this, arguments);
+ },
+
+ // private methods
+ _reset_state: function () {
+ this.was_cancelled = false;
+ this.last_diagnosis_service_id = false;
+ this.remaining_polls = 2;
+ clearTimeout(this.polling);
+ },
+
+ _handle_odoo_connection_failure: function (data) {
+ // handle timeout
+ var line = this.pos.get_order().selected_paymentline;
+ if (line) {
+ line.set_payment_status('retry');
+ }
+ this._show_error(_t('Could not connect to the Odoo server, please check your internet connection and try again.'));
+
+ return Promise.reject(data); // prevent subsequent onFullFilled's from being called
+ },
+
+ _call_adyen: function (data, operation) {
+ return rpc.query({
+ model: 'pos.payment.method',
+ method: 'proxy_adyen_request',
+ args: [[this.payment_method.id], data, operation],
+ }, {
+ // When a payment terminal is disconnected it takes Adyen
+ // a while to return an error (~6s). So wait 10 seconds
+ // before concluding Odoo is unreachable.
+ timeout: 10000,
+ shadow: true,
+ }).catch(this._handle_odoo_connection_failure.bind(this));
+ },
+
+ _adyen_get_sale_id: function () {
+ var config = this.pos.config;
+ return _.str.sprintf('%s (ID: %s)', config.display_name, config.id);
+ },
+
+ _adyen_common_message_header: function () {
+ var config = this.pos.config;
+ this.most_recent_service_id = Math.floor(Math.random() * Math.pow(2, 64)).toString(); // random ID to identify request/response pairs
+ this.most_recent_service_id = this.most_recent_service_id.substring(0, 10); // max length is 10
+
+ return {
+ 'ProtocolVersion': '3.0',
+ 'MessageClass': 'Service',
+ 'MessageType': 'Request',
+ 'SaleID': this._adyen_get_sale_id(config),
+ 'ServiceID': this.most_recent_service_id,
+ 'POIID': this.payment_method.adyen_terminal_identifier
+ };
+ },
+
+ _adyen_pay_data: function () {
+ var order = this.pos.get_order();
+ var config = this.pos.config;
+ var line = order.selected_paymentline;
+ var data = {
+ 'SaleToPOIRequest': {
+ 'MessageHeader': _.extend(this._adyen_common_message_header(), {
+ 'MessageCategory': 'Payment',
+ }),
+ 'PaymentRequest': {
+ 'SaleData': {
+ 'SaleTransactionID': {
+ 'TransactionID': order.uid,
+ 'TimeStamp': moment().format(), // iso format: '2018-01-10T11:30:15+00:00'
+ }
+ },
+ 'PaymentTransaction': {
+ 'AmountsReq': {
+ 'Currency': this.pos.currency.name,
+ 'RequestedAmount': line.amount,
+ }
+ }
+ }
+ }
+ };
+
+ if (config.adyen_ask_customer_for_tip) {
+ data.SaleToPOIRequest.PaymentRequest.SaleData.SaleToAcquirerData = "tenderOption=AskGratuity";
+ }
+
+ return data;
+ },
+
+ _adyen_pay: function () {
+ var self = this;
+
+ if (this.pos.get_order().selected_paymentline.amount < 0) {
+ this._show_error(_t('Cannot process transactions with negative amount.'));
+ return Promise.resolve();
+ }
+
+
+ if (this.poll_response_error) {
+ this.poll_response_error = false;
+ return self._adyen_handle_response({});
+ }
+
+ var data = this._adyen_pay_data();
+
+ return this._call_adyen(data).then(function (data) {
+ return self._adyen_handle_response(data);
+ });
+ },
+
+ _adyen_cancel: function (ignore_error) {
+ var self = this;
+ var previous_service_id = this.most_recent_service_id;
+ var header = _.extend(this._adyen_common_message_header(), {
+ 'MessageCategory': 'Abort',
+ });
+
+ var data = {
+ 'SaleToPOIRequest': {
+ 'MessageHeader': header,
+ 'AbortRequest': {
+ 'AbortReason': 'MerchantAbort',
+ 'MessageReference': {
+ 'MessageCategory': 'Payment',
+ 'ServiceID': previous_service_id,
+ }
+ },
+ }
+ };
+
+ return this._call_adyen(data).then(function (data) {
+
+ // Only valid response is a 200 OK HTTP response which is
+ // represented by true.
+ if (! ignore_error && data !== "ok") {
+ self._show_error(_t('Cancelling the payment failed. Please cancel it manually on the payment terminal.'));
+ }
+ });
+ },
+
+ _convert_receipt_info: function (output_text) {
+ return output_text.reduce(function (acc, entry) {
+ var params = new URLSearchParams(entry.Text);
+
+ if (params.get('name') && !params.get('value')) {
+ return acc + _.str.sprintf('<br/>%s', params.get('name'));
+ } else if (params.get('name') && params.get('value')) {
+ return acc + _.str.sprintf('<br/>%s: %s', params.get('name'), params.get('value'));
+ }
+
+ return acc;
+ }, '');
+ },
+
+ _poll_for_response: function (resolve, reject) {
+ var self = this;
+ if (this.was_cancelled) {
+ resolve(false);
+ return Promise.resolve();
+ }
+
+ return rpc.query({
+ model: 'pos.payment.method',
+ method: 'get_latest_adyen_status',
+ args: [[this.payment_method.id], this._adyen_get_sale_id()],
+ }, {
+ timeout: 5000,
+ shadow: true,
+ }).catch(function (data) {
+ reject();
+ self.poll_response_error = true;
+ return self._handle_odoo_connection_failure(data);
+ }).then(function (status) {
+ var notification = status.latest_response;
+ var last_diagnosis_service_id = status.last_received_diagnosis_id;
+ var order = self.pos.get_order();
+ var line = order.selected_paymentline;
+
+
+ if (self.last_diagnosis_service_id != last_diagnosis_service_id) {
+ self.last_diagnosis_service_id = last_diagnosis_service_id;
+ self.remaining_polls = 2;
+ } else {
+ self.remaining_polls--;
+ }
+
+ if (notification && notification.SaleToPOIResponse.MessageHeader.ServiceID == self.most_recent_service_id) {
+ var response = notification.SaleToPOIResponse.PaymentResponse.Response;
+ var additional_response = new URLSearchParams(response.AdditionalResponse);
+
+ if (response.Result == 'Success') {
+ var config = self.pos.config;
+ var payment_response = notification.SaleToPOIResponse.PaymentResponse;
+ var payment_result = payment_response.PaymentResult;
+
+ var cashier_receipt = payment_response.PaymentReceipt.find(function (receipt) {
+ return receipt.DocumentQualifier == 'CashierReceipt';
+ });
+
+ if (cashier_receipt) {
+ line.set_cashier_receipt(self._convert_receipt_info(cashier_receipt.OutputContent.OutputText));
+ }
+
+ var customer_receipt = payment_response.PaymentReceipt.find(function (receipt) {
+ return receipt.DocumentQualifier == 'CustomerReceipt';
+ });
+
+ if (customer_receipt) {
+ line.set_receipt_info(self._convert_receipt_info(customer_receipt.OutputContent.OutputText));
+ }
+
+ var tip_amount = payment_result.AmountsResp.TipAmount;
+ if (config.adyen_ask_customer_for_tip && tip_amount > 0) {
+ order.set_tip(tip_amount);
+ line.set_amount(payment_result.AmountsResp.AuthorizedAmount);
+ }
+
+ line.transaction_id = additional_response.get('pspReference');
+ line.card_type = additional_response.get('cardType');
+ line.cardholder_name = additional_response.get('cardHolderName') || '';
+ resolve(true);
+ } else {
+ var message = additional_response.get('message');
+ self._show_error(_.str.sprintf(_t('Message from Adyen: %s'), message));
+
+ // this means the transaction was cancelled by pressing the cancel button on the device
+ if (message.startsWith('108 ')) {
+ resolve(false);
+ } else {
+ line.set_payment_status('retry');
+ reject();
+ }
+ }
+ } else if (self.remaining_polls <= 0) {
+ self._show_error(_t('The connection to your payment terminal failed. Please check if it is still connected to the internet.'));
+ self._adyen_cancel();
+ resolve(false);
+ }
+ });
+ },
+
+ _adyen_handle_response: function (response) {
+ var line = this.pos.get_order().selected_paymentline;
+
+ if (response.error && response.error.status_code == 401) {
+ this._show_error(_t('Authentication failed. Please check your Adyen credentials.'));
+ line.set_payment_status('force_done');
+ return Promise.resolve();
+ }
+
+ response = response.SaleToPOIRequest;
+ if (response && response.EventNotification && response.EventNotification.EventToNotify == 'Reject') {
+ console.error('error from Adyen', response);
+
+ var msg = '';
+ if (response.EventNotification) {
+ var params = new URLSearchParams(response.EventNotification.EventDetails);
+ msg = params.get('message');
+ }
+
+ this._show_error(_.str.sprintf(_t('An unexpected error occured. Message from Adyen: %s'), msg));
+ if (line) {
+ line.set_payment_status('force_done');
+ }
+
+ return Promise.resolve();
+ } else {
+ line.set_payment_status('waitingCard');
+
+ var self = this;
+ var res = new Promise(function (resolve, reject) {
+ // clear previous intervals just in case, otherwise
+ // it'll run forever
+ clearTimeout(self.polling);
+
+ self.polling = setInterval(function () {
+ self._poll_for_response(resolve, reject);
+ }, 3000);
+ });
+
+ // make sure to stop polling when we're done
+ res.finally(function () {
+ self._reset_state();
+ });
+
+ return res;
+ }
+ },
+
+ _show_error: function (msg, title) {
+ if (!title) {
+ title = _t('Adyen Error');
+ }
+ Gui.showPopup('ErrorPopup',{
+ 'title': title,
+ 'body': msg,
+ });
+ },
+});
+
+return PaymentAdyen;
+});