Преглед изворни кода

style: format all python files using black (#16453)

Co-authored-by: Frappe Bot <developers@frappe.io>
pull/2/head
Suraj Shetty пре 3 година
committed by GitHub
родитељ
комит
dd9c4d4f1e
16 измењених фајлова са 1001 додато и 477 уклоњено
  1. +203
    -53
      payments/payment_gateways/doctype/braintree_settings/braintree_settings.py
  2. +1
    -0
      payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.py
  3. +203
    -120
      payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
  4. +94
    -70
      payments/payment_gateways/doctype/paytm_settings/paytm_settings.py
  5. +1
    -0
      payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py
  6. +179
    -130
      payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
  7. +192
    -42
      payments/payment_gateways/doctype/stripe_settings/stripe_settings.py
  8. +1
    -0
      payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.py
  9. +2
    -1
      payments/payments/doctype/payment_gateway/payment_gateway.py
  10. +3
    -1
      payments/payments/doctype/payment_gateway/test_payment_gateway.py
  11. +29
    -12
      payments/templates/pages/braintree_checkout.py
  12. +1
    -0
      payments/templates/pages/payment_cancel.py
  13. +6
    -5
      payments/templates/pages/payment_success.py
  14. +15
    -7
      payments/templates/pages/paytm_checkout.py
  15. +37
    -19
      payments/templates/pages/razorpay_checkout.py
  16. +34
    -17
      payments/templates/pages/stripe_checkout.py

+ 203
- 53
payments/payment_gateways/doctype/braintree_settings/braintree_settings.py Прегледај датотеку

@@ -2,25 +2,154 @@
# Copyright (c) 2018, Frappe Technologies and contributors # Copyright (c) 2018, Frappe Technologies and contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE


import frappe
from frappe.model.document import Document
from urllib.parse import urlencode
import braintree import braintree

import frappe
from frappe import _ from frappe import _
from urllib.parse import urlencode
from frappe.utils import get_url, call_hook_method
from frappe.integrations.utils import create_request_log, create_payment_gateway
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): class BraintreeSettings(Document):
supported_currencies = [ 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"
"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): def validate(self):
@@ -28,25 +157,31 @@ class BraintreeSettings(Document):
self.configure_braintree() self.configure_braintree()


def on_update(self): 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)
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): def configure_braintree(self):
if self.use_sandbox: if self.use_sandbox:
environment = 'sandbox'
environment = "sandbox"
else: else:
environment = 'production'
environment = "production"


braintree.Configuration.configure( braintree.Configuration.configure(
environment=environment, environment=environment,
merchant_id=self.merchant_id, merchant_id=self.merchant_id,
public_key=self.public_key, public_key=self.public_key,
private_key=self.get_password(fieldname='private_key',raise_exception=False)
private_key=self.get_password(fieldname="private_key", raise_exception=False),
) )


def validate_transaction_currency(self, currency): def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies: if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
frappe.throw(
_(
"Please select another payment method. Stripe does not support transactions in currency '{0}'"
).format(currency)
)


def get_payment_url(self, **kwargs): def get_payment_url(self, **kwargs):
return get_url("./integrations/braintree_checkout?{0}".format(urlencode(kwargs))) return get_url("./integrations/braintree_checkout?{0}".format(urlencode(kwargs)))
@@ -60,48 +195,62 @@ class BraintreeSettings(Document):


except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) 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
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): def create_charge_on_braintree(self):
self.configure_braintree() self.configure_braintree()


redirect_to = self.data.get('redirect_to') or None
redirect_message = self.data.get('redirect_message') or None
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
result = braintree.Transaction.sale(
{
"amount": self.data.amount,
"payment_method_nonce": self.data.payload_nonce,
"options": {"submit_for_settlement": True},
} }
})
)


if result.is_success: if result.is_success:
self.integration_request.db_set('status', 'Completed', update_modified=False)
self.integration_request.db_set("status", "Completed", update_modified=False)
self.flags.status_changed_to = "Completed" self.flags.status_changed_to = "Completed"
self.integration_request.db_set('output', result.transaction.status, update_modified=False)
self.integration_request.db_set("output", result.transaction.status, update_modified=False)


elif result.transaction: 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)
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: else:
self.integration_request.db_set('status', 'Failed', update_modified=False)
self.integration_request.db_set("status", "Failed", update_modified=False)
for error in result.errors.deep_errors: 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)
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": if self.flags.status_changed_to == "Completed":
status = 'Completed'
status = "Completed"
if self.data.reference_doctype and self.data.reference_docname: if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None custom_redirect_to = None
try: 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')
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: if braintree_success_page:
custom_redirect_to = frappe.get_attr(braintree_success_page[-1])(self.data) custom_redirect_to = frappe.get_attr(braintree_success_page[-1])(self.data)
except Exception: except Exception:
@@ -110,26 +259,27 @@ class BraintreeSettings(Document):
if custom_redirect_to: if custom_redirect_to:
redirect_to = custom_redirect_to redirect_to = custom_redirect_to


redirect_url = 'payment-success'
redirect_url = "payment-success"
else: else:
status = 'Error'
redirect_url = 'payment-failed'
status = "Error"
redirect_url = "payment-failed"


if redirect_to: if redirect_to:
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message: if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
redirect_url += "&" + urlencode({"redirect_message": redirect_message})

return {"redirect_to": redirect_url, "status": status}


return {
"redirect_to": redirect_url,
"status": status
}


def get_gateway_controller(doc): def get_gateway_controller(doc):
payment_request = frappe.get_doc("Payment Request", doc) payment_request = frappe.get_doc("Payment Request", doc)
gateway_controller = frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller")
gateway_controller = frappe.db.get_value(
"Payment Gateway", payment_request.payment_gateway, "gateway_controller"
)
return gateway_controller return gateway_controller



def get_client_token(doc): def get_client_token(doc):
gateway_controller = get_gateway_controller(doc) gateway_controller = get_gateway_controller(doc)
settings = frappe.get_doc("Braintree Settings", gateway_controller) settings = frappe.get_doc("Braintree Settings", gateway_controller)


+ 1
- 0
payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.py Прегледај датотеку

@@ -3,5 +3,6 @@
# License: MIT. See LICENSE # License: MIT. See LICENSE
import unittest import unittest



class TestBraintreeSettings(unittest.TestCase): class TestBraintreeSettings(unittest.TestCase):
pass pass

+ 203
- 120
payments/payment_gateways/doctype/paypal_settings/paypal_settings.py Прегледај датотеку

@@ -63,21 +63,48 @@ More Details:


""" """


import frappe
import json import json
from urllib.parse import urlencode

import pytz import pytz

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


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


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


class PayPalSettings(Document): 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"]
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): def __setup__(self):
setattr(self, "use_sandbox", 0) setattr(self, "use_sandbox", 0)
@@ -88,7 +115,7 @@ class PayPalSettings(Document):


