Merge pull request #3171 from expsa/ENS-3085

[IMP][ADD] apple_pay_fast_checkout
This commit is contained in:
abdurrahman-saber 2025-05-12 15:17:33 +03:00 committed by GitHub
commit 8046a2b1b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 232 additions and 226 deletions

View File

@ -0,0 +1 @@
from . import controllers

View File

@ -0,0 +1,19 @@
{
'name': 'HyperPay ApplePay CopyAndPay Fast-Checkout',
'version': '14.0.1.0',
'description': 'Technical module to add apple pay in any place in the document',
'summary': 'Technical module to add apple pay in any place in the document',
'author': 'Expert Co. Ltd.',
'website': 'https://www.exp-sa.com',
'license': 'LGPL-3',
'category': 'Payment',
'depends': [
'payment_applepay'
],
'data': [
'views/applepay_iframe.xml',
'views/templates.xml'
],
'auto_install': True,
'application': False,
}

View File

@ -0,0 +1 @@
from . import main

View File

@ -0,0 +1,33 @@
import json
from odoo.http import route, request, Controller
class ApplePayFastCheckout(Controller):
@route('/applepay', type='http', auth='public', website=True, csrf=False)
def apple_pay_iframe(self, **kwargs):
acquirer_id = request.env['payment.acquirer'].sudo().search([('provider', '=', 'applepay')], limit=1)
if acquirer_id.state == 'test':
url = "https://eu-test.oppwa.com/v1/paymentWidgets.js"
else:
url = "https://oppwa.com/v1/paymentWidgets.js"
response = request.render("applepay_fast_checkout.apple_pay_iframe", {'hyperpay_src': url})
response.headers['Content-Security-Policy'] = "script-src blob: 'self' 'unsafe-inline' 'unsafe-eval' https://*; worker-src blob: 'self' 'unsafe-inline' 'unsafe-eval' https://*;connect-src 'self' https://* wss://*;frame-src 'self' blob: https://*;"
return response
@route('/applepay/checkout', type='json', auth='public', website=True)
def apple_pay_create_checkout(self, **post):
data = json.loads(request.httprequest.data.decode('utf-8'))
processed_data = self._process_checkout_data(data)
checkout_id = self._get_checkout_id(processed_data)
return {'checkout_id': checkout_id}
def _process_checkout_data(self, data):
return data
def _get_checkout_id(self, vals):
return ''

View File

@ -0,0 +1,72 @@
var wpwlOptions = {
applePay: {
version: 3,
displayName: "ENSAN",
total: { label: "ENSAN", amount: "10" },
checkAvailability: "applePayCapabilities",
currencyCode: "SAR",
supportedNetworks: ["mada", "masterCard", "visa"],
merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
merchantIdentifier: "8ac9a4ca811e7d6f018132e7a3654ddf",
supportedCountries: ["SA"],
buttonSource: "css",
buttonStyle: "white",
buttonType: "pay",
countryCode: "SA",
submitOnPaymentAuthorized: ["customer"],
requiredShippingContactFields: ["phone"],
onCancel: function () {
if (window.wpwl && window.wpwl.checkout && window.wpwl.checkout.id) {
window.wpwl.checkout.id = "";
}
},
},
locale: "ar-AB",
onReady: function () {
window.parent.document.addEventListener("applePayAmountUpdate", function (e) {
try {
window.wpwlOptions.applePay.total.amount = e.detail.amount;
console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
} catch (error) {
console.error(error);
}
});
// let isCheckoutPage = $("#oe_structure_website_sale_payment_1", window.parent.document).length;
// if (isCheckoutPage) {
// let applepayButton = $("apple-pay-button");
// let totalAmount = $("tr#order_total", window.parent.document).find("span.oe_currency_value").text().replaceAll(",", "");
// window.wpwlOptions.applePay.total.amount = totalAmount;
// console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
// applepayButton.css({
// "--apple-pay-button-height": "60px",
// "--apple-pay-button-border-radius": "4px",
// "--apple-pay-button-padding": "15px 5px",
// "--apple-pay-button-box-sizing": "border-box",
// });
// }
},
createCheckout: function () {
const iframeElement = window.frameElement; // The <iframe> element in the parent
const parentDiv = iframeElement?.parentNode;
let requestDataJson = {};
if (parentDiv) {
let requestData = $(parentDiv).find("input[name='applepay_checkout_details_json']").val() || "{}";
requestDataJson = JSON.parse(requestData);
}
console.log(requestDataJson);
const rootUrl = `${window.location.protocol}//${window.location.host}/`;
return $.ajax({
url: `${rootUrl}applepay/checkout`,
type: "POST",
contentType: "application/json",
data: JSON.stringify(requestDataJson),
dataType: "json",
}).then(function (response) {
console.log(response);
return response.result.checkout_id;
});
},
};

