Merge branch 'develop' into get-all-mod

This commit is contained in:
Aradhya Tripathi 2022-07-29 14:27:37 +05:30 committed by GitHub
commit 5ccaebca34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 407 additions and 4551 deletions

View file

@ -30,7 +30,7 @@ def docs_link_exists(body):
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/frappe/pulls/{}".format(pr))
response = requests.get(f"https://api.github.com/repos/frappe/frappe/pulls/{pr}")
if response.ok:
payload = response.json()

View file

@ -5,55 +5,63 @@ cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --python "$(which python)" --frappe-path "${GITHUB_WORKSPACE}"
cd ./frappe-bench || exit
bench -v setup requirements --dev
if [ "$TYPE" == "ui" ]; then
bench -v setup requirements --node;
fi
echo "Setting Up Sites & Database..."
mkdir ~/frappe-bench/sites/test_site
cp "${GITHUB_WORKSPACE}/.github/helper/consumer_db/$DB.json" ~/frappe-bench/sites/test_site/site_config.json
if [ "$TYPE" == "server" ]; then
mkdir ~/frappe-bench/sites/test_site_producer;
cp "${GITHUB_WORKSPACE}/.github/helper/producer_db/$DB.json" ~/frappe-bench/sites/test_site_producer/site_config.json;
mkdir ~/frappe-bench/sites/test_site_producer;
cp "${GITHUB_WORKSPACE}/.github/helper/producer_db/$DB.json" ~/frappe-bench/sites/test_site_producer/site_config.json;
fi
if [ "$DB" == "mariadb" ];then
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_consumer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_consumer'@'localhost' IDENTIFIED BY 'test_frappe_consumer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_consumer\`.* TO 'test_frappe_consumer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_consumer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_consumer'@'localhost' IDENTIFIED BY 'test_frappe_consumer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_consumer\`.* TO 'test_frappe_consumer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_producer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_producer'@'localhost' IDENTIFIED BY 'test_frappe_producer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_producer\`.* TO 'test_frappe_producer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
fi
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_producer";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_producer'@'localhost' IDENTIFIED BY 'test_frappe_producer'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_producer\`.* TO 'test_frappe_producer'@'localhost'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
fi
if [ "$DB" == "postgres" ];then
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_consumer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_consumer WITH PASSWORD 'test_frappe'" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_consumer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_consumer WITH PASSWORD 'test_frappe'" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_producer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_producer WITH PASSWORD 'test_frappe'" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_producer" -U postgres;
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_producer WITH PASSWORD 'test_frappe'" -U postgres;
fi
cd ./frappe-bench || exit
echo "Setting Up Procfile..."
sed -i 's/^watch:/# watch:/g' Procfile
sed -i 's/^schedule:/# schedule:/g' Procfile
if [ "$TYPE" == "server" ]; then
sed -i 's/^socketio:/# socketio:/g' Procfile;
sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile;
fi
if [ "$TYPE" == "ui" ]; then
sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile;
fi
if [ "$TYPE" == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; fi
if [ "$TYPE" == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi
if [ "$TYPE" == "ui" ]; then bench -v setup requirements --node; fi
bench -v setup requirements --dev
if [ "$TYPE" == "ui" ]; then sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile; fi
echo "Starting Bench..."
bench start &> bench_start.log &
bench --site test_site reinstall --yes
if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi
if [ "$TYPE" == "server" ]; then CI=Yes bench build --app frappe; fi
if [ "$TYPE" == "server" ]; then
bench --site test_site_producer reinstall --yes;
CI=Yes bench build --app frappe;
fi

View file

@ -3,8 +3,11 @@ set -e
echo "Setting Up System Dependencies..."
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
install_wkhtmltopdf() {
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
}
install_wkhtmltopdf &
curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
sudo bash mariadb_repo_setup --mariadb-server-version=10.6

View file

@ -20,19 +20,12 @@ for _file in files_to_scan:
if 'frappe-lint: disable-translate' in line:
continue
start_matches = start_pattern.search(line)
if start_matches:
starts_with_f = starts_with_f_pattern.search(line)
if starts_with_f:
has_f_string = f_string_pattern.search(line)
if has_f_string:
if start_matches := start_pattern.search(line):
if starts_with_f := starts_with_f_pattern.search(line):
if has_f_string := f_string_pattern.search(line):
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
continue
match = pattern.search(line)
error_found = False

View file

@ -1,22 +0,0 @@
name: 'Python Dependency Check'
on:
pull_request:
workflow_dispatch:
push:
branches: [ develop ]
permissions:
contents: read
jobs:
deps-vulnerable-check:
name: 'Vulnerable Dependency'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: actions/checkout@v3
- run: pip install pip-audit
- run: pip-audit ${GITHUB_WORKSPACE}

View file

@ -1,20 +0,0 @@
name: 'Trigger Docker build on release'
on:
release:
types: [released]
permissions:
contents: read
jobs:
curl:
permissions:
contents: none
name: 'Trigger Docker build on release'
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'

View file

@ -1,28 +0,0 @@
name: 'Documentation Check'
on:
pull_request:
types: [ opened, synchronize, reopened, edited ]
permissions:
contents: read
jobs:
docs-required:
name: 'Documentation Required'
runs-on: ubuntu-latest
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: 'Clone repo'
uses: actions/checkout@v3
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER

View file

@ -1,29 +1,86 @@
name: Linters
on:
pull_request: { }
pull_request:
workflow_dispatch:
push:
branches: [ develop ]
permissions:
contents: read
concurrency:
group: commitcheck-frappe-${{ github.event.number }}
cancel-in-progress: true
jobs:
linters:
name: Frappe Linter
commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 16
check-latest: true
- name: Set up Python
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
docs-required:
name: 'Documentation Required'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: actions/checkout@v3
- name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0
- name: Validate Docs
env:
PR_NUMBER: ${{ github.event.number }}
run: |
pip install requests --quiet
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER
linter:
name: 'Frappe Linter'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: pre-commit/action@v3.0.0
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep==0.97.0
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
run: |
pip install semgrep==0.97.0
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
deps-vulnerable-check:
name: 'Vulnerable Dependency Check'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: actions/checkout@v3
- run: |
pip install pip-audit
pip-audit ${GITHUB_WORKSPACE}

View file

@ -1,8 +1,11 @@
name: 'Frappe Assets'
name: 'Release'
on:
release:
types: [ created ]
types: [released]
permissions:
contents: read
env:
GITHUB_TOKEN: ${{ github.token }}
@ -47,3 +50,16 @@ jobs:
asset_path: build/assets.tar.gz
asset_name: assets.tar.gz
asset_content_type: application/octet-stream
docker-release:
name: 'Trigger Docker build on release'
runs-on: ubuntu-latest
permissions:
contents: none
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'

View file

@ -1,7 +1,8 @@
name: Patch
on: [pull_request, workflow_dispatch]
name: Server (MariaDB)
on:
pull_request:
workflow_dispatch:
concurrency:
group: patch-mariadb-develop-${{ github.event.number }}
@ -12,11 +13,10 @@ permissions:
jobs:
test:
name: Patch
runs-on: ubuntu-latest
timeout-minutes: 60
name: Patch Test
services:
mariadb:
image: mariadb:10.6

View file

@ -1,6 +1,7 @@
name: 'Frappe Assets'
on:
workflow_dispatch:
push:
branches: [ develop ]

View file

@ -1,30 +0,0 @@
name: Semantic Commits
on:
pull_request: {}
permissions:
contents: read
concurrency:
group: commitcheck-frappe-${{ github.event.number }}
cancel-in-progress: true
jobs:
commitlint:
name: Check Commit Titles
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 16
check-latest: true
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}

View file

@ -1,4 +1,4 @@
name: Server
name: Server (MariaDB)
on:
pull_request:
@ -16,6 +16,7 @@ permissions:
jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 60
@ -24,8 +25,6 @@ jobs:
matrix:
container: [1, 2]
name: Python Unit Tests (MariaDB)
services:
mariadb:
image: mariadb:10.6

View file

@ -1,4 +1,4 @@
name: Server
name: Server (Postgres)
on:
pull_request:
@ -15,6 +15,7 @@ permissions:
jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 60
@ -23,8 +24,6 @@ jobs:
matrix:
container: [1, 2]
name: Python Unit Tests (Postgres)
services:
postgres:
image: postgres:12.4

View file

@ -112,16 +112,21 @@ function log(...args) {
function get_redis_subscriber(kind) {
// get redis subscriber that aborts after 10 connection attempts
let { get_redis_subscriber: get_redis } = require("../node_utils");
return get_redis(kind, {
retry_strategy: function(options) {
let retry_strategy;
let { get_redis_subscriber: get_redis, get_conf } = require("../node_utils");
if (process.env.CI == 1 || get_conf().developer_mode == 1) {
retry_strategy = () => { }
} else {
retry_strategy = function (options) {
// abort after 10 connection attempts
if (options.attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 100, 2000);
}
});
}
return get_redis(kind, { retry_strategy });
}
module.exports = {

View file

@ -391,7 +391,6 @@ def get_notification_settings():
return frappe.get_cached_doc("Notification Settings", frappe.session.user)
@frappe.whitelist()
def get_link_title_doctypes():
dts = frappe.get_all("DocType", {"show_title_field_in_link": 1})
custom_dts = frappe.get_all(

View file

@ -262,7 +262,7 @@ class File(Document):
def validate_remote_file(self):
"""Validates if file uploaded using URL already exist"""
site_url = get_url()
if "/files/" in self.file_url and self.file_url.startswith(site_url):
if self.file_url and "/files/" in self.file_url and self.file_url.startswith(site_url):
self.file_url = self.file_url.split(site_url, 1)[1]
def set_folder_name(self):

View file

@ -510,6 +510,16 @@ class TestFile(FrappeTestCase):
).insert(ignore_permissions=True)
self.assertRaisesRegex(ValidationError, "not a zip file", test_file.unzip)
def test_create_file_without_file_url(self):
test_file = frappe.get_doc(
{
"doctype": "File",
"file_name": "logo",
"content": "frappe",
}
).insert()
assert test_file is not None
class TestAttachment(unittest.TestCase):
test_doctype = "Test For Attachment"

View file

@ -1,8 +0,0 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Payment Gateway', {
refresh: function(frm) {
}
});

View file

@ -1,55 +0,0 @@
{
"actions": [],
"autoname": "field:gateway",
"creation": "2022-01-24 21:09:47.229371",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"gateway",
"gateway_settings",
"gateway_controller"
],
"fields": [
{
"fieldname": "gateway",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Gateway",
"reqd": 1,
"unique": 1
},
{
"fieldname": "gateway_settings",
"fieldtype": "Link",
"label": "Gateway Settings",
"options": "DocType"
},
{
"fieldname": "gateway_controller",
"fieldtype": "Dynamic Link",
"label": "Gateway Controller",
"options": "gateway_settings"
}
],
"links": [],
"modified": "2022-01-24 21:17:03.864719",
"modified_by": "Administrator",
"module": "Core",
"name": "Payment Gateway",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"read": 1,
"role": "System Manager",
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View file

@ -1,8 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# License: MIT. See LICENSE
from frappe.model.document import Document
class PaymentGateway(Document):
pass

View file

@ -1,9 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import unittest
# test_records = frappe.get_test_records('Payment Gateway')
class TestPaymentGateway(unittest.TestCase):
pass

View file

@ -1,13 +1,10 @@
{
"accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 1,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 0,
"breadcrumbs": "[{\"title\": _(\"My Account\"), \"route\": \"me\"}]",
"creation": "2016-09-19 05:16:59.242754",

View file

@ -54,6 +54,23 @@ class Database:
CHILD_TABLE_COLUMNS = ("parent", "parenttype", "parentfield")
MAX_WRITES_PER_TRANSACTION = 200_000
# NOTE:
# FOR MARIADB - using no cache - as during backup, if the sequence was used in anyform,
# it drops the cache and uses the next non cached value in setval query and
# puts that in the backup file, which will start the counter
# from that value when inserting any new record in the doctype.
# By default the cache is 1000 which will mess up the sequence when
# using the system after a restore.
#
# Another case could be if the cached values expire then also there is a chance of
# the cache being skipped.
#
# FOR POSTGRES - The sequence cache for postgres is per connection.
# Since we're opening and closing connections for every request this results in skipping the cache
# to the next non-cached value hence not using cache in postgres.
# ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
SEQUENCE_CACHE = 0
class InvalidColumnName(frappe.ValidationError):
pass

View file

@ -129,15 +129,6 @@ class MariaDBConnectionUtil:
class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
REGEX_CHARACTER = "regexp"
# NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
# it drops the cache and uses the next non cached value in setval query and
# puts that in the backup file, which will start the counter
# from that value when inserting any new record in the doctype.
# By default the cache is 1000 which will mess up the sequence when
# using the system after a restore.
# issue link: https://jira.mariadb.org/browse/MDEV-21786
SEQUENCE_CACHE = 50
CONVERSION_MAP = conversions | {
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,

View file

@ -44,7 +44,7 @@ class MariaDBTable(DBTable):
# NOTE: not used nextval func as default as the ability to restore
# database with sequences has bugs in mariadb and gives a scary error.
# issue link: https://jira.mariadb.org/browse/MDEV-21786
# issue link: https://jira.mariadb.org/browse/MDEV-20070
name_column = "name bigint primary key"
# create table

View file

@ -102,12 +102,6 @@ class PostgresExceptionUtil:
class PostgresDatabase(PostgresExceptionUtil, Database):
REGEX_CHARACTER = "~"
# NOTE; The sequence cache for postgres is per connection.
# Since we're opening and closing connections for every transaction this results in skipping the cache
# to the next non-cached value hence not using cache in postgres.
# ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
SEQUENCE_CACHE = 0
default_port = "5432"
def setup_type_map(self):

View file

@ -187,8 +187,6 @@ class Engine:
@cached_property
def OPERATOR_MAP(self):
from frappe.boot import get_additional_filters_from_hooks
# default operators
all_operators = OPERATOR_MAP.copy()

View file

@ -42,7 +42,8 @@
"column_break_2",
"color",
"section_break_10",
"last_synced_on"
"last_synced_on",
"roles"
],
"fields": [
{
@ -277,13 +278,20 @@
"fieldtype": "Link",
"label": "Parent Document Type",
"options": "DocType"
},
{
"fieldname": "roles",
"fieldtype": "Table",
"label": "Roles",
"options": "Has Role"
}
],
"links": [],
"modified": "2021-11-09 17:18:11.456145",
"modified": "2022-07-27 11:09:09.203236",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@ -323,5 +331,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View file

@ -11,7 +11,7 @@ from frappe.config import get_modules_from_all_apps_for_user
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
from frappe.utils import cint, get_datetime, getdate, now_datetime, nowdate
from frappe.utils import cint, get_datetime, getdate, has_common, now_datetime, nowdate
from frappe.utils.dashboard import cache_source
from frappe.utils.data import format_date
from frappe.utils.dateutils import (
@ -87,6 +87,11 @@ def has_permission(doc, ptype, user):
if doc.document_type in allowed_doctypes:
return True
if doc.roles:
allowed = [d.role for d in doc.roles]
if has_common(roles, allowed):
return True
return False

View file

@ -133,6 +133,22 @@ def get_email_header(doc):
return header_map[doc.type or "Default"]
@frappe.whitelist()
def get_notification_logs(limit=20):
notification_logs = frappe.db.get_list(
"Notification Log", fields=["*"], limit=limit, order_by="creation desc"
)
users = [log.from_user for log in notification_logs]
users = [*set(users)] # remove duplicates
user_info = frappe._dict()
for user in users:
frappe.utils.add_user_info(user, user_info)
return {"notification_logs": notification_logs, "user_info": user_info}
@frappe.whitelist()
def mark_all_as_read():
unread_docs_list = frappe.db.get_all(

View file

@ -199,7 +199,6 @@ scheduler_events = {
"frappe.email.queue.flush",
"frappe.email.doctype.email_account.email_account.pull",
"frappe.email.doctype.email_account.email_account.notify_unreplied",
"frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment",
"frappe.utils.global_search.sync_global_search",
"frappe.monitor.flush",
],

View file

@ -1,6 +0,0 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Braintree Settings', {
});

View file

@ -1,273 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:gateway_name",
"beta": 0,
"creation": "2018-02-05 13:46:12.101852",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Gateway Name",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "merchant_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Merchant ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "public_key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Public Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "private_key",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Private Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "use_sandbox",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Use Sandbox",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_img",
"fieldtype": "Attach Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Header Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-05 14:33:06.050377",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Braintree Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View file

@ -1,287 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and contributors
# License: MIT. See LICENSE
from urllib.parse import urlencode
import braintree
import frappe
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, get_url
class BraintreeSettings(Document):
supported_currencies = [
"AED",
"AMD",
"AOA",
"ARS",
"AUD",
"AWG",
"AZN",
"BAM",
"BBD",
"BDT",
"BGN",
"BIF",
"BMD",
"BND",
"BOB",
"BRL",
"BSD",
"BWP",
"BYN",
"BZD",
"CAD",
"CHF",
"CLP",
"CNY",
"COP",
"CRC",
"CVE",
"CZK",
"DJF",
"DKK",
"DOP",
"DZD",
"EGP",
"ETB",
"EUR",
"FJD",
"FKP",
"GBP",
"GEL",
"GHS",
"GIP",
"GMD",
"GNF",
"GTQ",
"GYD",
"HKD",
"HNL",
"HRK",
"HTG",
"HUF",
"IDR",
"ILS",
"INR",
"ISK",
"JMD",
"JPY",
"KES",
"KGS",
"KHR",
"KMF",
"KRW",
"KYD",
"KZT",
"LAK",
"LBP",
"LKR",
"LRD",
"LSL",
"LTL",
"MAD",
"MDL",
"MKD",
"MNT",
"MOP",
"MUR",
"MVR",
"MWK",
"MXN",
"MYR",
"MZN",
"NAD",
"NGN",
"NIO",
"NOK",
"NPR",
"NZD",
"PAB",
"PEN",
"PGK",
"PHP",
"PKR",
"PLN",
"PYG",
"QAR",
"RON",
"RSD",
"RUB",
"RWF",
"SAR",
"SBD",
"SCR",
"SEK",
"SGD",
"SHP",
"SLL",
"SOS",
"SRD",
"STD",
"SVC",
"SYP",
"SZL",
"THB",
"TJS",
"TOP",
"TRY",
"TTD",
"TWD",
"TZS",
"UAH",
"UGX",
"USD",
"UYU",
"UZS",
"VEF",
"VND",
"VUV",
"WST",
"XAF",
"XCD",
"XOF",
"XPF",
"YER",
"ZAR",
"ZMK",
"ZWD",
]
def validate(self):
if not self.flags.ignore_mandatory:
self.configure_braintree()
def on_update(self):
create_payment_gateway(
"Braintree-" + self.gateway_name, settings="Braintree Settings", controller=self.gateway_name
)
call_hook_method("payment_gateway_enabled", gateway="Braintree-" + self.gateway_name)
def configure_braintree(self):
if self.use_sandbox:
environment = "sandbox"
else:
environment = "production"
braintree.Configuration.configure(
environment=environment,
merchant_id=self.merchant_id,
public_key=self.public_key,
private_key=self.get_password(fieldname="private_key", raise_exception=False),
)
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(
_(
"Please select another payment method. Stripe does not support transactions in currency '{0}'"
).format(currency)
)
def get_payment_url(self, **kwargs):
return get_url(f"./integrations/braintree_checkout?{urlencode(kwargs)}")
def create_payment_request(self, data):
self.data = frappe._dict(data)
try:
self.integration_request = create_request_log(self.data, service_name="Braintree")
return self.create_charge_on_braintree()
except Exception:
frappe.log_error(frappe.get_traceback())
return {
"redirect_to": frappe.redirect_to_message(
_("Server Error"),
_(
"There seems to be an issue with the server's braintree configuration. Don't worry, in case of failure, the amount will get refunded to your account."
),
),
"status": 401,
}
def create_charge_on_braintree(self):
self.configure_braintree()
redirect_to = self.data.get("redirect_to") or None
redirect_message = self.data.get("redirect_message") or None
result = braintree.Transaction.sale(
{
"amount": self.data.amount,
"payment_method_nonce": self.data.payload_nonce,
"options": {"submit_for_settlement": True},
}
)
if result.is_success:
self.integration_request.db_set("status", "Completed", update_modified=False)
self.flags.status_changed_to = "Completed"
self.integration_request.db_set("output", result.transaction.status, update_modified=False)
elif result.transaction:
self.integration_request.db_set("status", "Failed", update_modified=False)
error_log = frappe.log_error(
"code: "
+ str(result.transaction.processor_response_code)
+ " | text: "
+ str(result.transaction.processor_response_text),
"Braintree Payment Error",
)
self.integration_request.db_set("error", error_log.error, update_modified=False)
else:
self.integration_request.db_set("status", "Failed", update_modified=False)
for error in result.errors.deep_errors:
error_log = frappe.log_error(
"code: " + str(error.code) + " | message: " + str(error.message), "Braintree Payment Error"
)
self.integration_request.db_set("error", error_log.error, update_modified=False)
if self.flags.status_changed_to == "Completed":
status = "Completed"
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(
self.data.reference_doctype, self.data.reference_docname
).run_method("on_payment_authorized", self.flags.status_changed_to)
braintree_success_page = frappe.get_hooks("braintree_success_page")
if braintree_success_page:
custom_redirect_to = frappe.get_attr(braintree_success_page[-1])(self.data)
except Exception:
frappe.log_error(frappe.get_traceback())
if custom_redirect_to:
redirect_to = custom_redirect_to
redirect_url = "payment-success"
else:
status = "Error"
redirect_url = "payment-failed"
if redirect_to:
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message:
redirect_url += "&" + urlencode({"redirect_message": redirect_message})
return {"redirect_to": redirect_url, "status": status}
def get_gateway_controller(doc):
payment_request = frappe.get_doc("Payment Request", doc)
gateway_controller = frappe.db.get_value(
"Payment Gateway", payment_request.payment_gateway, "gateway_controller"
)
return gateway_controller
def get_client_token(doc):
gateway_controller = get_gateway_controller(doc)
settings = frappe.get_doc("Braintree Settings", gateway_controller)
settings.configure_braintree()
return braintree.ClientToken.generate()

View file

@ -1,7 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and Contributors
# License: MIT. See LICENSE
import unittest
class TestBraintreeSettings(unittest.TestCase):
pass

View file

@ -1,8 +0,0 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('PayPal Settings', {
refresh: function(frm) {
}
});

View file

@ -1,202 +0,0 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-09-21 08:03:01.009852",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "api_username",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "API Username",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "api_password",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "API Password",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "signature",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Signature",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Check this if you are testing your payment using the Sandbox API",
"fieldname": "paypal_sandbox",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Use Sandbox",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Mention transaction completion page URL",
"fieldname": "redirect_to",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:40:31.574789",
"modified_by": "Administrator",
"module": "Integrations",
"name": "PayPal Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View file

@ -1,505 +0,0 @@
# Copyright (c) 2015, Frappe Technologies and contributors
# License: MIT. See LICENSE
"""
# Integrating PayPal
### 1. Validate Currency Support
Example:
from frappe.integrations.utils import get_payment_gateway_controller
controller = get_payment_gateway_controller("PayPal")
controller().validate_transaction_currency(currency)
### 2. Redirect for payment
Example:
payment_details = {
"amount": 600,
"title": "Payment for bill : 111",
"description": "payment via cart",
"reference_doctype": "Payment Request",
"reference_docname": "PR0001",
"payer_email": "NuranVerkleij@example.com",
"payer_name": "Nuran Verkleij",
"order_id": "111",
"currency": "USD",
"payment_gateway": "Razorpay",
"subscription_details": {
"plan_id": "plan_12313", # if Required
"start_date": "2018-08-30",
"billing_period": "Month" #(Day, Week, SemiMonth, Month, Year),
"billing_frequency": 1,
"customer_notify": 1,
"upfront_amount": 1000
}
}
# redirect the user to this url
url = controller().get_payment_url(**payment_details)
### 3. On Completion of Payment
Write a method for `on_payment_authorized` in the reference doctype
Example:
def on_payment_authorized(payment_status):
# your code to handle callback
##### Note:
payment_status - payment gateway will put payment status on callback.
For paypal payment status parameter is one from: [Completed, Cancelled, Failed]
More Details:
<div class="small">For details on how to get your API credentials, follow this link: <a href="https://developer.paypal.com/docs/classic/api/apiCredentials/" target="_blank">https://developer.paypal.com/docs/classic/api/apiCredentials/</a></div>
"""
import json
from urllib.parse import urlencode
import pytz
import frappe
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log, make_post_request
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, get_datetime, get_url
api_path = "/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings"
class PayPalSettings(Document):
supported_currencies = [
"AUD",
"BRL",
"CAD",
"CZK",
"DKK",
"EUR",
"HKD",
"HUF",
"ILS",
"JPY",
"MYR",
"MXN",
"TWD",
"NZD",
"NOK",
"PHP",
"PLN",
"GBP",
"RUB",
"SGD",
"SEK",
"CHF",
"THB",
"TRY",
"USD",
]
def __setup__(self):
setattr(self, "use_sandbox", 0)
def setup_sandbox_env(self, token):
data = json.loads(frappe.db.get_value("Integration Request", token, "data"))
setattr(self, "use_sandbox", cint(frappe._dict(data).use_sandbox) or 0)
def validate(self):
create_payment_gateway("PayPal")
call_hook_method("payment_gateway_enabled", gateway="PayPal")
if not self.flags.ignore_mandatory:
self.validate_paypal_credentails()
def on_update(self):
pass
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(
_(
"Please select another payment method. PayPal does not support transactions in currency '{0}'"
).format(currency)
)
def get_paypal_params_and_url(self):
params = {
"USER": self.api_username,
"PWD": self.get_password(fieldname="api_password", raise_exception=False),
"SIGNATURE": self.signature,
"VERSION": "98",
"METHOD": "GetPalDetails",
}
if hasattr(self, "use_sandbox") and self.use_sandbox:
params.update(
{
"USER": frappe.conf.sandbox_api_username,
"PWD": frappe.conf.sandbox_api_password,
"SIGNATURE": frappe.conf.sandbox_signature,
}
)
api_url = (
"https://api-3t.sandbox.paypal.com/nvp"
if (self.paypal_sandbox or self.use_sandbox)
else "https://api-3t.paypal.com/nvp"
)
return params, api_url
def validate_paypal_credentails(self):
params, url = self.get_paypal_params_and_url()
params = urlencode(params)
try:
res = make_post_request(url=url, data=params.encode("utf-8"))
if res["ACK"][0] == "Failure":
raise Exception
except Exception:
frappe.throw(_("Invalid payment gateway credentials"))
def get_payment_url(self, **kwargs):
setattr(self, "use_sandbox", cint(kwargs.get("use_sandbox", 0)))
response = self.execute_set_express_checkout(**kwargs)
if self.paypal_sandbox or self.use_sandbox:
return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}"
else:
return_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}"
kwargs.update(
{"token": response.get("TOKEN")[0], "correlation_id": response.get("CORRELATIONID")[0]}
)
create_request_log(kwargs, service_name="PayPal", name=kwargs["token"])
return return_url.format(kwargs["token"])
def execute_set_express_checkout(self, **kwargs):
params, url = self.get_paypal_params_and_url()
params.update(
{
"METHOD": "SetExpressCheckout",
"returnUrl": get_url(f"{api_path}.get_express_checkout_details"),
"cancelUrl": get_url("/payment-cancel"),
"PAYMENTREQUEST_0_PAYMENTACTION": "SALE",
"PAYMENTREQUEST_0_AMT": kwargs["amount"],
"PAYMENTREQUEST_0_CURRENCYCODE": kwargs["currency"].upper(),
}
)
if kwargs.get("subscription_details"):
self.configure_recurring_payments(params, kwargs)
params = urlencode(params)
response = make_post_request(url, data=params.encode("utf-8"))
if response.get("ACK")[0] != "Success":
frappe.throw(_("Looks like something is wrong with this site's Paypal configuration."))
return response
def configure_recurring_payments(self, params, kwargs):
# removing the params as we have to setup rucurring payments
for param in (
"PAYMENTREQUEST_0_PAYMENTACTION",
"PAYMENTREQUEST_0_AMT",
"PAYMENTREQUEST_0_CURRENCYCODE",
):
del params[param]
params.update(
{
"L_BILLINGTYPE0": "RecurringPayments", # The type of billing agreement
"L_BILLINGAGREEMENTDESCRIPTION0": kwargs["description"],
}
)
def get_paypal_and_transaction_details(token):
doc = frappe.get_doc("PayPal Settings")
doc.setup_sandbox_env(token)
params, url = doc.get_paypal_params_and_url()
integration_request = frappe.get_doc("Integration Request", token)
data = json.loads(integration_request.data)
return data, params, url
def setup_redirect(data, redirect_url, custom_redirect_to=None, redirect=True):
redirect_to = data.get("redirect_to") or None
redirect_message = data.get("redirect_message") or None
if custom_redirect_to:
redirect_to = custom_redirect_to
if redirect_to:
redirect_url += "&" + urlencode({"redirect_to": redirect_to})
if redirect_message:
redirect_url += "&" + urlencode({"redirect_message": redirect_message})
# this is done so that functions called via hooks can update flags.redirect_to
if redirect:
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url(redirect_url)
@frappe.whitelist(allow_guest=True, xss_safe=True)
def get_express_checkout_details(token):
try:
doc = frappe.get_doc("PayPal Settings")
doc.setup_sandbox_env(token)
params, url = doc.get_paypal_params_and_url()
params.update({"METHOD": "GetExpressCheckoutDetails", "TOKEN": token})
response = make_post_request(url, data=params)
if response.get("ACK")[0] != "Success":
frappe.respond_as_web_page(
_("Something went wrong"),
_(
"Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}."
).format(response.get("CORRELATIONID", [None])[0]),
indicator_color="red",
http_status_code=frappe.ValidationError.http_status_code,
)
return
doc = frappe.get_doc("Integration Request", token)
update_integration_request_status(
token,
{"payerid": response.get("PAYERID")[0], "payer_email": response.get("EMAIL")[0]},
"Authorized",
doc=doc,
)
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_redirect_uri(doc, token, response.get("PAYERID")[0])
except Exception:
frappe.log_error(frappe.get_traceback())
@frappe.whitelist(allow_guest=True, xss_safe=True)
def confirm_payment(token):
try:
custom_redirect_to = None
data, params, url = get_paypal_and_transaction_details(token)
params.update(
{
"METHOD": "DoExpressCheckoutPayment",
"PAYERID": data.get("payerid"),
"TOKEN": token,
"PAYMENTREQUEST_0_PAYMENTACTION": "SALE",
"PAYMENTREQUEST_0_AMT": data.get("amount"),
"PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency").upper(),
}
)
response = make_post_request(url, data=params)
if response.get("ACK")[0] == "Success":
update_integration_request_status(
token,
{
"transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0],
"correlation_id": response.get("CORRELATIONID")[0],
},
"Completed",
)
if data.get("reference_doctype") and data.get("reference_docname"):
custom_redirect_to = frappe.get_doc(
data.get("reference_doctype"), data.get("reference_docname")
).run_method("on_payment_authorized", "Completed")
frappe.db.commit()
redirect_url = "/integrations/payment-success?doctype={}&docname={}".format(
data.get("reference_doctype"), data.get("reference_docname")
)
else:
redirect_url = "/integrations/payment-failed"
setup_redirect(data, redirect_url, custom_redirect_to)
except Exception:
frappe.log_error(frappe.get_traceback())
@frappe.whitelist(allow_guest=True, xss_safe=True)
def create_recurring_profile(token, payerid):
try:
custom_redirect_to = None
updating = False
data, params, url = get_paypal_and_transaction_details(token)
addons = data.get("addons")
subscription_details = data.get("subscription_details")
if data.get("subscription_id"):
if addons:
updating = True
manage_recurring_payment_profile_status(data["subscription_id"], "Cancel", params, url)
params.update(
{
"METHOD": "CreateRecurringPaymentsProfile",
"PAYERID": payerid,
"TOKEN": token,
"DESC": data.get("description"),
"BILLINGPERIOD": subscription_details.get("billing_period"),
"BILLINGFREQUENCY": subscription_details.get("billing_frequency"),
"AMT": data.get("amount")
if data.get("subscription_amount") == data.get("amount")
else data.get("subscription_amount"),
"CURRENCYCODE": data.get("currency").upper(),
"INITAMT": data.get("upfront_amount"),
}
)
status_changed_to = "Completed" if data.get("starting_immediately") or updating else "Verified"
starts_at = get_datetime(subscription_details.get("start_date")) or frappe.utils.now_datetime()
starts_at = starts_at.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone())).astimezone(
pytz.utc
)
# "PROFILESTARTDATE": datetime.utcfromtimestamp(get_timestamp(starts_at)).isoformat()
params.update({"PROFILESTARTDATE": starts_at.isoformat()})
response = make_post_request(url, data=params)
if response.get("ACK")[0] == "Success":
update_integration_request_status(
token,
{
"profile_id": response.get("PROFILEID")[0],
},
"Completed",
)
if data.get("reference_doctype") and data.get("reference_docname"):
data["subscription_id"] = response.get("PROFILEID")[0]
frappe.flags.data = data
custom_redirect_to = frappe.get_doc(
data.get("reference_doctype"), data.get("reference_docname")
).run_method("on_payment_authorized", status_changed_to)
frappe.db.commit()
redirect_url = "/integrations/payment-success?doctype={}&docname={}".format(
data.get("reference_doctype"), data.get("reference_docname")
)
else:
redirect_url = "/integrations/payment-failed"
setup_redirect(data, redirect_url, custom_redirect_to)
except Exception:
frappe.log_error(frappe.get_traceback())
def update_integration_request_status(token, data, status, error=False, doc=None):
if not doc:
doc = frappe.get_doc("Integration Request", token)
doc.update_status(data, status)
def get_redirect_uri(doc, token, payerid):
data = json.loads(doc.data)
if data.get("subscription_details") or data.get("subscription_id"):
return get_url(f"{api_path}.create_recurring_profile?token={token}&payerid={payerid}")
else:
return get_url(f"{api_path}.confirm_payment?token={token}")
def manage_recurring_payment_profile_status(profile_id, action, args, url):
args.update(
{"METHOD": "ManageRecurringPaymentsProfileStatus", "PROFILEID": profile_id, "ACTION": action}
)
response = make_post_request(url, data=args)
# error code 11556 indicates profile is not in active state(or already cancelled)
# thus could not cancel the subscription.
# thus raise an exception only if the error code is not equal to 11556
if response.get("ACK")[0] != "Success" and response.get("L_ERRORCODE0", [])[0] != "11556":
frappe.throw(_("Failed while amending subscription"))
@frappe.whitelist(allow_guest=True)
def ipn_handler():
try:
data = frappe.local.form_dict
validate_ipn_request(data)
data.update({"payment_gateway": "PayPal"})
doc = frappe.get_doc(
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"request_description": "Subscription Notification",
"is_remote_request": 1,
"status": "Queued",
}
).insert(ignore_permissions=True)
frappe.db.commit()
frappe.enqueue(
method="frappe.integrations.doctype.paypal_settings.paypal_settings.handle_subscription_notification",
queue="long",
timeout=600,
is_async=True,
**{"doctype": "Integration Request", "docname": doc.name},
)
except frappe.InvalidStatusError:
pass
except Exception as e:
frappe.log(frappe.log_error(title=e))
def validate_ipn_request(data):
def _throw():
frappe.throw(_("In Valid Request"), exc=frappe.InvalidStatusError)
if not data.get("recurring_payment_id"):
_throw()
doc = frappe.get_doc("PayPal Settings")
params, url = doc.get_paypal_params_and_url()
params.update(
{"METHOD": "GetRecurringPaymentsProfileDetails", "PROFILEID": data.get("recurring_payment_id")}
)
params = urlencode(params)
res = make_post_request(url=url, data=params.encode("utf-8"))
if res["ACK"][0] != "Success":
_throw()
def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

View file

@ -1,8 +0,0 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Paytm Settings', {
refresh: function(frm) {
frm.dashboard.set_headline(__("For more information, {0}.", [`<a href='https://erpnext.com/docs/user/manual/en/erpnext_integration/paytm-integration'>${__('Click here')}</a>`]));
}
});

View file

@ -1,89 +0,0 @@
{
"actions": [],
"creation": "2020-04-02 00:11:22.846697",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"merchant_id",
"merchant_key",
"staging",
"column_break_4",
"industry_type_id",
"website"
],
"fields": [
{
"fieldname": "merchant_id",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Merchant ID",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "merchant_key",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Merchant Key",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "staging",
"fieldtype": "Check",
"label": "Staging",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval: !doc.staging",
"fieldname": "website",
"fieldtype": "Data",
"label": "Website",
"mandatory_depends_on": "eval: !doc.staging",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval: !doc.staging",
"fieldname": "industry_type_id",
"fieldtype": "Data",
"label": "Industry Type ID",
"mandatory_depends_on": "eval: !doc.staging",
"show_days": 1,
"show_seconds": 1
}
],
"issingle": 1,
"links": [],
"modified": "2020-06-08 13:36:09.703143",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Paytm Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -1,181 +0,0 @@
# Copyright (c) 2020, Frappe Technologies and contributors
# License: MIT. See LICENSE
import json
from urllib.parse import urlencode
import requests
from paytmchecksum import generateSignature, verifySignature
import frappe
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, cstr, flt, get_request_site_address, get_url
from frappe.utils.password import get_decrypted_password
class PaytmSettings(Document):
supported_currencies = ["INR"]
def validate(self):
create_payment_gateway("Paytm")
call_hook_method("payment_gateway_enabled", gateway="Paytm")
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(
_(
"Please select another payment method. Paytm does not support transactions in currency '{0}'"
).format(currency)
)
def get_payment_url(self, **kwargs):
"""Return payment url with several params"""
# create unique order id by making it equal to the integration request
integration_request = create_request_log(kwargs, service_name="Paytm")
kwargs.update(dict(order_id=integration_request.name))
return get_url(f"./integrations/paytm_checkout?{urlencode(kwargs)}")
def get_paytm_config():
"""Returns paytm config"""
paytm_config = frappe.db.get_singles_dict("Paytm Settings")
paytm_config.update(
dict(merchant_key=get_decrypted_password("Paytm Settings", "Paytm Settings", "merchant_key"))
)
if cint(paytm_config.staging):
paytm_config.update(
dict(
website="WEBSTAGING",
url="https://securegw-stage.paytm.in/order/process",
transaction_status_url="https://securegw-stage.paytm.in/order/status",
industry_type_id="RETAIL",
)
)
else:
paytm_config.update(
dict(
url="https://securegw.paytm.in/order/process",
transaction_status_url="https://securegw.paytm.in/order/status",
)
)
return paytm_config
def get_paytm_params(payment_details, order_id, paytm_config):
# initialize a dictionary
paytm_params = dict()
redirect_uri = (
get_request_site_address(True)
+ "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction"
)
paytm_params.update(
{
"MID": paytm_config.merchant_id,
"WEBSITE": paytm_config.website,
"INDUSTRY_TYPE_ID": paytm_config.industry_type_id,
"CHANNEL_ID": "WEB",
"ORDER_ID": order_id,
"CUST_ID": payment_details["payer_email"],
"EMAIL": payment_details["payer_email"],
"TXN_AMOUNT": cstr(flt(payment_details["amount"], 2)),
"CALLBACK_URL": redirect_uri,
}
)
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
paytm_params.update({"CHECKSUMHASH": checksum})
return paytm_params
@frappe.whitelist(allow_guest=True)
def verify_transaction(**paytm_params):
"""Verify checksum for received data in the callback and then verify the transaction"""
paytm_config = get_paytm_config()
is_valid_checksum = False
paytm_params.pop("cmd", None)
paytm_checksum = paytm_params.pop("CHECKSUMHASH", None)
if paytm_params and paytm_config and paytm_checksum:
# Verify checksum
is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_checksum)
if is_valid_checksum and paytm_params.get("RESPCODE") == "01":
verify_transaction_status(paytm_config, paytm_params["ORDERID"])
else:
frappe.respond_as_web_page(
"Payment Failed",
"Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.",
http_status_code=401,
indicator_color="red",
)
frappe.log_error(
"Order unsuccessful. Failed Response:" + cstr(paytm_params), "Paytm Payment Failed"
)
def verify_transaction_status(paytm_config, order_id):
"""Verify transaction completion after checksum has been verified"""
paytm_params = dict(MID=paytm_config.merchant_id, ORDERID=order_id)
checksum = generateSignature(paytm_params, paytm_config.merchant_key)
paytm_params["CHECKSUMHASH"] = checksum
post_data = json.dumps(paytm_params)
url = paytm_config.transaction_status_url
response = requests.post(url, data=post_data, headers={"Content-type": "application/json"}).json()
finalize_request(order_id, response)
def finalize_request(order_id, transaction_response):
request = frappe.get_doc("Integration Request", order_id)
transaction_data = frappe._dict(json.loads(request.data))
redirect_to = transaction_data.get("redirect_to") or None
redirect_message = transaction_data.get("redirect_message") or None
if transaction_response["STATUS"] == "TXN_SUCCESS":
if transaction_data.reference_doctype and transaction_data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(
transaction_data.reference_doctype, transaction_data.reference_docname
).run_method("on_payment_authorized", "Completed")
request.db_set("status", "Completed")
except Exception:
request.db_set("status", "Failed")
frappe.log_error(frappe.get_traceback())
if custom_redirect_to:
redirect_to = custom_redirect_to
redirect_url = "/integrations/payment-success"
else:
request.db_set("status", "Failed")
redirect_url = "/integrations/payment-failed"
if redirect_to:
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message:
redirect_url += "&" + urlencode({"redirect_message": redirect_message})
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = redirect_url
def get_gateway_controller(doctype, docname):
reference_doc = frappe.get_doc(doctype, docname)
gateway_controller = frappe.db.get_value(
"Payment Gateway", reference_doc.payment_gateway, "gateway_controller"
)
return gateway_controller

View file

@ -1,8 +0,0 @@
# Copyright (c) 2020, Frappe Technologies and Contributors
# License: MIT. See LICENSE
# import frappe
import unittest
class TestPaytmSettings(unittest.TestCase):
pass

View file

@ -1,8 +0,0 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Razorpay Settings', {
refresh: function(frm) {
}
});

View file

@ -1,145 +0,0 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-09-20 03:44:03.799402",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "api_key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "API Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "api_secret",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "API Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Mention transaction completion page URL",
"fieldname": "redirect_to",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:40:31.658270",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Razorpay Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View file

@ -1,530 +0,0 @@
# Copyright (c) 2015, Frappe Technologies and contributors
# License: MIT. See LICENSE
"""
# Integrating RazorPay
### Validate Currency
Example:
from frappe.integrations.utils import get_payment_gateway_controller
controller = get_payment_gateway_controller("Razorpay")
controller().validate_transaction_currency(currency)
### 2. Redirect for payment
Example:
payment_details = {
"amount": 600,
"title": "Payment for bill : 111",
"description": "payment via cart",
"reference_doctype": "Payment Request",
"reference_docname": "PR0001",
"payer_email": "NuranVerkleij@example.com",
"payer_name": "Nuran Verkleij",
"order_id": "111",
"currency": "INR",
"payment_gateway": "Razorpay",
"subscription_details": {
"plan_id": "plan_12313", # if Required
"start_date": "2018-08-30",
"billing_period": "Month" #(Day, Week, Month, Year),
"billing_frequency": 1,
"customer_notify": 1,
"upfront_amount": 1000
}
}
# Redirect the user to this url
url = controller().get_payment_url(**payment_details)
### 3. On Completion of Payment
Write a method for `on_payment_authorized` in the reference doctype
Example:
def on_payment_authorized(payment_status):
# this method will be called when payment is complete
##### Notes:
payment_status - payment gateway will put payment status on callback.
For razorpay payment status is Authorized
"""
import hashlib
import hmac
import json
from urllib.parse import urlencode
import razorpay
import frappe
from frappe import _
from frappe.integrations.utils import (
create_payment_gateway,
create_request_log,
make_get_request,
make_post_request,
)
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, get_timestamp, get_url
class RazorpaySettings(Document):
supported_currencies = ["INR"]
def init_client(self):
if self.api_key:
secret = self.get_password(fieldname="api_secret", raise_exception=False)
self.client = razorpay.Client(auth=(self.api_key, secret))
def validate(self):
create_payment_gateway("Razorpay")
call_hook_method("payment_gateway_enabled", gateway="Razorpay")
if not self.flags.ignore_mandatory:
self.validate_razorpay_credentails()
def validate_razorpay_credentails(self):
if self.api_key and self.api_secret:
try:
make_get_request(
url="https://api.razorpay.com/v1/payments",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
)
except Exception:
frappe.throw(_("Seems API Key or API Secret is wrong !!!"))
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(
_(
"Please select another payment method. Razorpay does not support transactions in currency '{0}'"
).format(currency)
)
def setup_addon(self, settings, **kwargs):
"""
Addon template:
{
"item": {
"name": row.upgrade_type,
"amount": row.amount,
"currency": currency,
"description": "add-on description"
},
"quantity": 1 (The total amount is calculated as item.amount * quantity)
}
"""
url = "https://api.razorpay.com/v1/subscriptions/{}/addons".format(kwargs.get("subscription_id"))
try:
if not frappe.conf.converted_rupee_to_paisa:
convert_rupee_to_paisa(**kwargs)
for addon in kwargs.get("addons"):
resp = make_post_request(
url,
auth=(settings.api_key, settings.api_secret),
data=json.dumps(addon),
headers={"content-type": "application/json"},
)
if not resp.get("id"):
frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
except Exception:
frappe.log_error()
# failed
pass
def setup_subscription(self, settings, **kwargs):
start_date = (
get_timestamp(kwargs.get("subscription_details").get("start_date"))
if kwargs.get("subscription_details").get("start_date")
else None
)
subscription_details = {
"plan_id": kwargs.get("subscription_details").get("plan_id"),
"total_count": kwargs.get("subscription_details").get("billing_frequency"),
"customer_notify": kwargs.get("subscription_details").get("customer_notify"),
}
if start_date:
subscription_details["start_at"] = cint(start_date)
if kwargs.get("addons"):
convert_rupee_to_paisa(**kwargs)
subscription_details.update({"addons": kwargs.get("addons")})
try:
resp = make_post_request(
"https://api.razorpay.com/v1/subscriptions",
auth=(settings.api_key, settings.api_secret),
data=json.dumps(subscription_details),
headers={"content-type": "application/json"},
)
if resp.get("status") == "created":
kwargs["subscription_id"] = resp.get("id")
frappe.flags.status = "created"
return kwargs
else:
frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
except Exception:
frappe.log_error()
def prepare_subscription_details(self, settings, **kwargs):
if not kwargs.get("subscription_id"):
kwargs = self.setup_subscription(settings, **kwargs)
if frappe.flags.status != "created":
kwargs["subscription_id"] = None
return kwargs
def get_payment_url(self, **kwargs):
integration_request = create_request_log(kwargs, service_name="Razorpay")
return get_url(f"./integrations/razorpay_checkout?token={integration_request.name}")
def create_order(self, **kwargs):
# Creating Orders https://razorpay.com/docs/api/orders/
# convert rupees to paisa
kwargs["amount"] *= 100
# Create integration log
integration_request = create_request_log(kwargs, service_name="Razorpay")
# Setup payment options
payment_options = {
"amount": kwargs.get("amount"),
"currency": kwargs.get("currency", "INR"),
"receipt": kwargs.get("receipt"),
"payment_capture": kwargs.get("payment_capture"),
}
if self.api_key and self.api_secret:
try:
order = make_post_request(
"https://api.razorpay.com/v1/orders",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
data=payment_options,
)
order["integration_request"] = integration_request.name
return order # Order returned to be consumed by razorpay.js
except Exception:
frappe.log(frappe.get_traceback())
frappe.throw(_("Could not create razorpay order"))
def create_request(self, data):
self.data = frappe._dict(data)
try:
self.integration_request = frappe.get_doc("Integration Request", self.data.token)
self.integration_request.update_status(self.data, "Queued")
return self.authorize_payment()
except Exception:
frappe.log_error(frappe.get_traceback())
return {
"redirect_to": frappe.redirect_to_message(
_("Server Error"),
_(
"Seems issue with server's razorpay config. Don't worry, in case of failure amount will get refunded to your account."
),
),
"status": 401,
}
def authorize_payment(self):
"""
An authorization is performed when users payment details are successfully authenticated by the bank.
The money is deducted from the customers account, but will not be transferred to the merchants account
until it is explicitly captured by merchant.
"""
data = json.loads(self.integration_request.data)
settings = self.get_settings(data)
try:
resp = make_get_request(
f"https://api.razorpay.com/v1/payments/{self.data.razorpay_payment_id}",
auth=(settings.api_key, settings.api_secret),
)
if resp.get("status") == "authorized":
self.integration_request.update_status(data, "Authorized")
self.flags.status_changed_to = "Authorized"
if resp.get("status") == "captured":
self.integration_request.update_status(data, "Completed")
self.flags.status_changed_to = "Completed"
elif data.get("subscription_id"):
if resp.get("status") == "refunded":
# if subscription start date is in future then
# razorpay refunds the amount after authorizing the card details
# thus changing status to Verified
self.integration_request.update_status(data, "Completed")
self.flags.status_changed_to = "Verified"
else:
frappe.log_error(message=str(resp), title="Razorpay Payment not authorized")
except Exception:
frappe.log_error()
status = frappe.flags.integration_request.status_code
redirect_to = data.get("redirect_to") or None
redirect_message = data.get("redirect_message") or None
if self.flags.status_changed_to in ("Authorized", "Verified", "Completed"):
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
try:
frappe.flags.data = data
custom_redirect_to = frappe.get_doc(
self.data.reference_doctype, self.data.reference_docname
).run_method("on_payment_authorized", self.flags.status_changed_to)
except Exception:
frappe.log_error(frappe.get_traceback())
if custom_redirect_to:
redirect_to = custom_redirect_to
redirect_url = "payment-success?doctype={}&docname={}".format(
self.data.reference_doctype, self.data.reference_docname
)
else:
redirect_url = "payment-failed"
if redirect_to:
redirect_url += "&" + urlencode({"redirect_to": redirect_to})
if redirect_message:
redirect_url += "&" + urlencode({"redirect_message": redirect_message})
return {"redirect_to": redirect_url, "status": status}
def get_settings(self, data):
settings = frappe._dict(
{
"api_key": self.api_key,
"api_secret": self.get_password(fieldname="api_secret", raise_exception=False),
}
)
if cint(data.get("notes", {}).get("use_sandbox")) or data.get("use_sandbox"):
settings.update(
{
"api_key": frappe.conf.sandbox_api_key,
"api_secret": frappe.conf.sandbox_api_secret,
}
)
return settings
def cancel_subscription(self, subscription_id):
settings = self.get_settings({})
try:
resp = make_post_request(
f"https://api.razorpay.com/v1/subscriptions/{subscription_id}/cancel",
auth=(settings.api_key, settings.api_secret),
)
except Exception:
frappe.log_error(frappe.get_traceback())
def verify_signature(self, body, signature, key):
key = bytes(key, "utf-8")
body = bytes(body, "utf-8")
dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256)
generated_signature = dig.hexdigest()
result = hmac.compare_digest(generated_signature, signature)
if not result:
frappe.throw(_("Razorpay Signature Verification Failed"), exc=frappe.PermissionError)
return result
def capture_payment(is_sandbox=False, sanbox_response=None):
"""
Verifies the purchase as complete by the merchant.
After capture, the amount is transferred to the merchant within T+3 days
where T is the day on which payment is captured.
Note: Attempting to capture a payment whose status is not authorized will produce an error.
"""
controller = frappe.get_doc("Razorpay Settings")
for doc in frappe.get_all(
"Integration Request",
filters={"status": "Authorized", "integration_request_service": "Razorpay"},
fields=["name", "data"],
):
try:
if is_sandbox:
resp = sanbox_response
else:
data = json.loads(doc.data)
settings = controller.get_settings(data)
resp = make_get_request(
"https://api.razorpay.com/v1/payments/{}".format(data.get("razorpay_payment_id")),
auth=(settings.api_key, settings.api_secret),
data={"amount": data.get("amount")},
)
if resp.get("status") == "authorized":
resp = make_post_request(
"https://api.razorpay.com/v1/payments/{}/capture".format(data.get("razorpay_payment_id")),
auth=(settings.api_key, settings.api_secret),
data={"amount": data.get("amount")},
)
if resp.get("status") == "captured":
frappe.db.set_value("Integration Request", doc.name, "status", "Completed")
except Exception:
doc = frappe.get_doc("Integration Request", doc.name)
doc.status = "Failed"
doc.error = frappe.get_traceback()
doc.save()
frappe.log_error(doc.error, f"{doc.name} Failed")
@frappe.whitelist(allow_guest=True)
def get_api_key():
controller = frappe.get_doc("Razorpay Settings")
return controller.api_key
@frappe.whitelist(allow_guest=True)
def get_order(doctype, docname):
# Order returned to be consumed by razorpay.js
doc = frappe.get_doc(doctype, docname)
try:
# Do not use run_method here as it fails silently
return doc.get_razorpay_order()
except AttributeError:
frappe.log_error(frappe.get_traceback(), _("Controller method get_razorpay_order missing"))
frappe.throw(_("Could not create Razorpay order. Please contact Administrator"))
@frappe.whitelist(allow_guest=True)
def order_payment_success(integration_request, params):
"""Called by razorpay.js on order payment success, the params
contains razorpay_payment_id, razorpay_order_id, razorpay_signature
that is updated in the data field of integration request
Args:
integration_request (string): Name for integration request doc
params (string): Params to be updated for integration request.
"""
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
# Update integration request
integration.update_status(params, integration.status)
integration.reload()
data = json.loads(integration.data)
controller = frappe.get_doc("Razorpay Settings")
# Update payment and integration data for payment controller object
controller.integration_request = integration
controller.data = frappe._dict(data)
# Authorize payment
controller.authorize_payment()
@frappe.whitelist(allow_guest=True)
def order_payment_failure(integration_request, params):
"""Called by razorpay.js on failure
Args:
integration_request (TYPE): Description
params (TYPE): error data to be updated
"""
frappe.log_error(params, "Razorpay Payment Failure")
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
integration.update_status(params, integration.status)
def convert_rupee_to_paisa(**kwargs):
for addon in kwargs.get("addons"):
addon["item"]["amount"] *= 100
frappe.conf.converted_rupee_to_paisa = True
@frappe.whitelist(allow_guest=True)
def razorpay_subscription_callback():
try:
data = frappe.local.form_dict
validate_payment_callback(data)
data.update({"payment_gateway": "Razorpay"})
doc = frappe.get_doc(
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"request_description": "Subscription Notification",
"is_remote_request": 1,
"status": "Queued",
}
).insert(ignore_permissions=True)
frappe.db.commit()
frappe.enqueue(
method="frappe.integrations.doctype.razorpay_settings.razorpay_settings.handle_subscription_notification",
queue="long",
timeout=600,
is_async=True,
**{"doctype": "Integration Request", "docname": doc.name},
)
except frappe.InvalidStatusError:
pass
except Exception as e:
frappe.log(frappe.log_error(title=e))
def validate_payment_callback(data):
def _throw():
frappe.throw(_("Invalid Subscription"), exc=frappe.InvalidStatusError)
subscription_id = data.get("payload").get("subscription").get("entity").get("id")
if not (subscription_id):
_throw()
controller = frappe.get_doc("Razorpay Settings")
settings = controller.get_settings(data)
resp = make_get_request(
f"https://api.razorpay.com/v1/subscriptions/{subscription_id}",
auth=(settings.api_key, settings.api_secret),
)
if resp.get("status") != "active":
_throw()
def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

View file

@ -1,8 +0,0 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Stripe Settings', {
refresh: function(frm) {
}
});

View file

@ -1,315 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:gateway_name",
"beta": 0,
"creation": "2017-03-09 17:18:29.458397",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Gateway Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "publishable_key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Publishable Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "secret_key",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Secret Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_img",
"fieldtype": "Attach Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Header Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "redirect_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-23 13:32:14.429916",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Stripe Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View file

@ -1,280 +0,0 @@
# Copyright (c) 2017, Frappe Technologies and contributors
# License: MIT. See LICENSE
from urllib.parse import urlencode
import frappe
from frappe import _
from frappe.integrations.utils import (
create_payment_gateway,
create_request_log,
make_get_request,
make_post_request,
)
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
class StripeSettings(Document):
supported_currencies = [
"AED",
"ALL",
"ANG",
"ARS",
"AUD",
"AWG",
"BBD",
"BDT",
"BIF",
"BMD",
"BND",
"BOB",
"BRL",
"BSD",
"BWP",
"BZD",
"CAD",
"CHF",
"CLP",
"CNY",
"COP",
"CRC",
"CVE",
"CZK",
"DJF",
"DKK",
"DOP",
"DZD",
"EGP",
"ETB",
"EUR",
"FJD",
"FKP",
"GBP",
"GIP",
"GMD",
"GNF",
"GTQ",
"GYD",
"HKD",
"HNL",
"HRK",
"HTG",
"HUF",
"IDR",
"ILS",
"INR",
"ISK",
"JMD",
"JPY",
"KES",
"KHR",
"KMF",
"KRW",
"KYD",
"KZT",
"LAK",
"LBP",
"LKR",
"LRD",
"MAD",
"MDL",
"MNT",
"MOP",
"MRO",
"MUR",
"MVR",
"MWK",
"MXN",
"MYR",
"NAD",
"NGN",
"NIO",
"NOK",
"NPR",
"NZD",
"PAB",
"PEN",
"PGK",
"PHP",
"PKR",
"PLN",
"PYG",
"QAR",
"RUB",
"SAR",
"SBD",
"SCR",
"SEK",
"SGD",
"SHP",
"SLL",
"SOS",
"STD",
"SVC",
"SZL",
"THB",
"TOP",
"TTD",
"TWD",
"TZS",
"UAH",
"UGX",
"USD",
"UYU",
"UZS",
"VND",
"VUV",
"WST",
"XAF",
"XOF",
"XPF",
"YER",
"ZAR",
]
currency_wise_minimum_charge_amount = {
"JPY": 50,
"MXN": 10,
"DKK": 2.50,
"HKD": 4.00,
"NOK": 3.00,
"SEK": 3.00,
"USD": 0.50,
"AUD": 0.50,
"BRL": 0.50,
"CAD": 0.50,
"CHF": 0.50,
"EUR": 0.50,
"GBP": 0.30,
"NZD": 0.50,
"SGD": 0.50,
}
def on_update(self):
create_payment_gateway(
"Stripe-" + self.gateway_name, settings="Stripe Settings", controller=self.gateway_name
)
call_hook_method("payment_gateway_enabled", gateway="Stripe-" + self.gateway_name)
if not self.flags.ignore_mandatory:
self.validate_stripe_credentails()
def validate_stripe_credentails(self):
if self.publishable_key and self.secret_key:
header = {
"Authorization": "Bearer {}".format(
self.get_password(fieldname="secret_key", raise_exception=False)
)
}
try:
make_get_request(url="https://api.stripe.com/v1/charges", headers=header)
except Exception:
frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!"))
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(
_(
"Please select another payment method. Stripe does not support transactions in currency '{0}'"
).format(currency)
)
def validate_minimum_transaction_amount(self, currency, amount):
if currency in self.currency_wise_minimum_charge_amount:
if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0):
frappe.throw(
_("For currency {0}, the minimum transaction amount should be {1}").format(
currency, self.currency_wise_minimum_charge_amount.get(currency, 0.0)
)
)
def get_payment_url(self, **kwargs):
return get_url(f"./integrations/stripe_checkout?{urlencode(kwargs)}")
def create_request(self, data):
import stripe
self.data = frappe._dict(data)
stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False)
stripe.default_http_client = stripe.http_client.RequestsClient()
try:
self.integration_request = create_request_log(self.data, service_name="Stripe")
return self.create_charge_on_stripe()
except Exception:
frappe.log_error(frappe.get_traceback())
return {
"redirect_to": frappe.redirect_to_message(
_("Server Error"),
_(
"It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account."
),
),
"status": 401,
}
def create_charge_on_stripe(self):
import stripe
try:
charge = stripe.Charge.create(
amount=cint(flt(self.data.amount) * 100),
currency=self.data.currency,
source=self.data.stripe_token_id,
description=self.data.description,
receipt_email=self.data.payer_email,
)
if charge.captured == True:
self.integration_request.db_set("status", "Completed", update_modified=False)
self.flags.status_changed_to = "Completed"
else:
frappe.log_error(charge.failure_message, "Stripe Payment not completed")
except Exception:
frappe.log_error(frappe.get_traceback())
return self.finalize_request()
def finalize_request(self):
redirect_to = self.data.get("redirect_to") or None
redirect_message = self.data.get("redirect_message") or None
status = self.integration_request.status
if self.flags.status_changed_to == "Completed":
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(
self.data.reference_doctype, self.data.reference_docname
).run_method("on_payment_authorized", self.flags.status_changed_to)
except Exception:
frappe.log_error(frappe.get_traceback())
if custom_redirect_to:
redirect_to = custom_redirect_to
redirect_url = "payment-success"
if self.redirect_url:
redirect_url = self.redirect_url
redirect_to = None
else:
redirect_url = "payment-failed"
if redirect_to:
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message:
redirect_url += "&" + urlencode({"redirect_message": redirect_message})
return {"redirect_to": redirect_url, "status": status}
def get_gateway_controller(doctype, docname):
reference_doc = frappe.get_doc(doctype, docname)
gateway_controller = frappe.db.get_value(
"Payment Gateway", reference_doc.payment_gateway, "gateway_controller"
)
return gateway_controller

View file

@ -1,7 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and Contributors
# License: MIT. See LICENSE
import unittest
class TestStripeSettings(unittest.TestCase):
pass

View file

@ -96,54 +96,6 @@ def get_json(obj):
return obj if isinstance(obj, str) else frappe.as_json(obj, indent=1)
def get_payment_gateway_controller(payment_gateway):
"""Return payment gateway controller"""
gateway = frappe.get_doc("Payment Gateway", payment_gateway)
if gateway.gateway_controller is None:
try:
return frappe.get_doc(f"{payment_gateway} Settings")
except Exception:
frappe.throw(_("{0} Settings not found").format(payment_gateway))
else:
try:
return frappe.get_doc(gateway.gateway_settings, gateway.gateway_controller)
except Exception:
frappe.throw(_("{0} Settings not found").format(payment_gateway))
@frappe.whitelist(allow_guest=True, xss_safe=True)
def get_checkout_url(**kwargs):
try:
if kwargs.get("payment_gateway"):
doc = frappe.get_doc("{} Settings".format(kwargs.get("payment_gateway")))
return doc.get_payment_url(**kwargs)
else:
raise Exception
except Exception:
frappe.respond_as_web_page(
_("Something went wrong"),
_(
"Looks like something is wrong with this site's payment gateway configuration. No payment has been made."
),
indicator_color="red",
http_status_code=frappe.ValidationError.http_status_code,
)
def create_payment_gateway(gateway, settings=None, controller=None):
# NOTE: we don't translate Payment Gateway name because it is an internal doctype
if not frappe.db.exists("Payment Gateway", gateway):
payment_gateway = frappe.get_doc(
{
"doctype": "Payment Gateway",
"gateway": gateway,
"gateway_settings": settings,
"gateway_controller": controller,
}
)
payment_gateway.insert(ignore_permissions=True)
def json_handler(obj):
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
return str(obj)

View file

@ -1,6 +1,6 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:16:18.714190",
"docstatus": 0,
"doctype": "Workspace",
@ -106,11 +106,52 @@
{
"hidden": 0,
"is_query_report": 0,
"label": "Authentication",
"label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Webhook",
"link_count": 0,
"link_to": "Webhook",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Slack Webhook URL",
"link_count": 0,
"link_to": "Slack Webhook URL",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "SMS Settings",
"link_count": 0,
"link_to": "SMS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Authentication",
"link_count": 4,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@ -154,119 +195,16 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Braintree Settings",
"link_count": 0,
"link_to": "Braintree Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "PayPal Settings",
"link_count": 0,
"link_to": "PayPal Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Razorpay Settings",
"link_count": 0,
"link_to": "Razorpay Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Stripe Settings",
"link_count": 0,
"link_to": "Stripe Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Paytm Settings",
"link_count": 0,
"link_to": "Paytm Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Webhook",
"link_count": 0,
"link_to": "Webhook",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Slack Webhook URL",
"link_count": 0,
"link_to": "Slack Webhook URL",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "SMS Settings",
"link_count": 0,
"link_to": "SMS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-01-13 17:39:01.292154",
"modified": "2022-07-23 18:00:28.805405",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 15.0,

View file

@ -66,7 +66,6 @@ execute:frappe.delete_doc_if_exists('Page', 'user-permissions')
frappe.patches.v10_0.set_no_copy_to_workflow_state
frappe.patches.v10_0.increase_single_table_column_length
frappe.patches.v11_0.create_contact_for_user
frappe.patches.v11_0.sync_stripe_settings_before_migrate
frappe.patches.v11_0.update_list_user_settings
frappe.patches.v11_0.rename_workflow_action_to_workflow_action_master #13-06-2018
frappe.patches.v11_0.rename_email_alert_to_notification #13-06-2018
@ -196,6 +195,7 @@ frappe.patches.v14_0.clear_long_pending_stale_logs
frappe.patches.v14_0.log_settings_migration
frappe.patches.v14_0.setup_likes_from_feedback
frappe.patches.v14_0.update_webforms
frappe.patches.v14_0.delete_payment_gateways
[post_model_sync]
frappe.patches.v14_0.drop_data_import_legacy

View file

@ -1,25 +0,0 @@
import frappe
from frappe.utils.password import get_decrypted_password
def execute():
publishable_key = frappe.db.sql(
"select value from tabSingles where doctype='Stripe Settings' and field='publishable_key'"
)
if publishable_key:
secret_key = get_decrypted_password(
"Stripe Settings", "Stripe Settings", fieldname="secret_key", raise_exception=False
)
if secret_key:
frappe.reload_doc("integrations", "doctype", "stripe_settings")
frappe.db.commit()
settings = frappe.new_doc("Stripe Settings")
settings.gateway_name = (
frappe.db.get_value("Global Defaults", None, "default_company") or "Stripe Settings"
)
settings.publishable_key = publishable_key
settings.secret_key = secret_key
settings.save(ignore_permissions=True)
frappe.db.delete("Singles", {"doctype": "Stripe Settings"})

View file

@ -0,0 +1,16 @@
import frappe
def execute():
if "payments" in frappe.get_installed_apps():
return
for doctype in (
"Payment Gateway",
"Razorpay Settings",
"Braintree Settings",
"PayPal Settings",
"Paytm Settings",
"Stripe Settings",
):
frappe.delete_doc_if_exists("DocType", doctype, force=True)

View file

@ -61,9 +61,12 @@ class LetterHead(Document):
# To preserve the aspect ratio of the image, apply constraints only on
# the greater dimension and allow the other to scale accordingly
dimension = "width" if width > height else "height"
dimension = "width" if self.get(width) > self.get(height) else "height"
dimension_value = self.get(f"{dimension_prefix}{dimension}")
if not dimension_value:
dimension_value = ""
self.set(
html_field,
f"""<div style="text-align: {self.get(align, "").lower()};">

View file

@ -1 +0,0 @@
import "./integrations/razorpay";

View file

@ -195,7 +195,9 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
let values = await frappe.xcall(method, {
search_term
});
renderList(values, search_term);
let sorted_values = me.prioritize_involved_users_in_mention(values);
renderList(sorted_values, search_term);
}, 300),
renderItem(item) {
let value = item.value;
@ -204,6 +206,16 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
};
}
prioritize_involved_users_in_mention(values) {
const involved_users = this.frm?.get_involved_users() // input on form
|| cur_frm?.get_involved_users() // comment box / dialog on active form
|| [];
return values
.filter(val => involved_users.includes(val.id))
.concat(values.filter(val => !involved_users.includes(val.id)));
}
get_toolbar_options() {
return [
[{ header: [1, 2, 3, false] }],

View file

@ -1873,6 +1873,29 @@ frappe.ui.form.Form = class FrappeForm {
get_active_tab() {
return this.active_tab_map && this.active_tab_map[this.docname];
}
get_involved_users() {
let user_fields = this.meta.fields
.filter(d => d.fieldtype === 'Link' && d.options === 'User')
.map(d => d.fieldname);
user_fields = [...user_fields, "owner", "modified_by"];
let involved_users = user_fields.map(field => this.doc[field]);
const docinfo = this.get_docinfo();
involved_users = involved_users.concat(
docinfo.communications.map(d => d.sender && d.delivery_status === 'sent'),
docinfo.comments.map(d => d.owner),
docinfo.versions.map(d => d.owner),
docinfo.assignments.map(d => d.owner)
);
return involved_users
.uniqBy(u => u)
.filter(user => !['Administrator', frappe.session.user].includes(user))
.filter(Boolean);
}
};
frappe.validated = 0;

View file

@ -298,15 +298,14 @@ export default class Grid {
show_search: true
});
Object.keys(this.filter).length !== 0 &&
this.update_search_columns();
this.filter_applied && this.update_search_columns();
}
update_search_columns() {
for (const field in this.filter) {
if (this.filter[field] && !this.header_search.search_columns[field]) {
delete this.filter[field];
this.data = this.get_data(Object.keys(this.filter).length !== 0);
this.data = this.get_data(this.filter_applied);
break;
}
@ -323,7 +322,8 @@ export default class Grid {
refresh() {
if (this.frm && this.frm.setting_dependency) return;
this.data = this.get_data(Object.keys(this.filter).length !== 0);
this.filter_applied = Object.keys(this.filter).length !== 0;
this.data = this.get_data(this.filter_applied);
!this.wrapper && this.make();
let $rows = $(this.parent).find('.rows');

View file

@ -14,7 +14,7 @@ export default class GridRow {
make() {
var me = this;
this.wrapper = $('<div class="grid-row"></div>').appendTo(this.parent).data("grid_row", this);
this.wrapper = $('<div class="grid-row"></div>');
this.row = $('<div class="data-row row"></div>').appendTo(this.wrapper)
.on("click", function(e) {
if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) {
@ -33,9 +33,9 @@ export default class GridRow {
} else {
this.render_row();
}
if(this.doc) {
this.set_data();
}
this.set_data();
this.wrapper.appendTo(this.parent);
}
set_docfields(update=false) {
@ -55,8 +55,9 @@ export default class GridRow {
set_data() {
this.wrapper.data({
"doc": this.doc
})
"grid_row": this,
"doc": this.doc || "",
});
}
set_row_index() {
if(this.doc) {
@ -750,6 +751,7 @@ export default class GridRow {
.option('disabled', Object.keys(this.grid.filter).length !== 0);
this.grid.prevent_build = true;
this.grid.grid_pagination.go_to_page(1);
this.grid.refresh();
this.grid.prevent_build = false;
}, 500));

View file

@ -156,7 +156,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
if (error_fields.length) {
let meta = frappe.get_meta(doc.doctype);
if (meta.istable) {
const table_label = __(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold();
const table_field = frappe.meta.docfield_map[doc.parenttype][doc.parentfield];
const table_label = __(
table_field.label || frappe.unscrub(table_field.fieldname)
).bold();
var message = __('Mandatory fields required in table {0}, Row {1}', [table_label, doc.idx]);
} else {
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]);

View file

@ -38,30 +38,9 @@ frappe.ui.form.Review = class Review {
review_button.click(() => this.show_review_dialog());
}
}
get_involved_users() {
const user_fields = this.frm.meta.fields
.filter(d => d.fieldtype === 'Link' && d.options === 'User')
.map(d => d.fieldname);
user_fields.push('owner');
let involved_users = user_fields.map(field => this.frm.doc[field]);
const docinfo = this.frm.get_docinfo();
involved_users = involved_users.concat(
docinfo.communications.map(d => d.sender && d.delivery_status === 'sent'),
docinfo.comments.map(d => d.owner),
docinfo.versions.map(d => d.owner),
docinfo.assignments.map(d => d.owner)
);
return involved_users
.uniqBy(u => u)
.filter(user => !['Administrator', frappe.session.user].includes(user))
.filter(Boolean);
}
show_review_dialog() {
const user_options = this.get_involved_users();
const user_options = this.frm.get_involved_users();
const review_dialog = new frappe.ui.Dialog({
'title': __('Add Review'),
'fields': [{

View file

@ -69,6 +69,14 @@ frappe.views.BaseList = class BaseList {
];
}
get_list_view_settings() {
return frappe
.call("frappe.desk.listview.get_list_settings", {
doctype: this.doctype,
})
.then((doc) => (this.list_view_settings = doc.message || {}));
}
setup_fields() {
this.set_fields();
this.build_fields();

View file

@ -107,14 +107,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return this.get_list_view_settings();
}
get_list_view_settings() {
return frappe
.call("frappe.desk.listview.get_list_settings", {
doctype: this.doctype,
})
.then((doc) => (this.list_view_settings = doc.message || {}));
}
on_sort_change(sort_by, sort_order) {
this.sort_by = sort_by;
this.sort_order = sort_order;

View file

@ -31,7 +31,7 @@ frappe.ui.FilterGroup = class {
trigger: 'manual',
container: 'body',
placement: 'bottom',
offset: '-100px 0'
offset: '-100px, 0'
});
}

View file

@ -42,7 +42,7 @@ frappe.ui.GroupBy = class {
trigger: 'manual',
container: 'body',
placement: 'bottom',
offset: '-100px 0',
offset: '-100px, 0',
});
}

View file

@ -189,19 +189,22 @@ class NotificationsView extends BaseNotificationsView {
);
this.setup_notification_listeners();
this.get_notifications_list(this.max_length).then(list => {
this.dropdown_items = list;
this.get_notifications_list(this.max_length).then(r => {
if (!r.message) return;
this.dropdown_items = r.message.notification_logs;
frappe.update_user_info(r.message.user_info);
this.render_notifications_dropdown();
if (this.settings.seen == 0) {
if (this.settings.seen == 0 && this.dropdown_items.length > 0) {
this.toggle_notification_icon(false);
}
});
}
update_dropdown() {
this.get_notifications_list(1).then(r => {
let new_item = r[0];
if (!r.message) return;
let new_item = r.message.notification_logs[0];
frappe.update_user_info(r.message.user_info);
this.dropdown_items.unshift(new_item);
if (this.dropdown_items.length > this.max_length) {
this.container
@ -322,11 +325,10 @@ class NotificationsView extends BaseNotificationsView {
}
get_notifications_list(limit) {
return frappe.db.get_list('Notification Log', {
fields: ['*'],
limit: limit,
order_by: 'creation desc'
});
return frappe.call(
'frappe.desk.doctype.notification_log.notification_log.get_notification_logs',
{ limit: limit }
);
}
get_item_link(notification_doc) {

View file

@ -134,7 +134,7 @@ frappe.ui.Tree = class {
}
reload_node(node) {
this.load_children(node);
return this.load_children(node);
}
toggle() {
@ -150,21 +150,20 @@ frappe.ui.Tree = class {
}
load_children(node, deep=false) {
let lab = node.label, value = node.data.value, is_root = node.is_root;
const value = node.data.value,
is_root = node.is_root;
if(!deep) {
frappe.run_serially([
return deep
? frappe.run_serially([
() => this.get_all_nodes(value, is_root, node.label),
data_list => this.render_children_of_all_nodes(data_list),
() => this.set_selected_node(node),
])
: frappe.run_serially([
() => this.get_nodes(value, is_root),
(data_set) => this.render_node_children(node, data_set),
() => this.set_selected_node(node)
data_set => this.render_node_children(node, data_set),
() => this.set_selected_node(node),
]);
} else {
frappe.run_serially([
() => this.get_all_nodes(value, is_root, lab),
(data_list) => this.render_children_of_all_nodes(data_list),
() => this.set_selected_node(node)
]);
}
}
render_children_of_all_nodes(data_list) {

View file

@ -14,6 +14,16 @@ frappe.user_info = function(uid) {
return user_info;
};
frappe.update_user_info = function(user_info) {
for (let user in user_info) {
if (frappe.boot.user_info[user]) {
Object.assign(frappe.boot.user_info[user], user_info[user]);
} else {
frappe.boot.user_info[user] = user_info[user];
}
}
};
frappe.provide('frappe.user');
$.extend(frappe.user, {

View file

@ -43,6 +43,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.add_totals_row = this.view_user_settings.add_totals_row || 0;
this.chart_args = this.view_user_settings.chart_args;
}
return this.get_list_view_settings();
}
setup_view() {
@ -53,6 +54,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
setup_events() {
if (this.list_view_settings?.disable_auto_refresh) {
return;
}
frappe.realtime.on("list_update", (data) => this.on_update(data));
}
@ -216,6 +220,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
render_count() {
if (this.list_view_settings?.disable_count) {
return;
}
let $list_count = this.$paging_area.find('.list-count');
if (!$list_count.length) {
$list_count = $('<span>')
@ -1540,6 +1547,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
});
}
if (frappe.user.has_role("System Manager")) {
if (this.get_view_settings) {
items.push(this.get_view_settings());
}
}
return items.map(i => Object.assign(i, { standard: true }));
}

View file

@ -280,6 +280,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
if (!doc_values) return;
if (window.saving) return;
// TODO: remove this (used for payments app)
let for_payment = Boolean(this.accept_payment && !this.doc.paid);
Object.assign(this.doc, doc_values);
@ -342,6 +343,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
handle_success(data) {
// TODO: remove this (used for payments app)
if (this.accept_payment && !this.doc.paid) {
window.location.href = data;
}

View file

@ -1,148 +0,0 @@
/* HOW-TO
Razorpay Payment
1. Include checkout script in your code
{{ include_script('checkout.bundle.js) }}
2. Create the Order controller in your backend
def get_razorpay_order(self):
controller = get_payment_gateway_controller("Razorpay")
payment_details = {
"amount": 300,
...
"reference_doctype": "Conference Participant",
"reference_docname": self.name,
...
"receipt": self.name
}
return controller.create_order(**payment_details)
3. Inititate the payment in client using checkout API
function make_payment(ticket) {
var options = {
"name": "<CHECKOUT MODAL TITLE>",
"description": "<CHECKOUT MODAL DESCRIPTION>",
"image": "<CHECKOUT MODAL LOGO>",
"prefill": {
"name": "<CUSTOMER NAME>",
"email": "<CUSTOMER EMAIL>",
"contact": "<CUSTOMER PHONE>"
},
"theme": {
"color": "<MODAL COLOR>"
},
"doctype": "<REFERENCE DOCTYPE>",
"docname": "<REFERENCE DOCNAME"
};
razorpay = new frappe.checkout.razorpay(options)
razorpay.on_open = () => {
<SCRIPT TO RUN WHEN MODAL OPENS>
}
razorpay.on_success = () => {
<SCRIPT TO RUN ON PAYMENT SUCCESS>
}
razorpay.on_fail = () => {
<SCRIPT TO RUN ON PAYMENT FAILURE>
}
razorpay.init() // Creates the order and opens the modal
}
*/
frappe.provide("frappe.checkout");
frappe.require('https://checkout.razorpay.com/v1/checkout.js').then(() => {
frappe.checkout.razorpay = class RazorpayCheckout {
constructor(opts) {
Object.assign(this, opts);
}
init() {
frappe.run_serially([
() => this.get_key(),
() => this.make_order(),
() => this.prepare_options(),
() => this.setup_handler(),
() => this.show()
]);
}
show() {
this.razorpay = new Razorpay(this.options);
this.razorpay.once('ready', (response) => {
this.on_open && this.on_open(response);
})
this.razorpay.open();
}
get_key() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_api_key").then(res => {
this.key = res.message;
resolve(true);
})
});
}
make_order() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_order", {
doctype: this.doctype,
docname: this.docname
}).then(res => {
this.order = res.message;
resolve(true);
})
});
}
order_success(response) {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_success", {
integration_request: this.order.integration_request,
params: {
razorpay_payment_id: response.razorpay_payment_id,
razorpay_order_id: response.razorpay_order_id,
razorpay_signature: response.razorpay_signature
}
})
}
order_fail(response) {
frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_failure", {
integration_request: this.order.integration_request,
params: response
})
}
prepare_options() {
this.options = {
"key": this.key,
"amount": this.order.amount_due,
"currency": this.order.currency,
"name": this.name,
"description": this.description,
"image": this.image,
"order_id": this.order.id,
"prefill": this.prefill,
"theme": this.theme,
"modal": this.modal
};
}
setup_handler() {
this.options.handler = (response) => {
if (response.error) {
this.order_fail(response);
this.on_fail && this.on_fail(response);
}
else if (response.razorpay_payment_id) {
this.order_success(response);
this.on_success && this.on_success(response);
}
}
}
}
});

View file

@ -62,6 +62,10 @@
border-bottom: none;
}
.form-section.card-section.hide-border {
border-bottom: none;
}
.form-dashboard-section {
.section-body:first-child {
margin-top: 0;

View file

@ -1,55 +0,0 @@
$(document).ready(function() {
var button = document.querySelector('#submit-button');
var form = document.querySelector('#payment-form');
var data = {{ frappe.form_dict | json }};
var doctype = "{{ reference_doctype }}"
var docname = "{{ reference_docname }}"
braintree.dropin.create({
authorization: "{{ client_token }}",
container: '#bt-dropin',
paypal: {
flow: 'vault'
}
}, function(createErr, instance) {
form.addEventListener('submit', function(event) {
event.preventDefault();
instance.requestPaymentMethod(function(err, payload) {
if (err) {
console.log('Error', err);
return;
}
frappe.call({
method: "frappe.templates.pages.integrations.braintree_checkout.make_payment",
freeze: true,
headers: {
"X-Requested-With": "XMLHttpRequest"
},
args: {
"payload_nonce": payload.nonce,
"data": JSON.stringify(data),
"reference_doctype": doctype,
"reference_docname": docname
},
callback: function(r) {
if (r.message && r.message.status == "Completed") {
window.location.href = r.message.redirect_to
} else if (r.message && r.message.status == "Error") {
window.location.href = r.message.redirect_to
}
}
})
});
});
instance.on('paymentMethodRequestable', function (event) {
button.removeAttribute('disabled');
});
instance.on('noPaymentMethodRequestable', function () {
button.setAttribute('disabled', true);
});
});
})

View file

@ -1,53 +0,0 @@
$(document).ready(function(){
(function(e){
var options = {
"key": "{{ api_key }}",
"amount": cint({{ amount }} * 100), // 2000 paise = INR 20
"currency": "{{ currency }}",
"name": "{{ title }}",
"description": "{{ description }}",
"subscription_id": "{{ subscription_id }}",
"handler": function (response){
razorpay.make_payment_log(response, options, "{{ reference_doctype }}", "{{ reference_docname }}", "{{ token }}");
},
"prefill": {
"name": "{{ payer_name }}",
"email": "{{ payer_email }}",
"order_id": "{{ order_id }}"
},
"notes": {{ frappe.form_dict|json }}
};
var rzp = new Razorpay(options);
rzp.open();
// e.preventDefault();
})();
})
frappe.provide('razorpay');
razorpay.make_payment_log = function(response, options, doctype, docname, token){
$('.razorpay-loading').addClass('hidden');
$('.razorpay-confirming').removeClass('hidden');
frappe.call({
method:"frappe.templates.pages.integrations.razorpay_checkout.make_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
args: {
"razorpay_payment_id": response.razorpay_payment_id,
"options": options,
"reference_doctype": doctype,
"reference_docname": docname,
"token": token
},
callback: function(r){
if (r.message && r.message.status == 200) {
window.location.href = r.message.redirect_to
}
else if (r.message && ([401,400,500].indexOf(r.message.status) > -1)) {
window.location.href = r.message.redirect_to
}
}
})
}

View file

@ -1,85 +0,0 @@
var stripe = Stripe("{{ publishable_key }}");
var elements = stripe.elements();
var style = {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
var card = elements.create('card', {
hidePostalCode: true,
style: style
});
card.mount('#card-element');
function setOutcome(result) {
if (result.token) {
$('#submit').prop('disabled', true)
$('#submit').html(__('Processing...'))
frappe.call({
method:"frappe.templates.pages.integrations.stripe_checkout.make_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
args: {
"stripe_token_id": result.token.id,
"data": JSON.stringify({{ frappe.form_dict|json }}),
"reference_doctype": "{{ reference_doctype }}",
"reference_docname": "{{ reference_docname }}"
},
callback: function(r) {
if (r.message.status == "Completed") {
$('#submit').hide()
$('.success').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
} else {
$('#submit').hide()
$('.error').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
}
}
});
} else if (result.error) {
$('.error').html(result.error.message);
$('.error').show()
}
}
card.on('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
frappe.ready(function() {
$('#submit').off("click").on("click", function(e) {
e.preventDefault();
var extraDetails = {
name: $('input[name=cardholder-name]').val(),
email: $('input[name=cardholder-email]').val()
}
stripe.createToken(card, extraDetails).then(setOutcome);
})
});

View file

@ -1,54 +0,0 @@
{% extends "templates/web.html" %}
{% block title %} Payment {% endblock %}
{%- block header -%}{% endblock %}
{% block script %}
<script src="https://js.braintreegateway.com/web/dropin/1.9.3/js/dropin.min.js"></script>
<script>{% include "templates/includes/integrations/braintree_checkout.js" %}</script>
{% endblock %}
{%- block page_content -%}
<div class="wrapper">
<div class="checkout container">
<header>
<div>
<img class="center" src="{{ header_img }}"></img>
</div>
</header>
<form id="payment-form">
<section>
<div class="bt-drop-in-wrapper">
<div id="bt-dropin"></div>
</div>
</section>
<button class="btn btn-primary" type="submit" id="submit-button" disabled><span>{{ _("Pay") }} {{ amount }} {{ currency }}</span></button>
</form>
</div>
</div>
<style>
.checkout {
max-width: 60%;
}
.center {
margin:auto;
display: block;
}
#payment-form {
margin-top: 40px;
}
#submit-button {
float: right;
}
</style>
{% endblock %}

View file

@ -1,64 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.integrations.doctype.braintree_settings.braintree_settings import (
get_client_token,
get_gateway_controller,
)
from frappe.utils import flt
no_cache = 1
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
"currency",
)
def get_context(context):
context.no_cache = 1
# all these keys exist in form_dict
if not (set(expected_keys) - set(list(frappe.form_dict))):
for key in expected_keys:
context[key] = frappe.form_dict[key]
context.client_token = get_client_token(context.reference_docname)
context["amount"] = flt(context["amount"])
gateway_controller = get_gateway_controller(context.reference_docname)
context["header_img"] = frappe.db.get_value(
"Braintree Settings", gateway_controller, "header_img"
)
else:
frappe.redirect_to_message(
_("Some information is missing"),
_("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
)
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
@frappe.whitelist(allow_guest=True)
def make_payment(payload_nonce, data, reference_doctype, reference_docname):
data = json.loads(data)
data.update({"payload_nonce": payload_nonce})
gateway_controller = get_gateway_controller(reference_docname)
data = frappe.get_doc("Braintree Settings", gateway_controller).create_payment_request(data)
frappe.db.commit()
return data

View file

@ -1,22 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Payment Cancelled") }}{% endblock %}
{%- block page_content -%}
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator red'>
{{ _("Payment Cancelled") }}</span>
</div>
<p>{{ _("Your payment is cancelled.") }}</p>
<div><a href='{{ frappe.form_dict.redirect_to or "/" }}' class='btn btn-primary btn-sm'>
{{ _("Continue") }}</a></div>
</div>
<style>
.hero-and-content {
background-color: #f5f7fa;
}
{% include "templates/styles/card_style.css" %}
</style>
{% endblock %}

View file

@ -1,22 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Payment Failed") }}{% endblock %}
{%- block page_content -%}
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator red'>
{{ _("Payment Failed") }}</span>
</div>
<p>{{ _("Your payment has failed.") }}</p>
<div><a href='{{ frappe.form_dict.redirect_to or "/" }}' class='btn btn-primary btn-sm'>
{{ _("Continue") }}</a></div>
</div>
<style>
.hero-and-content {
background-color: #f5f7fa;
}
{% include "templates/styles/card_style.css" %}
</style>
{% endblock %}

View file

@ -1,34 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ _("Payment Success") }}{% endblock %}
{%- block page_content -%}
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator green'>
{{ _("Success") }}</span>
</div>
<p>{{ payment_message or _("Your payment was successfully accepted") }}</p>
{% if not payment_message %}
<div>
<a
href='{{ frappe.form_dict.redirect_to or "/" }}'
class='btn btn-primary btn-sm'>
{{ _("Continue") }}
</a>
</div>
{% endif %}
</div>
<style>
{% include "templates/styles/card_style.css" %}
</style>
<script>
frappe.ready(function() {
if('{{ frappe.form_dict.redirect_to or "" }}'){
setTimeout(function(){
window.location.href = '{{ frappe.form_dict.redirect_to }}';
}, 4000);
}
})
</script>
{% endblock %}

View file

@ -1,12 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
def get_context(context):
token = frappe.local.form_dict.token
if token:
frappe.db.set_value("Integration Request", token, "status", "Cancelled")
frappe.db.commit()

View file

@ -1,15 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
no_cache = True
def get_context(context):
token = frappe.local.form_dict.token
doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.docname)
context.payment_message = ""
if hasattr(doc, "get_payment_success_message"):
context.payment_message = doc.get_payment_success_message()

View file

@ -1,43 +0,0 @@
{% extends "templates/web.html" %}
{% block title %} Payment {% endblock %}
{%- block header -%}
<head>
<title>Merchant Checkout Page</title>
</head>
{% endblock %}
{% block script %}
<script defer type="text/javascript">
document.paytm_form.submit();
</script>
{% endblock %}
{%- block page_content -%}
<body>
<div class="centered">
<h2>Please do not refresh this page...</h2>
<form method="post" action="{{ url }}" name="paytm_form">
{% for name, value in payment_details.items() %}
<input type="hidden" name="{{ name }}" value="{{ value }}">
{% endfor %}
</form>
</div>
</body>
{% endblock %}
{% block style %}
<style>
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.web-footer {
display: none;
}
</style>
{% endblock %}

View file

@ -1,34 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.integrations.doctype.paytm_settings.paytm_settings import (
get_paytm_config,
get_paytm_params,
)
def get_context(context):
context.no_cache = 1
paytm_config = get_paytm_config()
try:
doc = frappe.get_doc("Integration Request", frappe.form_dict["order_id"])
context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config)
context.url = paytm_config.url
except Exception:
frappe.log_error()
frappe.redirect_to_message(
_("Invalid Token"),
_("Seems token you are using is invalid!"),
http_status_code=400,
indicator_color="red",
)
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect

View file

@ -1,28 +0,0 @@
{% extends "templates/web.html" %}
{% block title %} Payment {% endblock %}
{%- block header -%}{% endblock %}
{% block script %}
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<script>{% include "templates/includes/integrations/razorpay_checkout.js" %}</script>
{% endblock %}
{%- block page_content -%}
<p class='lead text-center centered'>
<span class='razorpay-loading'>Loading Payment System</span>
<span class='razorpay-confirming hidden'>Confirming Payment</span>
</p>
{% endblock %}
{% block style %}
<style>
header, footer {
display: none;
}
</style>
{% endblock %}

View file

@ -1,79 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.utils import cint, flt
no_cache = 1
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
"currency",
)
def get_context(context):
context.no_cache = 1
context.api_key = get_api_key()
try:
doc = frappe.get_doc("Integration Request", frappe.form_dict["token"])
payment_details = json.loads(doc.data)
for key in expected_keys:
context[key] = payment_details[key]
context["token"] = frappe.form_dict["token"]
context["amount"] = flt(context["amount"])
context["subscription_id"] = (
payment_details["subscription_id"] if payment_details.get("subscription_id") else ""
)
except Exception as e:
frappe.redirect_to_message(
_("Invalid Token"),
_("Seems token you are using is invalid!"),
http_status_code=400,
indicator_color="red",
)
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
def get_api_key():
api_key = frappe.db.get_single_value("Razorpay Settings", "api_key")
if cint(frappe.form_dict.get("use_sandbox")):
api_key = frappe.conf.sandbox_api_key
return api_key
@frappe.whitelist(allow_guest=True)
def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token):
data = {}
if isinstance(options, str):
data = json.loads(options)
data.update(
{
"razorpay_payment_id": razorpay_payment_id,
"reference_docname": reference_docname,
"reference_doctype": reference_doctype,
"token": token,
}
)
data = frappe.get_doc("Razorpay Settings").create_request(data)
frappe.db.commit()
return data

View file

@ -1,113 +0,0 @@
.StripeElement {
background-color: white;
height: 40px;
padding: 10px 12px;
border-radius: 4px;
border: 1px solid transparent;
box-shadow: 0 1px 3px 0 #e6ebf1;
-webkit-transition: box-shadow 150ms ease;
transition: box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}
.StripeElement--invalid {
border-color: #fa755a;
}
.StripeElement--webkit-autofill {
background-color: #fefde5;
}
.stripe #payment-form {
margin-top: 80px;
}
.stripe button {
float: right;
display: block;
background: #5e64ff;
color: white;
box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
border-radius: 4px;
border: 0;
margin-top: 20px;
font-size: 15px;
font-weight: 400;
max-width: 40%;
height: 40px;
line-height: 38px;
outline: none;
}
.stripe button:hover, .stripe button:focus {
background: #2b33ff;
border-color: #0711ff;
}
.stripe button:active {
background: #5e64ff;
}
.stripe button:disabled {
background: #515e80;
}
.stripe .group {
background: white;
box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
border-radius: 4px;
margin-bottom: 20px;
}
.stripe label {
position: relative;
color: #8898AA;
font-weight: 300;
height: 40px;
line-height: 40px;
margin-left: 20px;
display: block;
}
.stripe .group label:not(:last-child) {
border-bottom: 1px solid #F0F5FA;
}
.stripe label>span {
width: 20%;
text-align: right;
float: left;
}
.current-card {
margin-left: 20px;
}
.field {
background: transparent;
font-weight: 300;
border: 0;
color: #31325F;
outline: none;
padding-right: 10px;
padding-left: 10px;
cursor: text;
width: 70%;
height: 40px;
float: right;
}
.field::-webkit-input-placeholder {
color: #CFD7E0;
}
.field::-moz-placeholder {
color: #CFD7E0;
}
.field:-ms-input-placeholder {
color: #CFD7E0;
}

View file

@ -1,58 +0,0 @@
{% extends "templates/web.html" %}
{% block title %} Payment {% endblock %}
{%- block header -%}
{% endblock %}
{% block script %}
<script src="https://js.stripe.com/v3/"></script>
<script>{% include "templates/includes/integrations/stripe_checkout.js" %}</script>
{% endblock %}
{%- block page_content -%}
<div class="stripe" style="min-height: 400px; padding-bottom: 50px; margin-top:100px;margin-left:250px;">
<div class="col-sm-10 col-sm-offset-2">
{% if image %}
<img src={{image}}>
{% endif %}
<h2 class="text-center">{{description}}</h2>
<form id="payment-form">
<div class="form-row row">
<div class="group col-12">
<div>
<label>
<span>{{ _("Name") }}</span>
<input id="cardholder-name" name="cardholder-name" class="field" placeholder="{{ _('John Doe') }}" value="{{payer_name}}"/>
</label>
</div>
</div>
<div class="group col-12">
<div>
<label>
<span>{{ _("Email") }}</span>
<input id="cardholder-email" name="cardholder-email" class="field" placeholder="{{ _('john@doe.com') }}" value="{{payer_email}}"/>
</label>
</div>
</div>
<div class="group col-12">
<label>
<span>{{ _("Card Details") }}</span>
<div id="card-element" name="card-element" class="field"></div>
<div id="card-errors" role="alert"></div>
</label>
</div>
</div>
<button type="submit" class="submit" id="submit">{{_('Pay')}} {{amount}}</button>
<div class="outcome text-center">
<div class="error" hidden>{{ _("An error occured during the payment process. Please contact us.") }}</div>
<div class="success" hidden>{{ _("Your payment has been successfully registered.") }}</div>
</div>
</form>
</div>
</div>
{% endblock %}

View file

@ -1,91 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller
from frappe.utils import cint, fmt_money
no_cache = 1
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
"currency",
)
def get_context(context):
context.no_cache = 1
# all these keys exist in form_dict
if not (set(expected_keys) - set(list(frappe.form_dict))):
for key in expected_keys:
context[key] = frappe.form_dict[key]
gateway_controller = get_gateway_controller(context.reference_doctype, context.reference_docname)
context.publishable_key = get_api_key(context.reference_docname, gateway_controller)
context.image = get_header_image(context.reference_docname, gateway_controller)
context["amount"] = fmt_money(amount=context["amount"], currency=context["currency"])
if is_a_subscription(context.reference_doctype, context.reference_docname):
payment_plan = frappe.db.get_value(
context.reference_doctype, context.reference_docname, "payment_plan"
)
recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence")
context["amount"] = context["amount"] + " " + _(recurrence)
else:
frappe.redirect_to_message(
_("Some information is missing"),
_("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
)
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
def get_api_key(doc, gateway_controller):
publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key")
if cint(frappe.form_dict.get("use_sandbox")):
publishable_key = frappe.conf.sandbox_publishable_key
return publishable_key
def get_header_image(doc, gateway_controller):
header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img")
return header_image
@frappe.whitelist(allow_guest=True)
def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None):
data = json.loads(data)
data.update({"stripe_token_id": stripe_token_id})
gateway_controller = get_gateway_controller(reference_doctype, reference_docname)
if is_a_subscription(reference_doctype, reference_docname):
reference = frappe.get_doc(reference_doctype, reference_docname)
data = reference.create_subscription("stripe", gateway_controller, data)
else:
data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data)
frappe.db.commit()
return data
def is_a_subscription(reference_doctype, reference_docname):
if not frappe.get_meta(reference_doctype).has_field("is_a_subscription"):
return False
return frappe.db.get_value(reference_doctype, reference_docname, "is_a_subscription")

View file

@ -517,8 +517,7 @@ class TestReportview(unittest.TestCase):
data = frappe.db.get_list(
"Web Form",
filters=[["Web Form Field", "reqd", "=", 1]],
group_by="amount_field",
fields=["count(*) as count", "`amount_field` as name"],
fields=["count(*) as count"],
order_by="count desc",
limit=50,
)

View file

@ -559,7 +559,7 @@ def is_cli() -> bool:
return invoked_from_terminal
def update_progress_bar(txt, i, l):
def update_progress_bar(txt, i, l, absolute=False):
if os.environ.get("CI"):
if i == 0:
sys.stdout.write(txt)
@ -581,8 +581,9 @@ def update_progress_bar(txt, i, l):
complete = int(float(i + 1) / l * col)
completion_bar = ("=" * complete).ljust(col, " ")
percent_complete = str(int(float(i + 1) / l * 100))
sys.stdout.write(f"\r{txt}: [{completion_bar}] {percent_complete}%")
percent_complete = f"{str(int(float(i + 1) / l * 100))}%"
status = f"{i} of {l}" if absolute else percent_complete
sys.stdout.write(f"\r{txt}: [{completion_bar}] {status}")
sys.stdout.flush()

Some files were not shown because too many files have changed in this diff Show more