diff --git a/webshop/templates/pages/order.html b/webshop/templates/pages/order.html
index 2e11784b91..6af149d54d 100644
--- a/webshop/templates/pages/order.html
+++ b/webshop/templates/pages/order.html
@@ -36,14 +36,18 @@
{{ doc.name }}
diff --git a/webshop/templates/pages/order.py b/webshop/templates/pages/order.py
index c97f3eaa62..7da78c4cf4 100644
--- a/webshop/templates/pages/order.py
+++ b/webshop/templates/pages/order.py
@@ -2,7 +2,9 @@
# License: GNU General Public License v3. See license.txt
import frappe
+from erpnext.templates.pages.order import get_payment_details
from frappe import _
+from frappe.sessions import get_csrf_token
from webshop.webshop.doctype.webshop_settings.webshop_settings import show_attachments
@@ -38,6 +40,10 @@ def get_context(context):
if not frappe.has_website_permission(context.doc):
frappe.throw(_("Not Permitted"), frappe.PermissionError)
+ context.show_pay_button, context.pay_amount = get_payment_details(context.doc)
+ if context.enabled_checkout and context.show_pay_button:
+ context.csrf_token = get_csrf_token()
+
if context.doc.get("customer"):
# check for the loyalty program of the customer
customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program")
diff --git a/webshop/webshop/crud_events/item/validate_duplicate_website_item.py b/webshop/webshop/crud_events/item/validate_duplicate_website_item.py
index 53ff840968..ce2c9c1f40 100644
--- a/webshop/webshop/crud_events/item/validate_duplicate_website_item.py
+++ b/webshop/webshop/crud_events/item/validate_duplicate_website_item.py
@@ -24,7 +24,7 @@ def execute(doc, method=None, old_name=None, new_name=None, merge=False):
if len(web_items) <= 1:
return
- old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
+ old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name)
web_item_link = get_link_to_form("Website Item", old_web_item)
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
diff --git a/webshop/webshop/doctype/override_doctype/item.py b/webshop/webshop/doctype/override_doctype/item.py
index 2045439a5e..466101c896 100644
--- a/webshop/webshop/doctype/override_doctype/item.py
+++ b/webshop/webshop/doctype/override_doctype/item.py
@@ -34,7 +34,7 @@ def validate_duplicate_website_item_before_merge(self, old_name, new_name):
if len(web_items) <= 1:
return
- old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
+ old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name)
web_item_link = get_link_to_form("Website Item", old_web_item)
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
diff --git a/webshop/webshop/product_data_engine/query.py b/webshop/webshop/product_data_engine/query.py
index 4fb6c60543..1fb0f9981f 100644
--- a/webshop/webshop/product_data_engine/query.py
+++ b/webshop/webshop/product_data_engine/query.py
@@ -56,7 +56,7 @@ def query(self, attributes=None, fields=None, search_term=None, start=0, item_gr
"""
# track if discounts included in field filters
self.filter_with_discount = bool(fields.get("discount"))
- result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
+ result, discount_list, cart_items, count = [], [], [], 0
if fields:
self.build_fields_filters(fields)
diff --git a/webshop/webshop/redisearch_utils.py b/webshop/webshop/redisearch_utils.py
index 55cbb4bc7d..9270b504c2 100644
--- a/webshop/webshop/redisearch_utils.py
+++ b/webshop/webshop/redisearch_utils.py
@@ -94,7 +94,7 @@ def create_website_items_index():
# TODO: sortable?
index.create_index(
- [TextField("web_item_name", sortable=True)] + idx_fields,
+ [TextField("web_item_name", sortable=True), *idx_fields],
definition=idx_def,
)
diff --git a/webshop/webshop/shopping_cart/cart.py b/webshop/webshop/shopping_cart/cart.py
index 9bde7119cc..1f6bf808f7 100644
--- a/webshop/webshop/shopping_cart/cart.py
+++ b/webshop/webshop/shopping_cart/cart.py
@@ -708,7 +708,6 @@ def get_applicable_shipping_rules(party=None, quotation=None):
shipping_rules = get_shipping_rules(quotation)
if shipping_rules:
- rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label")
# we need this in sorted order as per the position of the rule in the settings page
return [[rule, rule] for rule in shipping_rules]
diff --git a/webshop/webshop/shopping_cart/test_shopping_cart.py b/webshop/webshop/shopping_cart/test_shopping_cart.py
index 79ff5d6140..2d57abff15 100644
--- a/webshop/webshop/shopping_cart/test_shopping_cart.py
+++ b/webshop/webshop/shopping_cart/test_shopping_cart.py
@@ -5,10 +5,13 @@
import unittest
import frappe
+from bs4 import BeautifulSoup
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from frappe.tests.utils import change_settings
from frappe.utils import add_months, cint, nowdate
+from webshop.templates.pages.order import get_context
from webshop.webshop.doctype.website_item.website_item import make_website_item
from webshop.webshop.shopping_cart.cart import (
_get_cart_quotation,
@@ -63,6 +66,74 @@ def test_get_cart_new_user(self):
return quotation
+ def test_order_payment_form(self):
+ order = make_sales_order(qty=1, rate=1000)
+ draft = make_sales_order(do_not_submit=True)
+ cancelled = make_sales_order()
+ cancelled.cancel()
+ gateway = frappe.get_doc({"doctype": "Payment Gateway", "gateway": "_Test Webshop Gateway"}).insert()
+ gateway_account = frappe.get_doc(
+ {
+ "doctype": "Payment Gateway Account",
+ "payment_gateway": gateway.name,
+ "payment_account": frappe.get_cached_value("Company", order.company, "default_cash_account"),
+ "company": order.company,
+ "currency": order.currency,
+ }
+ ).insert()
+ settings = frappe.get_doc("Webshop Settings")
+ settings.enable_checkout = 1
+ settings.payment_gateway_account = gateway_account.name
+ settings.save()
+ user = frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": "_test_webshop_payment@example.com",
+ "first_name": "Webshop Customer",
+ "user_type": "Website User",
+ "send_welcome_email": 0,
+ "roles": [{"role": "Customer"}],
+ }
+ ).insert()
+ customer = frappe.get_doc("Customer", order.customer)
+ customer.append("portal_users", {"user": user.name})
+ customer.save()
+ self.addCleanup(setattr, frappe.local, "form_dict", frappe.local.form_dict)
+ frappe.set_user(user.name)
+ self.assertFalse(frappe.has_permission("Payment Request", "create"))
+
+ for document in (order, draft, cancelled):
+ with self.subTest(docstatus=document.docstatus):
+ frappe.local.form_dict = frappe._dict(doctype=document.doctype, name=document.name)
+ context = frappe._dict()
+ get_context(context)
+ template = frappe.get_template("webshop/templates/pages/order.html")
+ html = "".join(template.blocks["header_actions"](template.new_context(context)))
+ button = BeautifulSoup(html, "html.parser").find(id="pay-for-order")
+ if document.docstatus != 1:
+ self.assertIsNone(button)
+ continue
+
+ self.assertIsNotNone(button)
+ form = button.find_parent("form")
+ self.assertIsNotNone(form)
+ self.assertEqual(form["method"], "post")
+ self.assertEqual(
+ form["action"],
+ "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request",
+ )
+ self.assertEqual(context.pay_amount, order.grand_total)
+ self.assertEqual(
+ {field["name"]: field["value"] for field in form.find_all("input", type="hidden")},
+ {
+ "csrf_token": context.csrf_token,
+ "dn": order.name,
+ "dt": order.doctype,
+ "submit_doc": "1",
+ "order_type": "Shopping Cart",
+ },
+ )
+
def test_get_cart_customer(self, customer="_Test Customer 2"):
def validate_quotation(customer_name):
# test if quotation with customer is fetched
diff --git a/webshop/webshop/variant_selector/item_variants_cache.py b/webshop/webshop/variant_selector/item_variants_cache.py
index 1339d16f58..5f74d626db 100644
--- a/webshop/webshop/variant_selector/item_variants_cache.py
+++ b/webshop/webshop/variant_selector/item_variants_cache.py
@@ -87,7 +87,7 @@ def build_cache(self):
item_attribute_value_map.setdefault(item_code, {})[attribute] = attribute_value
optional_attributes = set()
- for item_code, attr_dict in item_attribute_value_map.items():
+ for attr_dict in item_attribute_value_map.values():
for attribute in attributes:
if attribute not in attr_dict:
optional_attributes.add(attribute)
diff --git a/webshop/webshop/variant_selector/utils.py b/webshop/webshop/variant_selector/utils.py
index 7a31879d8d..35b546b9bc 100644
--- a/webshop/webshop/variant_selector/utils.py
+++ b/webshop/webshop/variant_selector/utils.py
@@ -68,8 +68,9 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
return res
-@frappe.whitelist(allow_guest=True)
-def get_attributes_and_values(item_code):
+# Guest access is required by the storefront variant selector.
+@frappe.whitelist(allow_guest=True) # nosemgrep: guest-whitelisted-method
+def get_attributes_and_values(item_code: str):
"""Build a list of attributes and their possible values.
This will ignore the values upon selection of which there cannot exist one item.
"""
@@ -80,7 +81,8 @@ def get_attributes_and_values(item_code):
attribute_list = [a.attribute for a in attributes]
valid_options = {}
- for item_code, attribute, attribute_value in item_variants_data:
+ for row in item_variants_data:
+ attribute, attribute_value = row[1:]
if attribute in attribute_list:
valid_options.setdefault(attribute, set()).add(attribute_value)
@@ -112,8 +114,9 @@ def get_attributes_and_values(item_code):
return attributes
-@frappe.whitelist(allow_guest=True)
-def get_next_attribute_and_values(item_code, selected_attributes):
+# Guest access is required by the storefront variant selector.
+@frappe.whitelist(allow_guest=True) # nosemgrep: guest-whitelisted-method
+def get_next_attribute_and_values(item_code: str, selected_attributes: str | dict):
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
"""Find the count of Items that match the selected attributes.
@@ -183,7 +186,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
if exact_match and len(exact_match) == 1:
product_id = exact_match[0]
elif filtered_items_count == 1:
- product_id = list(filtered_items)[0]
+ product_id = next(iter(filtered_items))
if product_id:
warehouse = frappe.get_cached_value("Website Item", {"item_code": product_id}, "website_warehouse")