View File

@ -4,10 +4,10 @@
<template id="apple_pay_iframe" name="Apple Pay Iframe"> <template id="apple_pay_iframe" name="Apple Pay Iframe">
<html> <html>
<head> <head>
<script src="https://oppwa.com/v1/paymentWidgets.js" /> <script t-att-src="hyperpay_src" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js" />
<script src="/payment_applepay/static/src/js/applepay_iframe.js" /> <script src="/applepay_fast_checkout/static/src/js/applepay_iframe.js" />
<link rel="stylesheet" href="/payment_applepay/static/src/scss/applepay_iframe_content.scss" /> <link rel="stylesheet" href="/applepay_fast_checkout/static/src/scss/applepay_iframe_content.scss" />
</head> </head>
<body> <body>
<form t-attf-action="{{request.httprequest.url_root}}payment/applepay/return" <form t-attf-action="{{request.httprequest.url_root}}payment/applepay/return"

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="applepay_btn" name="ApplePay Button">
<t t-set="iframe_width" t-value="iframe_width or '100%'"/>
<t t-set="iframe_height" t-value="iframe_height or '70px'"/>
<div id="applepay_parent_root">
<!-- JSON Object contains all the details of the current checkout and will be sent to the backend for processing before -->
<!-- sending a checkout request to ApplePay, DEFAULTS TO THE CURRENT ORDER-->
<input type="hidden" name="applepay_checkout_details_json" value="{}"/>
<iframe t-attf-style="width: {{iframe_width}}; height: {{iframe_height}}; border: none;"
t-attf-src="{{request.httprequest.url_root}}applepay">
</iframe>
</div>
</template>
</odoo>

View File

@ -11,6 +11,7 @@
'website_sale', 'website_sale',
'payment_hyperpay', 'payment_hyperpay',
'payment_applepay', 'payment_applepay',
'applepay_fast_checkout',
'ensan_sale_management' 'ensan_sale_management'
], ],
'data': ['views/templates.xml', 'views/assets.xml'], 'data': ['views/templates.xml', 'views/assets.xml'],

View File

@ -1 +1,2 @@
from . import main from . import main
from . import applepay_fast_checkout

View File

