Ver a proveniência

feat: add payment and custom field creation utils

* did a bit of cleanup via pre-commit
* added custom field creation/deletion via hooks
* add scheduler event for razorpay
pull/2/head
phot0n há 2 anos
ascendente
cometimento
a79e6122fe
11 ficheiros alterados com 253 adições e 65 eliminações
  1. +1
    -3
      payments/__init__.py
  2. +10
    -22
      payments/hooks.py
  3. +8
    -3
      payments/payment_gateways/doctype/braintree_settings/braintree_settings.py
  4. +23
    -7
      payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
  5. +3
    -2
      payments/payment_gateways/doctype/paytm_settings/paytm_settings.py
  6. +29
    -16
      payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
  7. +6
    -7
      payments/payment_gateways/doctype/stripe_settings/stripe_settings.py
  8. +7
    -3
      payments/templates/pages/braintree_checkout.py
  9. +6
    -0
      payments/utils/__init__.py
  10. +158
    -0
      payments/utils/utils.py
  11. +2
    -2
      setup.py

+ 1
- 3
payments/__init__.py Ver ficheiro

@@ -1,3 +1 @@

__version__ = '0.0.1'

__version__ = "0.0.1"

+ 10
- 22
payments/hooks.py Ver ficheiro

@@ -64,12 +64,12 @@ app_license = "MIT"
# ------------

# before_install = "pay.install.before_install"
# after_install = "pay.install.after_install"
after_install = "payments.utils.make_custom_fields"

# Uninstallation
# ------------

# before_uninstall = "pay.uninstall.before_uninstall"
before_uninstall = "payments.utils.delete_custom_fields"
# after_uninstall = "pay.uninstall.after_uninstall"

# Desk Notifications
@@ -94,9 +94,9 @@ app_license = "MIT"
# ---------------
# Override standard doctype classes

# override_doctype_class = {
# "ToDo": "custom_app.overrides.CustomToDo"
# }
override_doctype_class = {
"Web Form": "payments.overrides.paymentwebform.PaymentWebForm"
}

# Document Events
# ---------------
@@ -113,23 +113,11 @@ app_license = "MIT"
# Scheduled Tasks
# ---------------

# scheduler_events = {
# "all": [
# "pay.tasks.all"
# ],
# "daily": [
# "pay.tasks.daily"
# ],
# "hourly": [
# "pay.tasks.hourly"
# ],
# "weekly": [
# "pay.tasks.weekly"
# ],
# "monthly": [
# "pay.tasks.monthly"
# ],
# }
scheduler_events = {
"all": [
"payments.payment_gateways.doctype.razorpay_settings.razorpay_settings.capture_payment",
],
}

# Testing
# -------


+ 8
- 3
payments/payment_gateways/doctype/braintree_settings/braintree_settings.py Ver ficheiro

@@ -7,10 +7,12 @@ import braintree

import frappe
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, get_url

from payments.utils import create_payment_gateway


class BraintreeSettings(Document):
supported_currencies = [
@@ -157,7 +159,9 @@ class BraintreeSettings(Document):

def on_update(self):
create_payment_gateway(
"Braintree-" + self.gateway_name, settings="Braintree Settings", controller=self.gateway_name
"Braintree-" + self.gateway_name,
settings="Braintree Settings",
controller=self.gateway_name,
)
call_hook_method("payment_gateway_enabled", gateway="Braintree-" + self.gateway_name)

@@ -237,7 +241,8 @@ class BraintreeSettings(Document):
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"
"code: " + str(error.code) + " | message: " + str(error.message),
"Braintree Payment Error",
)
self.integration_request.db_set("error", error_log.error, update_modified=False)



+ 23
- 7
payments/payment_gateways/doctype/paypal_settings/paypal_settings.py Ver ficheiro

@@ -8,7 +8,7 @@

Example:

from frappe.integrations.utils import get_payment_gateway_controller
from payments.utils import get_payment_gateway_controller

controller = get_payment_gateway_controller("PayPal")
controller().validate_transaction_currency(currency)
@@ -69,10 +69,12 @@ import pytz

import frappe
from frappe import _
from frappe.integrations.utils import create_payment_gateway, create_request_log, make_post_request
from frappe.integrations.utils import create_request_log, make_post_request
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, get_datetime, get_url

from payments.utils import create_payment_gateway

api_path = "/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings"


@@ -179,7 +181,10 @@ class PayPalSettings(Document):
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]}
{
"token": response.get("TOKEN")[0],
"correlation_id": response.get("CORRELATIONID")[0],
}
)