def validate(self): def validate(self):
create_payment_gateway("PayPal") create_payment_gateway("PayPal")
call_hook_method('payment_gateway_enabled', gateway="PayPal")
call_hook_method("payment_gateway_enabled", gateway="PayPal")
if not self.flags.ignore_mandatory: if not self.flags.ignore_mandatory:
self.validate_paypal_credentails() self.validate_paypal_credentails()


@@ -97,7 +124,11 @@ class PayPalSettings(Document):


def validate_transaction_currency(self, currency): def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies: if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. PayPal does not support transactions in currency '{0}'").format(currency))
frappe.throw(
_(
"Please select another payment method. PayPal does not support transactions in currency '{0}'"
).format(currency)
)


def get_paypal_params_and_url(self): def get_paypal_params_and_url(self):
params = { params = {
@@ -105,17 +136,23 @@ class PayPalSettings(Document):
"PWD": self.get_password(fieldname="api_password", raise_exception=False), "PWD": self.get_password(fieldname="api_password", raise_exception=False),
"SIGNATURE": self.signature, "SIGNATURE": self.signature,
"VERSION": "98", "VERSION": "98",
"METHOD": "GetPalDetails"
"METHOD": "GetPalDetails",
} }


if hasattr(self, "use_sandbox") and self.use_sandbox: 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"
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 return params, api_url


@@ -142,27 +179,30 @@ class PayPalSettings(Document):
else: else:
return_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" 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]
})
self.integration_request = create_request_log(kwargs, "Remote", "PayPal", response.get("TOKEN")[0])
kwargs.update(
{"token": response.get("TOKEN")[0], "correlation_id": response.get("CORRELATIONID")[0]}
)
self.integration_request = create_request_log(
kwargs, "Remote", "PayPal", response.get("TOKEN")[0]
)


return return_url.format(kwargs["token"]) return return_url.format(kwargs["token"])


def execute_set_express_checkout(self, **kwargs): def execute_set_express_checkout(self, **kwargs):
params, url = self.get_paypal_params_and_url() params, url = self.get_paypal_params_and_url()


params.update({
"METHOD": "SetExpressCheckout",
"returnUrl": get_url("{0}.get_express_checkout_details".format(api_path)),
"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'):
params.update(
{
"METHOD": "SetExpressCheckout",
"returnUrl": get_url("{0}.get_express_checkout_details".format(api_path)),
"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) self.configure_recurring_payments(params, kwargs)


params = urlencode(params) params = urlencode(params)
@@ -175,14 +215,20 @@ class PayPalSettings(Document):


def configure_recurring_payments(self, params, kwargs): def configure_recurring_payments(self, params, kwargs):
# removing the params as we have to setup rucurring payments # removing the params as we have to setup rucurring payments
for param in ('PAYMENTREQUEST_0_PAYMENTACTION', 'PAYMENTREQUEST_0_AMT',
'PAYMENTREQUEST_0_CURRENCYCODE'):
for param in (
"PAYMENTREQUEST_0_PAYMENTACTION",
"PAYMENTREQUEST_0_AMT",
"PAYMENTREQUEST_0_CURRENCYCODE",
):
del params[param] del params[param]


params.update({
"L_BILLINGTYPE0": "RecurringPayments", #The type of billing agreement
"L_BILLINGAGREEMENTDESCRIPTION0": kwargs['description']
})
params.update(
{
"L_BILLINGTYPE0": "RecurringPayments", # The type of billing agreement
"L_BILLINGAGREEMENTDESCRIPTION0": kwargs["description"],
}
)



def get_paypal_and_transaction_details(token): def get_paypal_and_transaction_details(token):
doc = frappe.get_doc("PayPal Settings") doc = frappe.get_doc("PayPal Settings")
@@ -194,23 +240,25 @@ def get_paypal_and_transaction_details(token):


return data, params, url return data, params, url



def setup_redirect(data, redirect_url, custom_redirect_to=None, redirect=True): 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
redirect_to = data.get("redirect_to") or None
redirect_message = data.get("redirect_message") or None


if custom_redirect_to: if custom_redirect_to:
redirect_to = custom_redirect_to redirect_to = custom_redirect_to


if redirect_to: if redirect_to:
redirect_url += '&' + urlencode({'redirect_to': redirect_to})
redirect_url += "&" + urlencode({"redirect_to": redirect_to})
if redirect_message: if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
redirect_url += "&" + urlencode({"redirect_message": redirect_message})


# this is done so that functions called via hooks can update flags.redirect_to # this is done so that functions called via hooks can update flags.redirect_to
if redirect: if redirect:
frappe.local.response["type"] = "redirect" frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url(redirect_url) frappe.local.response["location"] = get_url(redirect_url)



@frappe.whitelist(allow_guest=True, xss_safe=True) @frappe.whitelist(allow_guest=True, xss_safe=True)
def get_express_checkout_details(token): def get_express_checkout_details(token):
try: try:
@@ -218,26 +266,29 @@ def get_express_checkout_details(token):
doc.setup_sandbox_env(token) doc.setup_sandbox_env(token)


params, url = doc.get_paypal_params_and_url() params, url = doc.get_paypal_params_and_url()
params.update({
"METHOD": "GetExpressCheckoutDetails",
"TOKEN": token
})
params.update({"METHOD": "GetExpressCheckoutDetails", "TOKEN": token})


response = make_post_request(url, data=params) response = make_post_request(url, data=params)


if response.get("ACK")[0] != "Success": 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)
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 return


doc = frappe.get_doc("Integration Request", token) 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)
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["type"] = "redirect"
frappe.local.response["location"] = get_redirect_uri(doc, token, response.get("PAYERID")[0]) frappe.local.response["location"] = get_redirect_uri(doc, token, response.get("PAYERID")[0])
@@ -245,35 +296,45 @@ def get_express_checkout_details(token):
except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())



@frappe.whitelist(allow_guest=True, xss_safe=True) @frappe.whitelist(allow_guest=True, xss_safe=True)
def confirm_payment(token): def confirm_payment(token):
try: try:
custom_redirect_to = None custom_redirect_to = None
data, params, url = get_paypal_and_transaction_details(token) 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()
})
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) response = make_post_request(url, data=params)


if response.get("ACK")[0] == "Success": 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")
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"): 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")
custom_redirect_to = frappe.get_doc(
data.get("reference_doctype"), data.get("reference_docname")
).run_method("on_payment_authorized", "Completed")
frappe.db.commit() frappe.db.commit()


redirect_url = '/integrations/payment-success?doctype={0}&docname={1}'.format(data.get("reference_doctype"), data.get("reference_docname"))
redirect_url = "/integrations/payment-success?doctype={0}&docname={1}".format(
data.get("reference_doctype"), data.get("reference_docname")
)
else: else:
redirect_url = "/integrations/payment-failed" redirect_url = "/integrations/payment-failed"


@@ -282,6 +343,7 @@ def confirm_payment(token):
except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())