@ -0,0 +1,74 @@
import logging
from odoo.http import request
from odoo.addons.applepay_fast_checkout.controllers.main import ApplePayFastCheckout
from odoo.addons.payment.controllers.portal import PaymentProcessing
class WebsiteSaleFastCheckout(ApplePayFastCheckout):
def _process_checkout_data(self, data):
product_ids = data.get('product_ids', [])
gifts = data.get('gifts', [])
use_current_order = data.get('use_current_order', False)
current_order = request.website.sale_get_order(force_create=True)
if use_current_order:
order = current_order
else:
order = current_order.copy()
order_mobile_number = data.get('mobile', '')
order_name = data.get('name', '')
extra_donators_ids = [(0, 0, {
'sale_id': order.id,
'product_id': gift.get('product_id'),
'donated_amount': gift.get('amount'),
'donator_name': gift.get('receiver_name'),
'donator_mobile_number': gift.get('receiver_mobile')
}) for gift in gifts if gift.get('product_id')]
order_line = [(0, 0, {
'product_id': product.get('id'),
'extra_donators_ids': extra_donators_ids,
'product_uom_qty': product.get('quantity', 1),
'price_unit': product.get('price_unit', 0) + sum(gift.get('amount', 0) for gift in gifts if gift.get('product_id') == product.get('id'))}) for product in product_ids if product.get('id')]
if order_line:
order.order_line.sudo().unlink()
order.write({
'order_line': order_line,
'order_mobile_number': order_mobile_number,
'order_name': order_name
})
payment_acquire_id = request.env['payment.acquirer'].sudo().search([('provider', '=', 'applepay'),
('company_id', 'in', (False, order.company_id.id))],
limit=1)
# Create transaction
vals = {'acquirer_id': payment_acquire_id.id,
'return_url': '/payment/applepay/return'}
tx = order._create_payment_transaction(vals)
# store the new transaction into the transaction list and if there's an old one, we remove it
# until the day the ecommerce supports multiple orders at the same time
last_tx_id = request.session.get('__website_sale_last_tx_id')
last_tx = request.env['payment.transaction'].browse(last_tx_id).sudo().exists()
if last_tx:
PaymentProcessing.remove_payment_transaction(last_tx)
PaymentProcessing.add_payment_transaction(tx)
request.session['__website_sale_last_tx_id'] = tx.id
tx.sale_order_ids.sudo().write({'done_with_quick_donation': data.get('done_with_quick_donation', False)})
return {'partner_id': tx.partner_id.id,
'reference': tx.reference,
'payment_acquire_id': payment_acquire_id,
'order_id': order,
'amount': tx.amount}
def _get_checkout_id(self, vals):
payment_acquire_id = vals.get('payment_acquire_id')
checkout_id = payment_acquire_id._get_authenticate_apple_pay(vals)
return checkout_id

View File

@ -202,10 +202,10 @@
<div id="donate_error_msg" <div id="donate_error_msg"
class="o_hidden alert alert-danger text-center" /> class="o_hidden alert alert-danger text-center" />
<div class="apple-pay-button-container"> <div class="apple-pay-button-container">
<iframe t-if="not hide_quick_donation" <t t-call="applepay_fast_checkout.applepay_btn">
style="width: 100%; height: 40px; border: none;" <t t-set="iframe_width" t-value="'100%'"/>
t-attf-src="{{request.httprequest.url_root}}applepay"> <t t-set="iframe_height" t-value="'40px'"/>
</iframe> </t>
</div> </div>
<button <button
if="quick_donation_payment_acquire_ids and quick_donation_products" if="quick_donation_payment_acquire_ids and quick_donation_products"
@ -266,15 +266,13 @@
</xpath> </xpath>
</template> </template>
<template id="website_sale_payment_add_applepay" inherit_id="website_sale.payment"> <!-- <template id="website_sale_payment_add_applepay" inherit_id="website_sale.payment">
<xpath expr="//div[@id='payment_method']" position="inside"> <xpath expr="//div[@id='payment_method']" position="inside">
<div class="apple-pay-button-container"> <div class="apple-pay-button-container">
<iframe style="width: 100%; height: 70px; border: none;"
t-attf-src="{{request.httprequest.url_root}}applepay">
</iframe>
</div> </div>
</xpath> </xpath>
</template> </template> -->
<template id="cart_summary_inherit" inherit_id="website_sale.cart_summary"> <template id="cart_summary_inherit" inherit_id="website_sale.cart_summary">
<xpath expr="//tr[@t-foreach='website_sale_order.website_order_line']" position="attributes"> <xpath expr="//tr[@t-foreach='website_sale_order.website_order_line']" position="attributes">

View File

@ -20,7 +20,6 @@
'views/payment_popup_template.xml', 'views/payment_popup_template.xml',
'views/payment_applepay_templates.xml', 'views/payment_applepay_templates.xml',
'data/payment_acquirer_data.xml', 'data/payment_acquirer_data.xml',
'views/applepay_iframe.xml',
], ],
'images': ['images/main_screenshot.gif'], 'images': ['images/main_screenshot.gif'],
'installable': True, 'installable': True,

View File