create_request_log(kwargs, service_name="PayPal", name=kwargs["token"])
@@ -373,7 +378,9 @@ def create_recurring_profile(token, payerid):
}
)

status_changed_to = "Completed" if data.get("starting_immediately") or updating else "Verified"
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(
@@ -433,7 +440,11 @@ def get_redirect_uri(doc, token, payerid):

def manage_recurring_payment_profile_status(profile_id, action, args, url):
args.update(
{"METHOD": "ManageRecurringPaymentsProfileStatus", "PROFILEID": profile_id, "ACTION": action}
{
"METHOD": "ManageRecurringPaymentsProfileStatus",
"PROFILEID": profile_id,
"ACTION": action,
}
)

response = make_post_request(url, data=args)
@@ -442,7 +453,9 @@ def manage_recurring_payment_profile_status(profile_id, action, args, url):
# 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":
if (
response.get("ACK")[0] != "Success" and response.get("L_ERRORCODE0", [])[0] != "11556"
):
frappe.throw(_("Failed while amending subscription"))


@@ -491,7 +504,10 @@ def validate_ipn_request(data):
params, url = doc.get_paypal_params_and_url()

params.update(
{"METHOD": "GetRecurringPaymentsProfileDetails", "PROFILEID": data.get("recurring_payment_id")}
{
"METHOD": "GetRecurringPaymentsProfileDetails",
"PROFILEID": data.get("recurring_payment_id"),
}
)

params = urlencode(params)


+ 3
- 2
payments/payment_gateways/doctype/paytm_settings/paytm_settings.py Ver ficheiro

@@ -6,14 +6,15 @@ 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.integrations.utils import 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

from payments.utils import create_payment_gateway


class PaytmSettings(Document):
supported_currencies = ["INR"]


+ 29
- 16
payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py Ver ficheiro

@@ -8,7 +8,7 @@

Example:

from frappe.integrations.utils import get_payment_gateway_controller
from payments.utils import get_payment_gateway_controller

controller = get_payment_gateway_controller("Razorpay")
controller().validate_transaction_currency(currency)
@@ -64,19 +64,16 @@ import hmac
import json
from urllib.parse import urlencode

import razorpay

import frappe
import razorpay
from frappe import _
from frappe.integrations.utils import (
create_payment_gateway,
create_request_log,
make_get_request,
make_post_request,
)
from frappe.integrations.utils import (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

from payments.utils import create_payment_gateway


class RazorpaySettings(Document):
supported_currencies = ["INR"]
@@ -97,7 +94,10 @@ class RazorpaySettings(Document):
try:
make_get_request(
url="https://api.razorpay.com/v1/payments",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
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 !!!"))
@@ -123,7 +123,9 @@ class RazorpaySettings(Document):
"quantity": 1 (The total amount is calculated as item.amount * quantity)
}
"""
url = "https://api.razorpay.com/v1/subscriptions/{}/addons".format(kwargs.get("subscription_id"))
url = "https://api.razorpay.com/v1/subscriptions/{}/addons".format(
kwargs.get("subscription_id")
)

try:
if not frappe.conf.converted_rupee_to_paisa:
@@ -137,7 +139,9 @@ class RazorpaySettings(Document):
headers={"content-type": "application/json"},
)
if not resp.get("id"):
frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
frappe.log_error(
message=str(resp), title="Razorpay Failed while creating subscription"
)
except Exception:
frappe.log_error()
# failed
@@ -176,7 +180,9 @@ class RazorpaySettings(Document):
frappe.flags.status = "created"
return kwargs
else:
frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
frappe.log_error(
message=str(resp), title="Razorpay Failed while creating subscription"
)

except Exception:
frappe.log_error()
@@ -214,7 +220,10 @@ class RazorpaySettings(Document):
try:
order = make_post_request(
"https://api.razorpay.com/v1/orders",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
auth=(
self.api_key,
self.get_password(fieldname="api_secret", raise_exception=False),
),
data=payment_options,
)
order["integration_request"] = integration_request.name
@@ -387,7 +396,9 @@ def capture_payment(is_sandbox=False, sanbox_response=None):

if resp.get("status") == "authorized":
resp = make_post_request(
"https://api.razorpay.com/v1/payments/{}/capture".format(data.get("razorpay_payment_id")),
"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")},
)
@@ -417,7 +428,9 @@ def get_order(doctype, docname):
# 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.log_error(
frappe.get_traceback(), _("Controller method get_razorpay_order missing")
)
frappe.throw(_("Could not create Razorpay order. Please contact Administrator"))




+ 6
- 7
payments/payment_gateways/doctype/stripe_settings/stripe_settings.py Ver ficheiro

@@ -5,15 +5,12 @@ 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.integrations.utils import create_request_log, make_get_request
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url

from payments.utils import create_payment_gateway


class StripeSettings(Document):
supported_currencies = [
@@ -153,7 +150,9 @@ class StripeSettings(Document):

def on_update(self):
create_payment_gateway(
"Stripe-" + self.gateway_name, settings="Stripe Settings", controller=self.gateway_name
"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:


+ 7
- 3
payments/templates/pages/braintree_checkout.py Ver ficheiro

@@ -6,7 +6,7 @@ import json
import frappe
from frappe import _
from frappe.integrations.doctype.braintree_settings.braintree_settings import (
get_client_token,
get_client_token,
get_gateway_controller,
)
from frappe.utils import flt
@@ -46,7 +46,9 @@ def get_context(context):
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."),
_(
"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
@@ -59,6 +61,8 @@ def make_payment(payload_nonce, data, reference_doctype, reference_docname):
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)
data = frappe.get_doc("Braintree Settings", gateway_controller).create_payment_request(
data
)
frappe.db.commit()
return data

+ 6
- 0
payments/utils/__init__.py Ver ficheiro

@@ -0,0 +1,6 @@
from utils import (
get_payment_gateway_controller,
create_payment_gateway,
make_custom_fields,
delete_custom_fields,
)

+ 158
- 0
payments/utils/utils.py Ver ficheiro

@@ -0,0 +1,158 @@
import click

import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields


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 make_custom_fields():
if not frappe.get_meta("Web Form").has_field("payments"):
click.secho("* Installing Payment Custom Fields in Web Form")

create_custom_fields({
'Web Form': [
{
"fieldname": "payments_tab",
"fieldtype": "Tab Break",
"label": "Payments",
"insert_after": "custom_css"
},
{
"default": "0",
"fieldname": "accept_payment",
"fieldtype": "Check",
"label": "Accept Payment",
"insert_after": "payments"
},
{
"depends_on": "accept_payment",
"fieldname": "payment_gateway",
"fieldtype": "Link",
"label": "Payment Gateway",
"options": "Payment Gateway",
"insert_after": "accept_payment"
},
{
"default": "Buy Now",
"depends_on": "accept_payment",
"fieldname": "payment_button_label",
"fieldtype": "Data",
"label": "Button Label",
"insert_after": "payment_gateway"
},
{
"depends_on": "accept_payment",
"fieldname": "payment_button_help",
"fieldtype": "Text",
"label": "Button Help",
"insert_after": "payment_button_label"
},
{
"fieldname": "payments_cb",
"fieldtype": "Column Break",
"insert_after": "payment_button_help"
},
{
"default": "0",
"depends_on": "accept_payment",
"fieldname": "amount_based_on_field",
"fieldtype": "Check",
"label": "Amount Based On Field",
"insert_after": "payments_cb"
},
{
"depends_on": "eval:doc.accept_payment && doc.amount_based_on_field",
"fieldname": "amount_field",
"fieldtype": "Select",
"label": "Amount Field",
"insert_after": "amount_based_on_field"
},
{
"depends_on": "eval:doc.accept_payment && !doc.amount_based_on_field",
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"insert_after": "amount_field"
},
{
"depends_on": "accept_payment",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"insert_after": "amount"
}
]
})


def delete_custom_fields():
if frappe.get_meta("Web Form").has_field("payments_tab"):
click.secho("* Uninstalling Payment Custom Fields from Web Form")

fieldnames = (
"payments_tab",
"accept_payment",
"payment_gateway",
"payment_button_label",
"payment_button_help",
"payments_cb",
"amount_field",
"amount_based_on_field",
"amount",
"currency"
)

for fieldname in fieldnames:
frappe.db.delete(
"Custom Field",
{"name": "Web Form-" + fieldname}
)

+ 2
- 2
setup.py Ver ficheiro

@@ -1,4 +1,4 @@
from setuptools import setup, find_packages
from setuptools import find_packages, setup

with open("requirements.txt") as f:
install_requires = f.read().strip().split("\n")
@@ -15,5 +15,5 @@ setup(
packages=find_packages(),
zip_safe=False,
include_package_data=True,
install_requires=install_requires
install_requires=install_requires,
)

Carregando…
Cancelar
Guardar