Начальное наполнение
This commit is contained in:
parent
baeb5528f8
commit
745ad2b80c
4
__init__.py
Normal file
4
__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
17
__manifest__.py
Normal file
17
__manifest__.py
Normal file
@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "POS Self Order Stripe",
|
||||
"summary": "Addon for the Self Order App that allows customers to pay by Stripe.",
|
||||
"category": "Sales/Point Of Sale",
|
||||
"depends": ["pos_stripe", "pos_self_order"],
|
||||
"auto_install": True,
|
||||
'data': [
|
||||
'views/assets_stripe.xml',
|
||||
],
|
||||
'assets': {
|
||||
'pos_self_order.assets': [
|
||||
'pos_self_order_stripe/static/**/*',
|
||||
],
|
||||
},
|
||||
"license": "LGPL-3",
|
||||
}
|
2
controllers/__init__.py
Normal file
2
controllers/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import orders
|
55
controllers/orders.py
Normal file
55
controllers/orders.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.addons.pos_self_order.controllers.orders import PosSelfOrderController
|
||||
from werkzeug.exceptions import Unauthorized
|
||||
|
||||
class PosSelfOrderControllerStripe(PosSelfOrderController):
|
||||
@http.route("/pos-self-order/stripe-connection-token/", auth="public", type="json", website=True)
|
||||
def get_stripe_creditentials(self, access_token, payment_method_id):
|
||||
# stripe_connection_token
|
||||
pos_config, _ = self._verify_authorization(access_token, "", False)
|
||||
payment_method = pos_config.payment_method_ids.filtered(lambda p: p.id == payment_method_id)
|
||||
return payment_method.stripe_connection_token()
|
||||
|
||||
@http.route("/pos-self-order/stripe-capture-payment/", auth="public", type="json", website=True)
|
||||
def stripe_capture_payment(self, access_token, order_access_token, payment_intent_id, payment_method_id):
|
||||
pos_config, _ = self._verify_authorization(access_token, "", False)
|
||||
stripe_confirmation = pos_config.env['pos.payment.method'].stripe_capture_payment(payment_intent_id)
|
||||
order = pos_config.env['pos.order'].search([('access_token', '=', order_access_token), ('config_id', '=', pos_config.id)])
|
||||
|
||||
if not order:
|
||||
raise Unauthorized()
|
||||
|
||||
payment_method = pos_config.payment_method_ids.filtered(lambda p: p.id == payment_method_id)
|
||||
stripe_order_amount = payment_method._stripe_calculate_amount(order.amount_total)
|
||||
|
||||
if float_is_zero(stripe_order_amount - stripe_confirmation['amount'], precision_rounding=pos_config.currency_id.rounding) and stripe_confirmation['status'] == 'succeeded':
|
||||
transaction_id = stripe_confirmation['id']
|
||||
payment_result = stripe_confirmation['status']
|
||||
|
||||
order.add_payment({
|
||||
'amount': order.amount_total,
|
||||
'payment_date': fields.Datetime.now(),
|
||||
'payment_method_id': payment_method.id,
|
||||
'card_type': False,
|
||||
'cardholder_name': '',
|
||||
'transaction_id': transaction_id,
|
||||
'payment_status': payment_result,
|
||||
'ticket': '',
|
||||
'pos_order_id': order.id
|
||||
})
|
||||
|
||||
order.action_pos_order_paid()
|
||||
|
||||
if order.config_id.self_ordering_mode == 'kiosk':
|
||||
request.env['bus.bus']._sendone(f'pos_config-{order.config_id.access_token}', 'PAYMENT_STATUS', {
|
||||
'payment_result': 'Success',
|
||||
'order': order._export_for_self_order(),
|
||||
})
|
||||
else:
|
||||
request.env['bus.bus']._sendone(f'pos_config-{order.config_id.access_token}', 'PAYMENT_STATUS', {
|
||||
'payment_result': 'fail',
|
||||
'order': order._export_for_self_order(),
|
||||
})
|
21
i18n/pos_self_order_stripe.pot
Normal file
21
i18n/pos_self_order_stripe.pot
Normal file
@ -0,0 +1,21 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * pos_self_order_stripe
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-10-26 21:55+0000\n"
|
||||
"PO-Revision-Date: 2023-10-26 21:55+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: pos_self_order_stripe
|
||||
#: model:ir.model,name:pos_self_order_stripe.model_pos_payment_method
|
||||
msgid "Point of Sale Payment Methods"
|
||||
msgstr ""
|
23
i18n/ru.po
Normal file
23
i18n/ru.po
Normal file
@ -0,0 +1,23 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * pos_self_order_stripe
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 17.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-10-26 21:55+0000\n"
|
||||
"PO-Revision-Date: 2024-01-30 15:14+0400\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian (https://app.transifex.com/odoo/teams/41243/ru/)\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#. module: pos_self_order_stripe
|
||||
#: model:ir.model,name:pos_self_order_stripe.model_pos_payment_method
|
||||
msgid "Point of Sale Payment Methods"
|
||||
msgstr "Способы оплаты в торговых точках"
|
3
models/__init__.py
Normal file
3
models/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import pos_payment_method
|
11
models/pos_payment_method.py
Normal file
11
models/pos_payment_method.py
Normal file
@ -0,0 +1,11 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class PosPaymentMethod(models.Model):
|
||||
_inherit = "pos.payment.method"
|
||||
|
||||
def _payment_request_from_kiosk(self, order):
|
||||
if self.use_payment_terminal != 'stripe':
|
||||
return super()._payment_request_from_kiosk(order)
|
||||
else:
|
||||
return self.stripe_payment_intent(order.amount_total)
|
19
static/src/app/pages/payment_page/payment_page.js
Normal file
19
static/src/app/pages/payment_page/payment_page.js
Normal file
@ -0,0 +1,19 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PaymentPage } from "@pos_self_order/app/pages/payment_page/payment_page";
|
||||
|
||||
patch(PaymentPage.prototype, {
|
||||
async startPayment() {
|
||||
this.selfOrder.paymentError = false;
|
||||
const paymentMethod = this.selfOrder.pos_payment_methods.find(
|
||||
(p) => p.id === this.state.paymentMethodId
|
||||
);
|
||||
|
||||
if (paymentMethod.use_payment_terminal === "stripe") {
|
||||
await this.selfOrder.stripe.startPayment(this.selfOrder.currentOrder);
|
||||
} else {
|
||||
await super.startPayment(...arguments);
|
||||
}
|
||||
},
|
||||
});
|
49
static/src/app/self_order_service.js
Normal file
49
static/src/app/self_order_service.js
Normal file
@ -0,0 +1,49 @@
|
||||
/** @odoo-module */
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { SelfOrder } from "@pos_self_order/app/self_order_service";
|
||||
import { Stripe, StripeError } from "@pos_self_order_stripe/app/stripe";
|
||||
|
||||
patch(SelfOrder.prototype, {
|
||||
async setup() {
|
||||
await super.setup(...arguments);
|
||||
this.stripeState = "not_connected";
|
||||
|
||||
const stripePaymentMethod = this.pos_payment_methods.find(
|
||||
(p) => p.use_payment_terminal === "stripe"
|
||||
);
|
||||
|
||||
if (stripePaymentMethod) {
|
||||
this.stripe = new Stripe(
|
||||
this.env,
|
||||
stripePaymentMethod,
|
||||
this.access_token,
|
||||
this.pos_config_id,
|
||||
this.handleStripeError.bind(this),
|
||||
this.handleReaderConnection.bind(this)
|
||||
);
|
||||
}
|
||||
},
|
||||
handleReaderConnection(state) {
|
||||
this.stripeState = state.status;
|
||||
},
|
||||
handleStripeError(error) {
|
||||
this.paymentError = true;
|
||||
this.handleErrorNotification(error);
|
||||
},
|
||||
handleErrorNotification(error) {
|
||||
let message = "";
|
||||
|
||||
if (error.code) {
|
||||
message = `Error: ${error.code}`;
|
||||
} else if (error instanceof StripeError) {
|
||||
message = `Stripe: ${error.message}`;
|
||||
} else {
|
||||
super.handleErrorNotification(...arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
this.notification.add(message, {
|
||||
type: "danger",
|
||||
});
|
||||
},
|
||||
});
|
139
static/src/app/stripe.js
Normal file
139
static/src/app/stripe.js
Normal file
@ -0,0 +1,139 @@
|
||||
/** @odoo-module **/
|
||||
/* global StripeTerminal */
|
||||
|
||||
export class StripeError extends Error {}
|
||||
|
||||
export class Stripe {
|
||||
constructor(...args) {
|
||||
this.setup(...args);
|
||||
}
|
||||
|
||||
setup(
|
||||
env,
|
||||
stripePaymentMethod,
|
||||
access_token,
|
||||
pos_config_id,
|
||||
errorCallback,
|
||||
handleReaderConnection
|
||||
) {
|
||||
this.env = env;
|
||||
this.terminal = null;
|
||||
this.access_token = access_token;
|
||||
this.stripePaymentMethod = stripePaymentMethod;
|
||||
this.pos_config_id = pos_config_id;
|
||||
this.errorCallback = errorCallback;
|
||||
this.handleReaderConnection = handleReaderConnection;
|
||||
|
||||
this.createTerminal();
|
||||
}
|
||||
|
||||
get connectionStatus() {
|
||||
return this.terminal.getConnectionStatus();
|
||||
}
|
||||
|
||||
createTerminal() {
|
||||
this.terminal = StripeTerminal.create({
|
||||
onFetchConnectionToken: this.getBackendConnectionToken.bind(this),
|
||||
onConnectionStatusChange: this.handleReaderConnection.bind(this),
|
||||
onUnexpectedReaderDisconnect: () => {
|
||||
this.handleReaderConnection({
|
||||
status: "not_connected",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async startPayment(order) {
|
||||
try {
|
||||
const result = await this.env.services.rpc(
|
||||
`/kiosk/payment/${this.pos_config_id}/kiosk`,
|
||||
{
|
||||
order: order,
|
||||
access_token: this.access_token,
|
||||
payment_method_id: this.stripePaymentMethod.id,
|
||||
}
|
||||
);
|
||||
const paymentStatus = result.payment_status;
|
||||
const savedOrder = result.order;
|
||||
await this.connectReader();
|
||||
const clientSecret = paymentStatus.client_secret;
|
||||
const paymentMethod = await this.collectPaymentMethod(clientSecret);
|
||||
const processPayment = await this.processPayment(paymentMethod.paymentIntent);
|
||||
await this.capturePayment(processPayment.paymentIntent.id, savedOrder);
|
||||
} catch (error) {
|
||||
this.errorCallback(error);
|
||||
}
|
||||
}
|
||||
|
||||
async processPayment(paymentIntent) {
|
||||
const result = await this.terminal.processPayment(paymentIntent);
|
||||
|
||||
if (result.error) {
|
||||
throw new StripeError(result.error.code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getBackendConnectionToken() {
|
||||
const data = await this.env.services.rpc("/pos-self-order/stripe-connection-token", {
|
||||
access_token: this.access_token,
|
||||
payment_method_id: this.stripePaymentMethod.id,
|
||||
});
|
||||
|
||||
return data.secret;
|
||||
}
|
||||
|
||||
async capturePayment(paymentIntentId, order) {
|
||||
return await this.env.services.rpc("/pos-self-order/stripe-capture-payment", {
|
||||
access_token: this.access_token,
|
||||
order_access_token: order.access_token,
|
||||
payment_intent_id: paymentIntentId,
|
||||
payment_method_id: this.stripePaymentMethod.id,
|
||||
});
|
||||
}
|
||||
|
||||
async discoverReaders() {
|
||||
const result = await this.terminal.discoverReaders({
|
||||
allowCustomerCancel: true,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw new StripeError(result.error.code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async connectReader() {
|
||||
if (this.connectionStatus !== "not_connected") {
|
||||
return;
|
||||
}
|
||||
|
||||
const discoverReaders = await this.discoverReaders();
|
||||
const discoveredReaders = discoverReaders.discoveredReaders;
|
||||
const findLinkedReader = discoveredReaders.find(
|
||||
(reader) => reader.serial_number == this.stripePaymentMethod.stripe_serial_number
|
||||
);
|
||||
|
||||
const result = await this.terminal.connectReader(findLinkedReader, {
|
||||
fail_if_in_use: true,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw new StripeError(result.error.code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async collectPaymentMethod(clientSecret) {
|
||||
const result = await this.terminal.collectPaymentMethod(clientSecret);
|
||||
|
||||
if (result.error) {
|
||||
throw new StripeError(result.error.code);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
10
views/assets_stripe.xml
Normal file
10
views/assets_stripe.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="stripe" inherit_id="pos_self_order.index">
|
||||
<xpath expr="//head" position="inside">
|
||||
<!-- As the following link does not end with '.js', it's not loaded when
|
||||
placed in __manifest__.py. The following declaration fix this problem -->
|
||||
<script src="https://js.stripe.com/terminal/v1/"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
Loading…
x
Reference in New Issue
Block a user