@ -11,7 +11,6 @@ import io
import base64 import base64
from odoo import http, _ from odoo import http, _
from odoo.addons.payment.controllers.portal import PaymentProcessing
from odoo.http import request, send_file from odoo.http import request, send_file
from odoo.exceptions import UserError from odoo.exceptions import UserError
@ -108,83 +107,4 @@ class ApplepayController(http.Controller):
else: else:
return request.render("payment_applepay.payment_applepay_card_live", return request.render("payment_applepay.payment_applepay_card_live",
{'check_out_id': kw.get('check_out_id'), 'return_url': kw.get('applepay_return')}) {'check_out_id': kw.get('check_out_id'), 'return_url': kw.get('applepay_return')})
@http.route('/applepay', type='http', auth='public', website=True, csrf=False)
def apple_pay_iframe(self, **kwargs):
response = request.render("payment_applepay.apple_pay_iframe")
response.headers['Content-Security-Policy'] = "script-src blob: 'self' 'unsafe-inline' 'unsafe-eval' https://*; worker-src blob: 'self' 'unsafe-inline' 'unsafe-eval' https://*;connect-src 'self' https://* wss://*;frame-src 'self' blob: https://*;"
return response
@http.route('/applepay/checkout', type='json', auth='public', website=True)
def apple_pay_create_checkout(self, **post):
data = json.loads(request.httprequest.data.decode('utf-8'))
checkout_id = self._apple_pay_create_checkout(data)
return {'checkout_id': checkout_id}
def _apple_pay_create_checkout(self, data):
try:
order = request.website.sale_get_order(force_create=True)
product_ids = data.get('product_ids', [])
gifts = data.get('gifts', [])
use_current_order = data.get('use_current_order', False)
if not order:
return ''
if not use_current_order:
order_mobile_number = data.get('mobile', '')
order_name = data.get('name', '')
extra_donators_ids = [(0, 0, {
'sale_id': order.id,
'product_id': gift.get('product_id'),
'donated_amount': gift.get('amount'),
'donator_name': gift.get('receiver_name'),
'donator_mobile_number': gift.get('receiver_mobile')
}) for gift in gifts if gift.get('product_id')]
order_line = [(0, 0, {
'product_id': product.get('id'),
'extra_donators_ids': extra_donators_ids,
'product_uom_qty': product.get('quantity', 1),
'price_unit': product.get('price_unit', 0) + sum(gift.get('amount', 0) for gift in gifts if gift.get('product_id') == product.get('id'))}) for product in product_ids if product.get('id')]
if order_line:
order.order_line.sudo().unlink()
order.write({
'order_line': order_line,
'order_mobile_number': order_mobile_number,
'order_name': order_name
})
payment_acquire_id = request.env['payment.acquirer'].sudo().search([('provider', '=', 'applepay'),
('company_id', 'in', (False, order.company_id.id))], limit=1)
# Create transaction
vals = {'acquirer_id': payment_acquire_id.id,
'return_url': '/payment/applepay/return'}
tx = order._create_payment_transaction(vals)
# store the new transaction into the transaction list and if there's an old one, we remove it
# until the day the ecommerce supports multiple orders at the same time
last_tx_id = request.session.get('__website_sale_last_tx_id')
last_tx = request.env['payment.transaction'].browse(last_tx_id).sudo().exists()
if last_tx:
PaymentProcessing.remove_payment_transaction(last_tx)
PaymentProcessing.add_payment_transaction(tx)
request.session['__website_sale_last_tx_id'] = tx.id
vals = {'partner_id': tx.partner_id.id,
'reference': tx.reference,
'amount': tx.amount}
checkout_id = payment_acquire_id._get_authenticate_apple_pay(vals)
tx.sale_order_ids.sudo().write({'done_with_quick_donation': data.get('done_with_quick_donation', False)})
return checkout_id
except Exception as e:
_logger.error("Error in Apple pay Payment: %s" % (e))
return ''

View File