@frappe.whitelist(allow_guest=True, xss_safe=True) @frappe.whitelist(allow_guest=True, xss_safe=True)
def create_recurring_profile(token, payerid): def create_recurring_profile(token, payerid):
try: try:
@@ -292,49 +354,60 @@ def create_recurring_profile(token, payerid):
addons = data.get("addons") addons = data.get("addons")
subscription_details = data.get("subscription_details") subscription_details = data.get("subscription_details")


if data.get('subscription_id'):
if data.get("subscription_id"):
if addons: if addons:
updating = True 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'
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 = 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)
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()
})
# "PROFILESTARTDATE": datetime.utcfromtimestamp(get_timestamp(starts_at)).isoformat()
params.update({"PROFILESTARTDATE": starts_at.isoformat()})


response = make_post_request(url, data=params) response = make_post_request(url, data=params)


if response.get("ACK")[0] == "Success": if response.get("ACK")[0] == "Success":
update_integration_request_status(token, {
"profile_id": response.get("PROFILEID")[0],
}, "Completed")
update_integration_request_status(
token,
{
"profile_id": response.get("PROFILEID")[0],
},
"Completed",
)


if data.get("reference_doctype") and data.get("reference_docname"): if data.get("reference_doctype") and data.get("reference_docname"):
data['subscription_id'] = response.get("PROFILEID")[0]
data["subscription_id"] = response.get("PROFILEID")[0]


frappe.flags.data = data 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)
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() frappe.db.commit()


redirect_url = '/integrations/payment-success?doctype={0}&docname={1}'.format(data.get("reference_doctype"), data.get("reference_docname"))
redirect_url = "/integrations/payment-success?doctype={0}&docname={1}".format(
data.get("reference_doctype"), data.get("reference_docname")
)
else: else:
redirect_url = "/integrations/payment-failed" redirect_url = "/integrations/payment-failed"


@@ -343,26 +416,29 @@ def create_recurring_profile(token, payerid):
except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())



def update_integration_request_status(token, data, status, error=False, doc=None): def update_integration_request_status(token, data, status, error=False, doc=None):
if not doc: if not doc:
doc = frappe.get_doc("Integration Request", token) doc = frappe.get_doc("Integration Request", token)


doc.update_status(data, status) doc.update_status(data, status)



def get_redirect_uri(doc, token, payerid): def get_redirect_uri(doc, token, payerid):
data = json.loads(doc.data) data = json.loads(doc.data)


if data.get("subscription_details") or data.get("subscription_id"): if data.get("subscription_details") or data.get("subscription_id"):
return get_url("{0}.create_recurring_profile?token={1}&payerid={2}".format(api_path, token, payerid))
return get_url(
"{0}.create_recurring_profile?token={1}&payerid={2}".format(api_path, token, payerid)
)
else: else:
return get_url("{0}.confirm_payment?token={1}".format(api_path, token)) return get_url("{0}.confirm_payment?token={1}".format(api_path, token))



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


response = make_post_request(url, data=args) response = make_post_request(url, data=args)


@@ -370,9 +446,10 @@ def manage_recurring_payment_profile_status(profile_id, action, args, url):
# thus could not cancel the subscription. # thus could not cancel the subscription.
# thus raise an exception only if the error code is not equal to 11556 # 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")) frappe.throw(_("Failed while amending subscription"))



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def ipn_handler(): def ipn_handler():
try: try:
@@ -380,26 +457,32 @@ def ipn_handler():


validate_ipn_request(data) validate_ipn_request(data)


data.update({
"payment_gateway": "PayPal"
})
data.update({"payment_gateway": "PayPal"})


doc = frappe.get_doc({
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued"
}).insert(ignore_permissions=True)
doc = frappe.get_doc(
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued",
}
).insert(ignore_permissions=True)
frappe.db.commit() 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})
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: except frappe.InvalidStatusError:
pass pass
except Exception as e: except Exception as e:
frappe.log(frappe.log_error(title=e)) frappe.log(frappe.log_error(title=e))



def validate_ipn_request(data): def validate_ipn_request(data):
def _throw(): def _throw():
frappe.throw(_("In Valid Request"), exc=frappe.InvalidStatusError) frappe.throw(_("In Valid Request"), exc=frappe.InvalidStatusError)
@@ -410,16 +493,16 @@ def validate_ipn_request(data):
doc = frappe.get_doc("PayPal Settings") doc = frappe.get_doc("PayPal Settings")
params, url = doc.get_paypal_params_and_url() params, url = doc.get_paypal_params_and_url()


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


params = urlencode(params) params = urlencode(params)
res = make_post_request(url=url, data=params.encode("utf-8")) res = make_post_request(url=url, data=params.encode("utf-8"))


if res['ACK'][0] != 'Success':
if res["ACK"][0] != "Success":
_throw() _throw()



def handle_subscription_notification(doctype, docname): def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

+ 94
- 70
payments/payment_gateways/doctype/paytm_settings/paytm_settings.py Прегледај датотеку

@@ -3,112 +3,131 @@
# License: MIT. See LICENSE # License: MIT. See LICENSE


import json import json
import requests
from urllib.parse import urlencode from urllib.parse import urlencode


import requests
from paytmchecksum import generateSignature, verifySignature

import frappe import frappe
from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import get_url, call_hook_method, cint, flt, cstr
from frappe.integrations.utils import create_request_log, create_payment_gateway
from frappe.utils import get_request_site_address
from paytmchecksum import generateSignature, verifySignature
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 from frappe.utils.password import get_decrypted_password



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


def validate(self): def validate(self):
create_payment_gateway('Paytm')
call_hook_method('payment_gateway_enabled', gateway='Paytm')
create_payment_gateway("Paytm")
call_hook_method("payment_gateway_enabled", gateway="Paytm")


def validate_transaction_currency(self, currency): def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies: if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Paytm does not support transactions in currency '{0}'").format(currency))
frappe.throw(
_(
"Please select another payment method. Paytm does not support transactions in currency '{0}'"
).format(currency)
)


def get_payment_url(self, **kwargs): def get_payment_url(self, **kwargs):
'''Return payment url with several params'''
"""Return payment url with several params"""
# create unique order id by making it equal to the integration request # create unique order id by making it equal to the integration request
integration_request = create_request_log(kwargs, "Host", "Paytm") integration_request = create_request_log(kwargs, "Host", "Paytm")
kwargs.update(dict(order_id=integration_request.name)) kwargs.update(dict(order_id=integration_request.name))


return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs))) return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs)))