@ -1,131 +0,0 @@
var wpwlOptions = {
applePay: {
version: 3,
displayName: "ENSAN",
total: { label: "ENSAN", amount: "10" },
checkAvailability: "applePayCapabilities",
currencyCode: "SAR",
supportedNetworks: ["mada","masterCard", "visa"],
merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
merchantIdentifier: "8ac9a4ca811e7d6f018132e7a3654ddf",
supportedCountries: ["SA"],
buttonSource: "css",
buttonStyle: "black",
buttonType: "pay",
countryCode: "SA",
submitOnPaymentAuthorized: ["customer"],
requiredShippingContactFields: ["phone"],
onCancel: function() {
if (window.wpwl && window.wpwl.checkout && window.wpwl.checkout.id) {
window.wpwl.checkout.id = "";
}
},
},
locale: "ar-AB",
onReady: function(){
const amounts = $('.donation-product-detail-layout .single-amount', window.parent.document);
const fixedQty = $('#fixedqtyinput-1', window.parent.document);
if (amounts.length > 0) {
window.wpwlOptions.applePay.total.amount = amounts.get(0).dataset.amount;
console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
} else if(fixedQty.length > 0){
window.wpwlOptions.applePay.total.amount = fixedQty.data('price');
console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
}
window.parent.document.addEventListener('applePayAmountUpdate', function(e) {
window.wpwlOptions.applePay.total.amount = e.detail.amount;
console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
});
let isCheckoutPage = $("#oe_structure_website_sale_payment_1", window.parent.document).length;
if (isCheckoutPage) {
let applepayButton = $('apple-pay-button');
let totalAmount = $("tr#order_total", window.parent.document).find("span.oe_currency_value").text().replaceAll(",", "");
window.wpwlOptions.applePay.total.amount = totalAmount;
console.log("Apple Pay Amount updated to:", window.wpwlOptions.applePay.total.amount);
applepayButton.css({
'--apple-pay-button-height': '60px',
'--apple-pay-button-border-radius': '4px',
'--apple-pay-button-padding': '15px 5px',
'--apple-pay-button-box-sizing': 'border-box'
});
}
},
createCheckout: function() {
let name = "";
let product_ids = gifts = [];
let done_with_quick_donation = false;
let use_current_order = false;
// product details screen
if ($(".js_product", window.parent.document).length) {
let hasGifts = $("#sendAsGiftCheckbox", window.parent.document).is(":checked");
let product_id = parseInt($('.js_product', window.parent.document).find('.product_id').val());
let amount = parseFloat($("input.personal-donation-amount", window.parent.document).val()) || 0;
let quantity = parseInt($($('#fixedqtyinput-1', window.parent.document).get(0)).val()) || 1;
product_ids = [{
id: product_id,
quantity: hasGifts ? 1 : quantity,
price_unit: hasGifts ? 0 : amount
}];
if (hasGifts) {
let $giftBoxElements = $(".gifteeBoxDetails", window.parent.document);
gifts = $giftBoxElements.map(function() {
let result = {};
result.product_id = product_id;
result.amount = parseFloat($(this).find('input.gift-donation-amount').val(), 10) || 0;
result.receiver_name = $(this).find('.input-gifteeName').val();
result.receiver_mobile = $(this).find('.input-gifteeNumber').val();
return result;
}).get();
}
}
// checkout page
else if ($("#oe_structure_website_sale_payment_1", window.parent.document).length) {
name = $("#order_name", window.parent.document).val();
let $productRows = $("table#cart_products tbody tr", window.parent.document);
product_ids = $productRows.map(function() {
let result = {};
result.id = parseInt($(this).data('product-id'));
result.quantity = parseInt($(this).find('td.td-qty').text().replaceAll(",", ""));
result.price_unit = parseFloat($(this).find('td.td-price span.oe_currency_value').text().replaceAll(",", ""));
return result;
}).get();
use_current_order = true;
}
// quick donation page
else {
done_with_quick_donation = true;
let amount = parseFloat($("#qd_amount", window.parent.document).val());
product_ids = [{
id: parseInt($("#qd-options li.active", window.parent.document).data('product_id')),
quantity: 1,
price_unit: amount
}];
}
let requestData = { product_ids: product_ids,
gifts: gifts,
name: name,
done_with_quick_donation: done_with_quick_donation,
use_current_order: use_current_order};
console.log(requestData);
const rootUrl = `${window.location.protocol}//${window.location.host}/`;
return $.ajax({
url: `${rootUrl}applepay/checkout`,
type: "POST",
contentType: "application/json",
data: JSON.stringify(requestData),
dataType: "json",
})
.then(function(response) {
console.log(response);
return response.result.checkout_id;
});
}
}