def get_paytm_config(): def get_paytm_config():
''' Returns 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')))
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): 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'
))
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: else:
paytm_config.update(dict(
url='https://securegw.paytm.in/order/process',
transaction_status_url='https://securegw.paytm.in/order/status',
))
paytm_config.update(
dict(
url="https://securegw.paytm.in/order/process",
transaction_status_url="https://securegw.paytm.in/order/status",
)
)
return paytm_config return paytm_config



def get_paytm_params(payment_details, order_id, paytm_config): def get_paytm_params(payment_details, order_id, paytm_config):


# initialize a dictionary # initialize a dictionary
paytm_params = dict() paytm_params = dict()


redirect_uri = get_request_site_address(True) + "/api/method/frappe.integrations.doctype.paytm_settings.paytm_settings.verify_transaction"

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,
})
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) checksum = generateSignature(paytm_params, paytm_config.merchant_key)


paytm_params.update({
"CHECKSUMHASH" : checksum
})
paytm_params.update({"CHECKSUMHASH": checksum})


return paytm_params return paytm_params



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def verify_transaction(**paytm_params): def verify_transaction(**paytm_params):
'''Verify checksum for received data in the callback and then verify the transaction'''
"""Verify checksum for received data in the callback and then verify the transaction"""
paytm_config = get_paytm_config() paytm_config = get_paytm_config()
is_valid_checksum = False is_valid_checksum = False


paytm_params.pop('cmd', None)
paytm_checksum = paytm_params.pop('CHECKSUMHASH', None)
paytm_params.pop("cmd", None)
paytm_checksum = paytm_params.pop("CHECKSUMHASH", None)


if paytm_params and paytm_config and paytm_checksum: if paytm_params and paytm_config and paytm_checksum:
# Verify checksum # Verify checksum
is_valid_checksum = verifySignature(paytm_params, paytm_config.merchant_key, paytm_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'])
if is_valid_checksum and paytm_params.get("RESPCODE") == "01":
verify_transaction_status(paytm_config, paytm_params["ORDERID"])
else: else:
frappe.respond_as_web_page("Payment Failed",
frappe.respond_as_web_page(
"Payment Failed",
"Transaction failed to complete. In case of any deductions, deducted amount will get refunded to your account.", "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')
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): 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
)
"""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) checksum = generateSignature(paytm_params, paytm_config.merchant_key)
paytm_params["CHECKSUMHASH"] = checksum paytm_params["CHECKSUMHASH"] = checksum
@@ -116,43 +135,48 @@ def verify_transaction_status(paytm_config, order_id):
post_data = json.dumps(paytm_params) post_data = json.dumps(paytm_params)
url = paytm_config.transaction_status_url url = paytm_config.transaction_status_url


response = requests.post(url, data = post_data, headers = {"Content-type": "application/json"}).json()
response = requests.post(url, data=post_data, headers={"Content-type": "application/json"}).json()
finalize_request(order_id, response) finalize_request(order_id, response)



def finalize_request(order_id, transaction_response): def finalize_request(order_id, transaction_response):
request = frappe.get_doc('Integration Request', order_id)
request = frappe.get_doc("Integration Request", order_id)
transaction_data = frappe._dict(json.loads(request.data)) 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
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_response["STATUS"] == "TXN_SUCCESS":
if transaction_data.reference_doctype and transaction_data.reference_docname: if transaction_data.reference_doctype and transaction_data.reference_docname:
custom_redirect_to = None custom_redirect_to = None
try: 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')
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: except Exception:
request.db_set('status', 'Failed')
request.db_set("status", "Failed")
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())


if custom_redirect_to: if custom_redirect_to:
redirect_to = custom_redirect_to redirect_to = custom_redirect_to


redirect_url = '/integrations/payment-success'
redirect_url = "/integrations/payment-success"
else: else:
request.db_set('status', 'Failed')
redirect_url = '/integrations/payment-failed'
request.db_set("status", "Failed")
redirect_url = "/integrations/payment-failed"


if redirect_to: if redirect_to:
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message: if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
redirect_url += "&" + urlencode({"redirect_message": redirect_message})

frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = redirect_url


frappe.local.response['type'] = 'redirect'
frappe.local.response['location'] = redirect_url


def get_gateway_controller(doctype, docname): def get_gateway_controller(doctype, docname):
reference_doc = frappe.get_doc(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
gateway_controller = frappe.db.get_value(
"Payment Gateway", reference_doc.payment_gateway, "gateway_controller"
)
return gateway_controller

+ 1
- 0
payments/payment_gateways/doctype/paytm_settings/test_paytm_settings.py Прегледај датотеку

@@ -4,5 +4,6 @@
# import frappe # import frappe
import unittest import unittest



class TestPaytmSettings(unittest.TestCase): class TestPaytmSettings(unittest.TestCase):
pass pass

+ 179
- 130
payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py Прегледај датотеку

@@ -60,17 +60,24 @@ For razorpay payment status is Authorized


""" """


import frappe
from frappe import _
import json
import hmac
import razorpay
import hashlib import hashlib
import hmac
import json
from urllib.parse import urlencode 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.model.document import Document
from frappe.utils import get_url, call_hook_method, cint, get_timestamp
from frappe.integrations.utils import (make_get_request, make_post_request, create_request_log,
create_payment_gateway)
from frappe.utils import call_hook_method, cint, get_timestamp, get_url



class RazorpaySettings(Document): class RazorpaySettings(Document):
supported_currencies = ["INR"] supported_currencies = ["INR"]
@@ -81,37 +88,45 @@ class RazorpaySettings(Document):
self.client = razorpay.Client(auth=(self.api_key, secret)) self.client = razorpay.Client(auth=(self.api_key, secret))


def validate(self): def validate(self):
create_payment_gateway('Razorpay')
call_hook_method('payment_gateway_enabled', gateway='Razorpay')
create_payment_gateway("Razorpay")
call_hook_method("payment_gateway_enabled", gateway="Razorpay")
if not self.flags.ignore_mandatory: if not self.flags.ignore_mandatory:
self.validate_razorpay_credentails() self.validate_razorpay_credentails()


def validate_razorpay_credentails(self): def validate_razorpay_credentails(self):
if self.api_key and self.api_secret: if self.api_key and self.api_secret:
try: try:
make_get_request(url="https://api.razorpay.com/v1/payments",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)))
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: except Exception:
frappe.throw(_("Seems API Key or API Secret is wrong !!!")) frappe.throw(_("Seems API Key or API Secret is wrong !!!"))


def validate_transaction_currency(self, currency): def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies: if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Razorpay does not support transactions in currency '{0}'").format(currency))
frappe.throw(
_(
"Please select another payment method. Razorpay does not support transactions in currency '{0}'"
).format(currency)
)


def setup_addon(self, settings, **kwargs): 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)
}
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/{0}/addons".format(kwargs.get('subscription_id'))
url = "https://api.razorpay.com/v1/subscriptions/{0}/addons".format(
kwargs.get("subscription_id")
)


try: try:
if not frappe.conf.converted_rupee_to_paisa: if not frappe.conf.converted_rupee_to_paisa:
@@ -122,52 +137,49 @@ class RazorpaySettings(Document):
url, url,
auth=(settings.api_key, settings.api_secret), auth=(settings.api_key, settings.api_secret),
data=json.dumps(addon), data=json.dumps(addon),
headers={
"content-type": "application/json"
}
headers={"content-type": "application/json"},
) )
if not resp.get('id'):
frappe.log_error(str(resp), 'Razorpay Failed while creating subscription')
if not resp.get("id"):
frappe.log_error(str(resp), "Razorpay Failed while creating subscription")
except: except:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
# failed # failed
pass pass


def setup_subscription(self, settings, **kwargs): 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
start_date = (
get_timestamp(kwargs.get("subscription_details").get("start_date"))
if kwargs.get("subscription_details").get("start_date")
else None
)


subscription_details = { 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")
"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: if start_date:
subscription_details['start_at'] = cint(start_date)
subscription_details["start_at"] = cint(start_date)


if kwargs.get('addons'):
if kwargs.get("addons"):
convert_rupee_to_paisa(**kwargs) convert_rupee_to_paisa(**kwargs)
subscription_details.update({
"addons": kwargs.get('addons')
})
subscription_details.update({"addons": kwargs.get("addons")})


try: try:
resp = make_post_request( resp = make_post_request(
"https://api.razorpay.com/v1/subscriptions", "https://api.razorpay.com/v1/subscriptions",
auth=(settings.api_key, settings.api_secret), auth=(settings.api_key, settings.api_secret),
data=json.dumps(subscription_details), data=json.dumps(subscription_details),
headers={
"content-type": "application/json"
}
headers={"content-type": "application/json"},
) )


if resp.get('status') == 'created':
kwargs['subscription_id'] = resp.get('id')
frappe.flags.status = 'created'
if resp.get("status") == "created":
kwargs["subscription_id"] = resp.get("id")
frappe.flags.status = "created"
return kwargs return kwargs
else: else:
frappe.log_error(str(resp), 'Razorpay Failed while creating subscription')
frappe.log_error(str(resp), "Razorpay Failed while creating subscription")


except: except:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
@@ -178,8 +190,8 @@ class RazorpaySettings(Document):
if not kwargs.get("subscription_id"): if not kwargs.get("subscription_id"):
kwargs = self.setup_subscription(settings, **kwargs) kwargs = self.setup_subscription(settings, **kwargs)


if frappe.flags.status !='created':
kwargs['subscription_id'] = None
if frappe.flags.status != "created":
kwargs["subscription_id"] = None


return kwargs return kwargs


@@ -191,25 +203,27 @@ class RazorpaySettings(Document):
# Creating Orders https://razorpay.com/docs/api/orders/ # Creating Orders https://razorpay.com/docs/api/orders/


# convert rupees to paisa # convert rupees to paisa
kwargs['amount'] *= 100
kwargs["amount"] *= 100


# Create integration log # Create integration log
integration_request = create_request_log(kwargs, "Host", "Razorpay") integration_request = create_request_log(kwargs, "Host", "Razorpay")


# Setup payment options # Setup payment options
payment_options = { payment_options = {
"amount": kwargs.get('amount'),
"currency": kwargs.get('currency', 'INR'),
"receipt": kwargs.get('receipt'),
"payment_capture": kwargs.get('payment_capture')
"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: if self.api_key and self.api_secret:
try: try:
order = make_post_request("https://api.razorpay.com/v1/orders",
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
return order # Order returned to be consumed by razorpay.js
data=payment_options,
)
order["integration_request"] = integration_request.name
return order # Order returned to be consumed by razorpay.js
except Exception: except Exception:
frappe.log(frappe.get_traceback()) frappe.log(frappe.get_traceback())
frappe.throw(_("Could not create razorpay order")) frappe.throw(_("Could not create razorpay order"))
@@ -219,14 +233,19 @@ class RazorpaySettings(Document):


try: try:
self.integration_request = frappe.get_doc("Integration Request", self.data.token) self.integration_request = frappe.get_doc("Integration Request", self.data.token)
self.integration_request.update_status(self.data, 'Queued')
self.integration_request.update_status(self.data, "Queued")
return self.authorize_payment() return self.authorize_payment()


except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) 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
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): def authorize_payment(self):
@@ -239,29 +258,30 @@ class RazorpaySettings(Document):
settings = self.get_settings(data) settings = self.get_settings(data)


try: try:
resp = make_get_request("https://api.razorpay.com/v1/payments/{0}"
.format(self.data.razorpay_payment_id), auth=(settings.api_key,
settings.api_secret))
resp = make_get_request(
"https://api.razorpay.com/v1/payments/{0}".format(self.data.razorpay_payment_id),
auth=(settings.api_key, settings.api_secret),
)


if resp.get("status") == "authorized": if resp.get("status") == "authorized":
self.integration_request.update_status(data, 'Authorized')
self.integration_request.update_status(data, "Authorized")
self.flags.status_changed_to = "Authorized" self.flags.status_changed_to = "Authorized"


if resp.get("status") == "captured": if resp.get("status") == "captured":
self.integration_request.update_status(data, 'Completed')
self.integration_request.update_status(data, "Completed")
self.flags.status_changed_to = "Completed" self.flags.status_changed_to = "Completed"


elif data.get('subscription_id'):
elif data.get("subscription_id"):
if resp.get("status") == "refunded": if resp.get("status") == "refunded":
# if subscription start date is in future then # if subscription start date is in future then
# razorpay refunds the amount after authorizing the card details # razorpay refunds the amount after authorizing the card details
# thus changing status to Verified # thus changing status to Verified


self.integration_request.update_status(data, 'Completed')
self.integration_request.update_status(data, "Completed")
self.flags.status_changed_to = "Verified" self.flags.status_changed_to = "Verified"


else: else:
frappe.log_error(str(resp), 'Razorpay Payment not authorized')
frappe.log_error(str(resp), "Razorpay Payment not authorized")


except: except:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
@@ -270,15 +290,16 @@ class RazorpaySettings(Document):


status = frappe.flags.integration_request.status_code status = frappe.flags.integration_request.status_code


redirect_to = data.get('redirect_to') or None
redirect_message = data.get('redirect_message') or None
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.flags.status_changed_to in ("Authorized", "Verified", "Completed"):
if self.data.reference_doctype and self.data.reference_docname: if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None custom_redirect_to = None
try: try:
frappe.flags.data = data 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)
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: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
@@ -286,31 +307,34 @@ class RazorpaySettings(Document):
if custom_redirect_to: if custom_redirect_to:
redirect_to = custom_redirect_to redirect_to = custom_redirect_to


redirect_url = 'payment-success?doctype={0}&docname={1}'.format(self.data.reference_doctype, self.data.reference_docname)
redirect_url = "payment-success?doctype={0}&docname={1}".format(
self.data.reference_doctype, self.data.reference_docname
)
else: else:
redirect_url = 'payment-failed'
redirect_url = "payment-failed"


if redirect_to: if redirect_to:
redirect_url += '&' + urlencode({'redirect_to': redirect_to})
redirect_url += "&" + urlencode({"redirect_to": redirect_to})
if redirect_message: if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
redirect_url += "&" + urlencode({"redirect_message": redirect_message})


return {
"redirect_to": redirect_url,
"status": status
}
return {"redirect_to": redirect_url, "status": status}


def get_settings(self, data): def get_settings(self, data):
settings = frappe._dict({
"api_key": self.api_key,
"api_secret": self.get_password(fieldname="api_secret", raise_exception=False)
})
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,
})
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 return settings


@@ -318,15 +342,16 @@ class RazorpaySettings(Document):
settings = self.get_settings({}) settings = self.get_settings({})


try: try:
resp = make_post_request("https://api.razorpay.com/v1/subscriptions/{0}/cancel"
.format(subscription_id), auth=(settings.api_key,
settings.api_secret))
resp = make_post_request(
"https://api.razorpay.com/v1/subscriptions/{0}/cancel".format(subscription_id),
auth=(settings.api_key, settings.api_secret),
)
except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())


def verify_signature(self, body, signature, key): def verify_signature(self, body, signature, key):
key = bytes(key, 'utf-8')
body = bytes(body, 'utf-8')
key = bytes(key, "utf-8")
body = bytes(body, "utf-8")


dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256) dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256)


@@ -334,22 +359,26 @@ class RazorpaySettings(Document):
result = hmac.compare_digest(generated_signature, signature) result = hmac.compare_digest(generated_signature, signature)


if not result: if not result:
frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError)
frappe.throw(_("Razorpay Signature Verification Failed"), exc=frappe.PermissionError)


return result return result



def capture_payment(is_sandbox=False, sanbox_response=None): 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.
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.
Note: Attempting to capture a payment whose status is not authorized will produce an error.
""" """
controller = frappe.get_doc("Razorpay Settings") controller = frappe.get_doc("Razorpay Settings")


for doc in frappe.get_all("Integration Request", filters={"status": "Authorized",
"integration_request_service": "Razorpay"}, fields=["name", "data"]):
for doc in frappe.get_all(
"Integration Request",
filters={"status": "Authorized", "integration_request_service": "Razorpay"},
fields=["name", "data"],
):
try: try:
if is_sandbox: if is_sandbox:
resp = sanbox_response resp = sanbox_response
@@ -357,12 +386,18 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
data = json.loads(doc.data) data = json.loads(doc.data)
settings = controller.get_settings(data) settings = controller.get_settings(data)


resp = make_get_request("https://api.razorpay.com/v1/payments/{0}".format(data.get("razorpay_payment_id")),
auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")})
resp = make_get_request(
"https://api.razorpay.com/v1/payments/{0}".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/{0}/capture".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/{0}/capture".format(data.get("razorpay_payment_id")),
auth=(settings.api_key, settings.api_secret),
data={"amount": data.get("amount")},
)


if resp.get("status") == "captured": if resp.get("status") == "captured":
frappe.db.set_value("Integration Request", doc.name, "status", "Completed") frappe.db.set_value("Integration Request", doc.name, "status", "Completed")
@@ -372,7 +407,7 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
doc.status = "Failed" doc.status = "Failed"
doc.error = frappe.get_traceback() doc.error = frappe.get_traceback()
doc.save() doc.save()
frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
frappe.log_error(doc.error, "{0} Failed".format(doc.name))




@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
@@ -380,6 +415,7 @@ def get_api_key():
controller = frappe.get_doc("Razorpay Settings") controller = frappe.get_doc("Razorpay Settings")
return controller.api_key return controller.api_key



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_order(doctype, docname): def get_order(doctype, docname):
# Order returned to be consumed by razorpay.js # Order returned to be consumed by razorpay.js
@@ -391,6 +427,7 @@ def get_order(doctype, docname):
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")) frappe.throw(_("Could not create Razorpay order. Please contact Administrator"))



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def order_payment_success(integration_request, params): def order_payment_success(integration_request, params):
"""Called by razorpay.js on order payment success, the params """Called by razorpay.js on order payment success, the params
@@ -398,8 +435,8 @@ def order_payment_success(integration_request, params):
that is updated in the data field of integration request that is updated in the data field of integration request


Args: Args:
integration_request (string): Name for integration request doc
params (string): Params to be updated for integration request.
integration_request (string): Name for integration request doc
params (string): Params to be updated for integration request.
""" """
params = json.loads(params) params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request) integration = frappe.get_doc("Integration Request", integration_request)
@@ -418,25 +455,28 @@ def order_payment_success(integration_request, params):
# Authorize payment # Authorize payment
controller.authorize_payment() controller.authorize_payment()



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def order_payment_failure(integration_request, params): def order_payment_failure(integration_request, params):
"""Called by razorpay.js on failure """Called by razorpay.js on failure


Args: Args:
integration_request (TYPE): Description
params (TYPE): error data to be updated
integration_request (TYPE): Description
params (TYPE): error data to be updated
""" """
frappe.log_error(params, 'Razorpay Payment Failure')
frappe.log_error(params, "Razorpay Payment Failure")
params = json.loads(params) params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request) integration = frappe.get_doc("Integration Request", integration_request)
integration.update_status(params, integration.status) integration.update_status(params, integration.status)



def convert_rupee_to_paisa(**kwargs): def convert_rupee_to_paisa(**kwargs):
for addon in kwargs.get('addons'):
addon['item']['amount'] *= 100
for addon in kwargs.get("addons"):
addon["item"]["amount"] *= 100


frappe.conf.converted_rupee_to_paisa = True frappe.conf.converted_rupee_to_paisa = True



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def razorpay_subscription_callback(): def razorpay_subscription_callback():
try: try:
@@ -444,44 +484,53 @@ def razorpay_subscription_callback():


validate_payment_callback(data) validate_payment_callback(data)


data.update({
"payment_gateway": "Razorpay"
})
data.update({"payment_gateway": "Razorpay"})


doc = frappe.get_doc({
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued"
}).insert(ignore_permissions=True)
doc = frappe.get_doc(
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued",
}
).insert(ignore_permissions=True)
frappe.db.commit() 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})
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: except frappe.InvalidStatusError:
pass pass
except Exception as e: except Exception as e:
frappe.log(frappe.log_error(title=e)) frappe.log(frappe.log_error(title=e))



def validate_payment_callback(data): def validate_payment_callback(data):
def _throw(): def _throw():
frappe.throw(_("Invalid Subscription"), exc=frappe.InvalidStatusError) frappe.throw(_("Invalid Subscription"), exc=frappe.InvalidStatusError)


subscription_id = data.get('payload').get("subscription").get("entity").get("id")
subscription_id = data.get("payload").get("subscription").get("entity").get("id")


if not(subscription_id):
if not (subscription_id):
_throw() _throw()


controller = frappe.get_doc("Razorpay Settings") controller = frappe.get_doc("Razorpay Settings")


settings = controller.get_settings(data) settings = controller.get_settings(data)


resp = make_get_request("https://api.razorpay.com/v1/subscriptions/{0}".format(subscription_id),
auth=(settings.api_key, settings.api_secret))
resp = make_get_request(
"https://api.razorpay.com/v1/subscriptions/{0}".format(subscription_id),
auth=(settings.api_key, settings.api_secret),
)


if resp.get("status") != "active": if resp.get("status") != "active":
_throw() _throw()



def handle_subscription_notification(doctype, docname): def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname) call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

+ 192
- 42
payments/payment_gateways/doctype/stripe_settings/stripe_settings.py Прегледај датотеку

@@ -2,41 +2,171 @@
# Copyright (c) 2017, Frappe Technologies and contributors # Copyright (c) 2017, Frappe Technologies and contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE


from urllib.parse import urlencode

import frappe import frappe
from frappe.model.document import Document
from frappe import _ from frappe import _
from urllib.parse import urlencode
from frappe.utils import get_url, call_hook_method, cint, flt
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
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): class StripeSettings(Document):
supported_currencies = [ 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"
"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 = { 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
"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): 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)
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: if not self.flags.ignore_mandatory:
self.validate_stripe_credentails() self.validate_stripe_credentails()


def validate_stripe_credentails(self): def validate_stripe_credentails(self):
if self.publishable_key and self.secret_key: if self.publishable_key and self.secret_key:
header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
header = {
"Authorization": "Bearer {0}".format(
self.get_password(fieldname="secret_key", raise_exception=False)
)
}
try: try:
make_get_request(url="https://api.stripe.com/v1/charges", headers=header) make_get_request(url="https://api.stripe.com/v1/charges", headers=header)
except Exception: except Exception:
@@ -44,19 +174,27 @@ class StripeSettings(Document):


def validate_transaction_currency(self, currency): def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies: if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
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): def validate_minimum_transaction_amount(self, currency, amount):
if currency in self.currency_wise_minimum_charge_amount: if currency in self.currency_wise_minimum_charge_amount:
if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0): 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)))
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): def get_payment_url(self, **kwargs):
return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs))) return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs)))


def create_request(self, data): def create_request(self, data):
import stripe import stripe

self.data = frappe._dict(data) self.data = frappe._dict(data)
stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False) stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False)
stripe.default_http_client = stripe.http_client.RequestsClient() stripe.default_http_client = stripe.http_client.RequestsClient()
@@ -67,65 +205,77 @@ class StripeSettings(Document):


except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) 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
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): def create_charge_on_stripe(self):
import stripe import stripe

try: 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)
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: if charge.captured == True:
self.integration_request.db_set('status', 'Completed', update_modified=False)
self.integration_request.db_set("status", "Completed", update_modified=False)
self.flags.status_changed_to = "Completed" self.flags.status_changed_to = "Completed"


else: else:
frappe.log_error(charge.failure_message, 'Stripe Payment not completed')
frappe.log_error(charge.failure_message, "Stripe Payment not completed")


except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())


return self.finalize_request() return self.finalize_request()



def finalize_request(self): def finalize_request(self):
redirect_to = self.data.get('redirect_to') or None
redirect_message = self.data.get('redirect_message') or None
redirect_to = self.data.get("redirect_to") or None
redirect_message = self.data.get("redirect_message") or None
status = self.integration_request.status status = self.integration_request.status


if self.flags.status_changed_to == "Completed": if self.flags.status_changed_to == "Completed":
if self.data.reference_doctype and self.data.reference_docname: if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None custom_redirect_to = None
try: 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)
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: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())


if custom_redirect_to: if custom_redirect_to:
redirect_to = custom_redirect_to redirect_to = custom_redirect_to


redirect_url = 'payment-success'
redirect_url = "payment-success"


if self.redirect_url: if self.redirect_url:
redirect_url = self.redirect_url redirect_url = self.redirect_url
redirect_to = None redirect_to = None
else: else:
redirect_url = 'payment-failed'
redirect_url = "payment-failed"


if redirect_to: if redirect_to:
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
redirect_url += "?" + urlencode({"redirect_to": redirect_to})
if redirect_message: if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
redirect_url += "&" + urlencode({"redirect_message": redirect_message})

return {"redirect_to": redirect_url, "status": status}


return {
"redirect_to": redirect_url,
"status": status
}


def get_gateway_controller(doctype, docname): def get_gateway_controller(doctype, docname):
reference_doc = frappe.get_doc(doctype, docname) reference_doc = frappe.get_doc(doctype, docname)
gateway_controller = frappe.db.get_value("Payment Gateway", reference_doc.payment_gateway, "gateway_controller")
gateway_controller = frappe.db.get_value(
"Payment Gateway", reference_doc.payment_gateway, "gateway_controller"
)
return gateway_controller return gateway_controller

+ 1
- 0
payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.py Прегледај датотеку

@@ -3,5 +3,6 @@
# License: MIT. See LICENSE # License: MIT. See LICENSE
import unittest import unittest



class TestStripeSettings(unittest.TestCase): class TestStripeSettings(unittest.TestCase):
pass pass

+ 2
- 1
payments/payments/doctype/payment_gateway/payment_gateway.py Прегледај датотеку

@@ -5,5 +5,6 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document



class PaymentGateway(Document): class PaymentGateway(Document):
pass
pass

+ 3
- 1
payments/payments/doctype/payment_gateway/test_payment_gateway.py Прегледај датотеку

@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
import frappe
import unittest import unittest


import frappe

# test_records = frappe.get_test_records('Payment Gateway') # test_records = frappe.get_test_records('Payment Gateway')



class TestPaymentGateway(unittest.TestCase): class TestPaymentGateway(unittest.TestCase):
pass pass

+ 29
- 12
payments/templates/pages/braintree_checkout.py Прегледај датотеку

@@ -1,16 +1,30 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE


import json

import frappe import frappe
from frappe import _ from frappe import _
from frappe.integrations.doctype.braintree_settings.braintree_settings import (
get_client_token,
get_gateway_controller,
)
from frappe.utils import flt from frappe.utils import flt
import json
from frappe.integrations.doctype.braintree_settings.braintree_settings import get_client_token, get_gateway_controller


no_cache = 1 no_cache = 1


expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
'payer_name', 'payer_email', 'order_id', 'currency')
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
"currency",
)



def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
@@ -22,26 +36,29 @@ def get_context(context):


context.client_token = get_client_token(context.reference_docname) context.client_token = get_client_token(context.reference_docname)


context['amount'] = flt(context['amount'])
context["amount"] = flt(context["amount"])


gateway_controller = get_gateway_controller(context.reference_docname) gateway_controller = get_gateway_controller(context.reference_docname)
context['header_img'] = frappe.db.get_value("Braintree Settings", gateway_controller, "header_img")
context["header_img"] = frappe.db.get_value(
"Braintree Settings", gateway_controller, "header_img"
)


else: 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.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 frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect raise frappe.Redirect



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def make_payment(payload_nonce, data, reference_doctype, reference_docname): def make_payment(payload_nonce, data, reference_doctype, reference_docname):
data = json.loads(data) data = json.loads(data)


data.update({
"payload_nonce": payload_nonce
})
data.update({"payload_nonce": payload_nonce})


gateway_controller = get_gateway_controller(reference_docname) 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() frappe.db.commit()
return data return data

+ 1
- 0
payments/templates/pages/payment_cancel.py Прегледај датотеку

@@ -3,6 +3,7 @@


import frappe import frappe



def get_context(context): def get_context(context):
token = frappe.local.form_dict.token token = frappe.local.form_dict.token




+ 6
- 5
payments/templates/pages/payment_success.py Прегледај датотеку

@@ -2,13 +2,14 @@
# License: MIT. See LICENSE # License: MIT. See LICENSE


import frappe import frappe

no_cache = True no_cache = True



def get_context(context): def get_context(context):
token = frappe.local.form_dict.token
doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.docname)
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 = ""
if hasattr(doc, "get_payment_success_message"):
context.payment_message = doc.get_payment_success_message() context.payment_message = doc.get_payment_success_message()


+ 15
- 7
payments/templates/pages/paytm_checkout.py Прегледај датотеку

@@ -1,16 +1,21 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
import json

import frappe import frappe
from frappe import _ from frappe import _
import json
from frappe.integrations.doctype.paytm_settings.paytm_settings import get_paytm_params, get_paytm_config
from frappe.integrations.doctype.paytm_settings.paytm_settings import (
get_paytm_config,
get_paytm_params,
)



def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
paytm_config = get_paytm_config() paytm_config = get_paytm_config()


try: try:
doc = frappe.get_doc("Integration Request", frappe.form_dict['order_id'])
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.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config)


@@ -18,9 +23,12 @@ def get_context(context):


except Exception: except Exception:
frappe.log_error() frappe.log_error()
frappe.redirect_to_message(_('Invalid Token'),
_('Seems token you are using is invalid!'),
http_status_code=400, indicator_color='red')
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 frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
raise frappe.Redirect

+ 37
- 19
payments/templates/pages/razorpay_checkout.py Прегледај датотеку

@@ -1,39 +1,54 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
import json

import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, cint
import json
from frappe.utils import cint, flt


no_cache = 1 no_cache = 1


expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
'payer_name', 'payer_email', 'order_id')
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
)



def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
context.api_key = get_api_key() context.api_key = get_api_key()


try: try:
doc = frappe.get_doc("Integration Request", frappe.form_dict['token'])
doc = frappe.get_doc("Integration Request", frappe.form_dict["token"])
payment_details = json.loads(doc.data) payment_details = json.loads(doc.data)


for key in expected_keys: for key in expected_keys:
context[key] = payment_details[key] 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 ''
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: 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.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 frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect raise frappe.Redirect



def get_api_key(): def get_api_key():
api_key = frappe.db.get_value("Razorpay Settings", None, "api_key") api_key = frappe.db.get_value("Razorpay Settings", None, "api_key")
if cint(frappe.form_dict.get("use_sandbox")): if cint(frappe.form_dict.get("use_sandbox")):
@@ -41,6 +56,7 @@ def get_api_key():


return api_key return api_key



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token): def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token):
data = {} data = {}
@@ -48,13 +64,15 @@ def make_payment(razorpay_payment_id, options, reference_doctype, reference_docn
if isinstance(options, str): if isinstance(options, str):
data = json.loads(options) data = json.loads(options)


data.update({
"razorpay_payment_id": razorpay_payment_id,
"reference_docname": reference_docname,
"reference_doctype": reference_doctype,
"token": token
})
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)
data = frappe.get_doc("Razorpay Settings").create_request(data)
frappe.db.commit() frappe.db.commit()
return data return data

+ 34
- 17
payments/templates/pages/stripe_checkout.py Прегледај датотеку

@@ -1,15 +1,26 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
import json

import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint, fmt_money
import json
from frappe.integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller from frappe.integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller
from frappe.utils import cint, fmt_money


no_cache = 1 no_cache = 1


expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
'payer_name', 'payer_email', 'order_id', 'currency')
expected_keys = (
"amount",
"title",
"description",
"reference_doctype",
"reference_docname",
"payer_name",
"payer_email",
"order_id",
"currency",
)



def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
@@ -23,20 +34,25 @@ def get_context(context):
context.publishable_key = get_api_key(context.reference_docname, gateway_controller) context.publishable_key = get_api_key(context.reference_docname, gateway_controller)
context.image = get_header_image(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'])
context["amount"] = fmt_money(amount=context["amount"], currency=context["currency"])


if is_a_subscription(context.reference_doctype, context.reference_docname): if is_a_subscription(context.reference_doctype, context.reference_docname):
payment_plan = frappe.db.get_value(context.reference_doctype, context.reference_docname, "payment_plan")
payment_plan = frappe.db.get_value(
context.reference_doctype, context.reference_docname, "payment_plan"
)
recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence") recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence")


context['amount'] = context['amount'] + " " + _(recurrence)
context["amount"] = context["amount"] + " " + _(recurrence)


else: 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.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 frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect raise frappe.Redirect



def get_api_key(doc, gateway_controller): def get_api_key(doc, gateway_controller):
publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key") publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key")
if cint(frappe.form_dict.get("use_sandbox")): if cint(frappe.form_dict.get("use_sandbox")):
@@ -44,31 +60,32 @@ def get_api_key(doc, gateway_controller):


return publishable_key return publishable_key



def get_header_image(doc, gateway_controller): def get_header_image(doc, gateway_controller):
header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img") header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img")


return header_image return header_image



@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None): def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None):
data = json.loads(data) data = json.loads(data)


data.update({
"stripe_token_id": stripe_token_id
})
data.update({"stripe_token_id": stripe_token_id})


gateway_controller = get_gateway_controller(reference_doctype,reference_docname)
gateway_controller = get_gateway_controller(reference_doctype, reference_docname)


if is_a_subscription(reference_doctype, reference_docname): if is_a_subscription(reference_doctype, reference_docname):
reference = frappe.get_doc(reference_doctype, reference_docname) reference = frappe.get_doc(reference_doctype, reference_docname)
data = reference.create_subscription("stripe", gateway_controller, data)
data = reference.create_subscription("stripe", gateway_controller, data)
else: else:
data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data)
data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data)


frappe.db.commit() frappe.db.commit()
return data return data



def is_a_subscription(reference_doctype, reference_docname): def is_a_subscription(reference_doctype, reference_docname):
if not frappe.get_meta(reference_doctype).has_field('is_a_subscription'):
if not frappe.get_meta(reference_doctype).has_field("is_a_subscription"):
return False return False
return frappe.db.get_value(reference_doctype, reference_docname, "is_a_subscription")
return frappe.db.get_value(reference_doctype, reference_docname, "is_a_subscription")

Loading…
Откажи
Сачувај