Ver código fonte

Merge branch 'master' into hotfix

pull/2/head
Sagar Vora 6 anos atrás
pai
commit
e433fd4ca3
30 arquivos alterados com 1675 adições e 158 exclusões
  1. +0
    -0
      payments/payment_gateways/doctype/braintree_settings/__init__.py
  2. +6
    -0
      payments/payment_gateways/doctype/braintree_settings/braintree_settings.js
  3. +273
    -0
      payments/payment_gateways/doctype/braintree_settings/braintree_settings.json
  4. +139
    -0
      payments/payment_gateways/doctype/braintree_settings/braintree_settings.py
  5. +23
    -0
      payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.js
  6. +9
    -0
      payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.py
  7. +1
    -1
      payments/payment_gateways/doctype/paypal_settings/paypal_settings.json
  8. +205
    -39
      payments/payment_gateways/doctype/paypal_settings/paypal_settings.py
  9. +1
    -1
      payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json
  10. +184
    -8
      payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py
  11. +200
    -5
      payments/payment_gateways/doctype/stripe_settings/stripe_settings.json
  12. +33
    -28
      payments/payment_gateways/doctype/stripe_settings/stripe_settings.py
  13. +23
    -0
      payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.js
  14. +9
    -0
      payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.py
  15. +65
    -2
      payments/payments/doctype/payment_gateway/payment_gateway.json
  16. +23
    -0
      payments/payments/doctype/payment_gateway/test_payment_gateway.js
  17. +55
    -0
      payments/templates/includes/braintree_checkout.js
  18. +1
    -0
      payments/templates/includes/razorpay_checkout.js
  19. +81
    -43
      payments/templates/includes/stripe_checkout.js
  20. +54
    -0
      payments/templates/pages/braintree_checkout.html
  21. +48
    -0
      payments/templates/pages/braintree_checkout.py
  22. +21
    -0
      payments/templates/pages/gcalendar-success.html
  23. +1
    -0
      payments/templates/pages/payment-cancel.html
  24. +1
    -0
      payments/templates/pages/payment-failed.html
  25. +20
    -6
      payments/templates/pages/payment-success.html
  26. +8
    -1
      payments/templates/pages/payment_success.py
  27. +3
    -1
      payments/templates/pages/razorpay_checkout.py
  28. +113
    -0
      payments/templates/pages/stripe_checkout.css
  29. +45
    -15
      payments/templates/pages/stripe_checkout.html
  30. +30
    -8
      payments/templates/pages/stripe_checkout.py

+ 0
- 0
payments/payment_gateways/doctype/braintree_settings/__init__.py Ver arquivo


+ 6
- 0
payments/payment_gateways/doctype/braintree_settings/braintree_settings.js Ver arquivo

@@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Braintree Settings', {

});

+ 273
- 0
payments/payment_gateways/doctype/braintree_settings/braintree_settings.json Ver arquivo

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

+ 139
- 0
payments/payment_gateways/doctype/braintree_settings/braintree_settings.py Ver arquivo

@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
import braintree
from frappe import _
from six.moves.urllib.parse import urlencode
from frappe.utils import get_url, call_hook_method
from frappe.integrations.utils import create_request_log, create_payment_gateway

class BraintreeSettings(Document):
supported_currencies = [
"AED","AMD","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BIF","BMD","BND","BOB",
"BRL","BSD","BWP","BYN","BZD","CAD","CHF","CLP","CNY","COP","CRC","CVE","CZK","DJF","DKK",
"DOP","DZD","EGP","ETB","EUR","FJD","FKP","GBP","GEL","GHS","GIP","GMD","GNF","GTQ","GYD",
"HKD","HNL","HRK","HTG","HUF","IDR","ILS","INR","ISK","JMD","JPY","KES","KGS","KHR","KMF",
"KRW","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LTL","MAD","MDL","MKD","MNT","MOP","MUR",
"MVR","MWK","MXN","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","PAB","PEN","PGK","PHP",
"PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SAR","SBD","SCR","SEK","SGD","SHP","SLL",
"SOS","SRD","STD","SVC","SYP","SZL","THB","TJS","TOP","TRY","TTD","TWD","TZS","UAH","UGX",
"USD","UYU","UZS","VEF","VND","VUV","WST","XAF","XCD","XOF","XPF","YER","ZAR","ZMK","ZWD"
]

def validate(self):
if not self.flags.ignore_mandatory:
self.configure_braintree()

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

def configure_braintree(self):
if self.use_sandbox:
environment = 'sandbox'
else:
environment = 'production'

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

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

def get_payment_url(self, **kwargs):
return get_url("./integrations/braintree_checkout?{0}".format(urlencode(kwargs)))

def create_payment_request(self, data):
self.data = frappe._dict(data)

try:
self.integration_request = create_request_log(self.data, "Host", "Braintree")
return self.create_charge_on_braintree()

except Exception:
frappe.log_error(frappe.get_traceback())
return{
"redirect_to": frappe.redirect_to_message(_('Server Error'), _("There seems to be an issue with the server's braintree configuration. Don't worry, in case of failure, the amount will get refunded to your account.")),
"status": 401
}

def create_charge_on_braintree(self):
self.configure_braintree()

redirect_to = self.data.get('redirect_to') or None
redirect_message = self.data.get('redirect_message') or None

result = braintree.Transaction.sale({
"amount": self.data.amount,
"payment_method_nonce": self.data.payload_nonce,
"options": {
"submit_for_settlement": True
}
})

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

elif result.transaction:
self.integration_request.db_set('status', 'Failed', update_modified=False)
error_log = frappe.log_error("code: " + str(result.transaction.processor_response_code) + " | text: " + str(result.transaction.processor_response_text), "Braintree Payment Error")
self.integration_request.db_set('error', error_log.error, update_modified=False)
else:
self.integration_request.db_set('status', 'Failed', update_modified=False)
for error in result.errors.deep_errors:
error_log = frappe.log_error("code: " + str(error.code) + " | message: " + str(error.message), "Braintree Payment Error")
self.integration_request.db_set('error', error_log.error, update_modified=False)

if self.flags.status_changed_to == "Completed":
status = 'Completed'
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
try:
custom_redirect_to = frappe.get_doc(self.data.reference_doctype,
self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to)
braintree_success_page = frappe.get_hooks('braintree_success_page')
if braintree_success_page:
custom_redirect_to = frappe.get_attr(braintree_success_page[-1])(self.data)
except Exception:
frappe.log_error(frappe.get_traceback())

if custom_redirect_to:
redirect_to = custom_redirect_to

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

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

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

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

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

return braintree.ClientToken.generate()

+ 23
- 0
payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.js Ver arquivo

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Braintree Settings", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially([
// insert a new Braintree Setting
() => frappe.tests.make('Braintree Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 9
- 0
payments/payment_gateways/doctype/braintree_settings/test_braintree_settings.py Ver arquivo

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import unittest

class TestBraintreeSettings(unittest.TestCase):
pass

+ 1
- 1
payments/payment_gateways/doctype/paypal_settings/paypal_settings.json Ver arquivo

@@ -158,7 +158,7 @@
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 1, "in_create": 1,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,


+ 205
- 39
payments/payment_gateways/doctype/paypal_settings/paypal_settings.py Ver arquivo

@@ -28,7 +28,15 @@ Example:
"payer_name": "Nuran Verkleij", "payer_name": "Nuran Verkleij",
"order_id": "111", "order_id": "111",
"currency": "USD", "currency": "USD",
"payment_gateway": "Razorpay"
"payment_gateway": "Razorpay",
"subscription_details": {
"plan_id": "plan_12313", # if Required
"start_date": "2018-08-30",
"billing_period": "Month" #(Day, Week, SemiMonth, Month, Year),
"billing_frequency": 1,
"customer_notify": 1,
"upfront_amount": 1000
}
} }


# redirect the user to this url # redirect the user to this url
@@ -58,11 +66,15 @@ More Details:
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json import json
import pytz
from frappe import _ from frappe import _
from frappe.utils import get_url, call_hook_method, cint
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
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.integrations.utils import create_request_log, make_post_request, create_payment_gateway
from frappe.utils import get_url, call_hook_method, cint, get_datetime


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", supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN",
@@ -124,7 +136,7 @@ class PayPalSettings(Document):
def get_payment_url(self, **kwargs): def get_payment_url(self, **kwargs):
setattr(self, "use_sandbox", cint(kwargs.get("use_sandbox", 0))) setattr(self, "use_sandbox", cint(kwargs.get("use_sandbox", 0)))


response = self.execute_set_express_checkout(kwargs["amount"], kwargs["currency"])
response = self.execute_set_express_checkout(**kwargs)


if self.paypal_sandbox or self.use_sandbox: if self.paypal_sandbox or self.use_sandbox:
return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}" return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}"
@@ -135,30 +147,71 @@ class PayPalSettings(Document):
"token": response.get("TOKEN")[0], "token": response.get("TOKEN")[0],
"correlation_id": response.get("CORRELATIONID")[0] "correlation_id": response.get("CORRELATIONID")[0]
}) })

self.integration_request = create_request_log(kwargs, "Remote", "PayPal", response.get("TOKEN")[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, amount, currency):
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({ params.update({
"METHOD": "SetExpressCheckout", "METHOD": "SetExpressCheckout",
"returnUrl": get_url("{0}.get_express_checkout_details".format(api_path)),
"cancelUrl": get_url("/payment-cancel"),
"PAYMENTREQUEST_0_PAYMENTACTION": "SALE", "PAYMENTREQUEST_0_PAYMENTACTION": "SALE",
"PAYMENTREQUEST_0_AMT": amount,
"PAYMENTREQUEST_0_CURRENCYCODE": currency.upper(),
"returnUrl": get_url("/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings.get_express_checkout_details"),
"cancelUrl": get_url("/payment-cancel")
"PAYMENTREQUEST_0_AMT": kwargs['amount'],
"PAYMENTREQUEST_0_CURRENCYCODE": kwargs['currency'].upper()
}) })


params = urlencode(params)
if kwargs.get('subscription_details'):
self.configure_recurring_payments(params, kwargs)


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

if response.get("ACK")[0] != "Success": if response.get("ACK")[0] != "Success":
frappe.throw(_("Looks like something is wrong with this site's Paypal configuration.")) frappe.throw(_("Looks like something is wrong with this site's Paypal configuration."))


return response return response


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

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

def get_paypal_and_transaction_details(token):
doc = frappe.get_doc("PayPal Settings")
doc.setup_sandbox_env(token)
params, url = doc.get_paypal_params_and_url()

integration_request = frappe.get_doc("Integration Request", token)
data = json.loads(integration_request.data)

return data, params, url

def setup_redirect(data, redirect_url, custom_redirect_to=None, redirect=True):
redirect_to = data.get('redirect_to') or None
redirect_message = data.get('redirect_message') or None

if custom_redirect_to:
redirect_to = custom_redirect_to

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

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

@frappe.whitelist(allow_guest=True, xss_safe=True) @frappe.whitelist(allow_guest=True, xss_safe=True)
def get_express_checkout_details(token): def get_express_checkout_details(token):
try: try:
@@ -181,14 +234,14 @@ def get_express_checkout_details(token):


return return


doc = frappe.get_doc("Integration Request", token)
update_integration_request_status(token, { update_integration_request_status(token, {
"payerid": response.get("PAYERID")[0], "payerid": response.get("PAYERID")[0],
"payer_email": response.get("EMAIL")[0] "payer_email": response.get("EMAIL")[0]
}, "Authorized")
}, "Authorized", doc=doc)


frappe.local.response["type"] = "redirect" frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url( \
"/api/method/frappe.integrations.doctype.paypal_settings.paypal_settings.confirm_payment?token={0}".format(token))
frappe.local.response["location"] = get_redirect_uri(doc, token, response.get("PAYERID")[0])


except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
@@ -196,19 +249,9 @@ def get_express_checkout_details(token):
@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:
redirect = True
status_changed_to, redirect_to = None, None
custom_redirect_to = None
data, params, url = get_paypal_and_transaction_details(token)


doc = frappe.get_doc("PayPal Settings")
doc.setup_sandbox_env(token)

integration_request = frappe.get_doc("Integration Request", token)
data = json.loads(integration_request.data)

redirect_to = data.get('redirect_to') or None
redirect_message = data.get('redirect_message') or None

params, url = doc.get_paypal_params_and_url()
params.update({ params.update({
"METHOD": "DoExpressCheckoutPayment", "METHOD": "DoExpressCheckoutPayment",
"PAYERID": data.get("payerid"), "PAYERID": data.get("payerid"),
@@ -231,25 +274,148 @@ def confirm_payment(token):
data.get("reference_docname")).run_method("on_payment_authorized", "Completed") data.get("reference_docname")).run_method("on_payment_authorized", "Completed")
frappe.db.commit() frappe.db.commit()


if custom_redirect_to:
redirect_to = custom_redirect_to

redirect_url = '/integrations/payment-success'
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"


if redirect_to:
redirect_url += '?' + urlencode({'redirect_to': redirect_to})
if redirect_message:
redirect_url += '&' + urlencode({'redirect_message': redirect_message})
setup_redirect(data, redirect_url, custom_redirect_to)

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

@frappe.whitelist(allow_guest=True, xss_safe=True)
def create_recurring_profile(token, payerid):
try:
custom_redirect_to = None
updating = False
data, params, url = get_paypal_and_transaction_details(token)

addons = data.get("addons")
subscription_details = data.get("subscription_details")

if data.get('subscription_id') and addons:
updating = True
manage_recurring_payment_profile_status(data['subscription_id'], 'Cancel', params, url)

params.update({
"METHOD": "CreateRecurringPaymentsProfile",
"PAYERID": payerid,
"TOKEN": token,
"DESC": data.get("description"),
"BILLINGPERIOD": subscription_details.get("billing_period"),
"BILLINGFREQUENCY": subscription_details.get("billing_frequency"),
"AMT": data.get("amount") if data.get("subscription_amount") == data.get("amount") else data.get("subscription_amount"),
"CURRENCYCODE": data.get("currency").upper(),
"INITAMT": data.get("upfront_amount")
})

status_changed_to = 'Completed' if data.get("starting_immediately") or updating else 'Verified'


# this is done so that functions called via hooks can update flags.redirect_to
if redirect:
frappe.local.response["type"] = "redirect"
frappe.local.response["location"] = get_url(redirect_url)
starts_at = get_datetime(subscription_details.get("start_date")) or frappe.utils.now_datetime()
starts_at = starts_at.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone())).astimezone(pytz.utc)

#"PROFILESTARTDATE": datetime.utcfromtimestamp(get_timestamp(starts_at)).isoformat()
params.update({
"PROFILESTARTDATE": starts_at.isoformat()
})

response = make_post_request(url, data=params)

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

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

frappe.flags.data = data
custom_redirect_to = frappe.get_doc(data.get("reference_doctype"),
data.get("reference_docname")).run_method("on_payment_authorized", status_changed_to)
frappe.db.commit()

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

setup_redirect(data, redirect_url, custom_redirect_to)


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


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

doc.update_status(data, status)

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

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

response = make_post_request(url, data=args)

if response.get("ACK")[0] != "Success":
frappe.throw(_("Failed while amending subscription"))

@frappe.whitelist(allow_guest=True)
def ipn_handler():
try:
data = frappe.local.form_dict

validate_ipn_request(data)

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

doc = frappe.get_doc({
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued"
}).insert(ignore_permissions=True)
frappe.db.commit()

frappe.enqueue(method='frappe.integrations.doctype.paypal_settings.paypal_settings.handle_subscription_notification',
queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name})

except frappe.InvalidStatusError:
pass
except Exception as e:
frappe.log(frappe.log_error(title=e))

def validate_ipn_request(data):
def _throw():
frappe.throw(_("In Valid Request"), exc=frappe.InvalidStatusError)

if not data.get("recurring_payment_id"):
_throw()

doc = frappe.get_doc("PayPal Settings")
params, url = doc.get_paypal_params_and_url()

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

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

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

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

+ 1
- 1
payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.json Ver arquivo

@@ -101,7 +101,7 @@
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 1, "in_create": 1,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,


+ 184
- 8
payments/payment_gateways/doctype/razorpay_settings/razorpay_settings.py Ver arquivo

@@ -28,7 +28,15 @@ Example:
"payer_name": "Nuran Verkleij", "payer_name": "Nuran Verkleij",
"order_id": "111", "order_id": "111",
"currency": "INR", "currency": "INR",
"payment_gateway": "Razorpay"
"payment_gateway": "Razorpay",
"subscription_details": {
"plan_id": "plan_12313", # if Required
"start_date": "2018-08-30",
"billing_period": "Month" #(Day, Week, Month, Year),
"billing_frequency": 1,
"customer_notify": 1,
"upfront_amount": 1000
}
} }


# Redirect the user to this url # Redirect the user to this url
@@ -58,8 +66,9 @@ from frappe import _
import json import json
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import get_url, call_hook_method, cint
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
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)


class RazorpaySettings(Document): class RazorpaySettings(Document):
supported_currencies = ["INR"] supported_currencies = ["INR"]
@@ -82,6 +91,89 @@ class RazorpaySettings(Document):
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):
"""
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'))

try:
if not frappe.conf.converted_rupee_to_paisa:
convert_rupee_to_paisa(**kwargs)

for addon in kwargs.get("addons"):
resp = make_post_request(
url,
auth=(settings.api_key, settings.api_secret),
data=json.dumps(addon),
headers={
"content-type": "application/json"
}
)
if not resp.get('id'):
frappe.log_error(str(resp), 'Razorpay Failed while creating subscription')
except:
frappe.log_error(frappe.get_traceback())
# failed
pass

def setup_subscription(self, settings, **kwargs):
start_date = get_timestamp(kwargs.get('subscription_details').get("start_date")) \
if kwargs.get('subscription_details').get("start_date") else None

subscription_details = {
"plan_id": kwargs.get('subscription_details').get("plan_id"),
"start_at": cint(start_date),
"total_count": kwargs.get('subscription_details').get("billing_frequency"),
"customer_notify": kwargs.get('subscription_details').get("customer_notify")
}

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

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

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

except:
frappe.log_error(frappe.get_traceback())
# failed
pass

def prepare_subscription_details(self, settings, **kwargs):
if not kwargs.get("subscription_id"):
kwargs = self.setup_subscription(settings, **kwargs)

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

return kwargs

def get_payment_url(self, **kwargs): def get_payment_url(self, **kwargs):
integration_request = create_request_log(kwargs, "Host", "Razorpay") integration_request = create_request_log(kwargs, "Host", "Razorpay")
return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name)) return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name))
@@ -119,6 +211,23 @@ class RazorpaySettings(Document):
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"


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

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

if resp.get("status") == "captured":
# if subscription starts immediately then
# razorpay charge the actual amount
# thus changing status to Completed

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

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


@@ -132,24 +241,26 @@ class RazorpaySettings(Document):
redirect_to = data.get('notes', {}).get('redirect_to') or None redirect_to = data.get('notes', {}).get('redirect_to') or None
redirect_message = data.get('notes', {}).get('redirect_message') or None redirect_message = data.get('notes', {}).get('redirect_message') or None


if self.flags.status_changed_to == "Authorized":
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
custom_redirect_to = frappe.get_doc(self.data.reference_doctype, custom_redirect_to = frappe.get_doc(self.data.reference_doctype,
self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) 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?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})


@@ -164,7 +275,7 @@ class RazorpaySettings(Document):
"api_secret": self.get_password(fieldname="api_secret", raise_exception=False) "api_secret": self.get_password(fieldname="api_secret", raise_exception=False)
}) })


if cint(data.get('notes', {}).get('use_sandbox')):
if cint(data.get('notes', {}).get('use_sandbox')) or data.get("use_sandbox"):
settings.update({ settings.update({
"api_key": frappe.conf.sandbox_api_key, "api_key": frappe.conf.sandbox_api_key,
"api_secret": frappe.conf.sandbox_api_secret, "api_secret": frappe.conf.sandbox_api_secret,
@@ -172,6 +283,16 @@ class RazorpaySettings(Document):


return settings return settings


def cancel_subscription(self, subscription_id):
settings = self.get_settings({})

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

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. Verifies the purchase as complete by the merchant.
@@ -201,4 +322,59 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
doc = frappe.get_doc("Integration Request", doc.name) doc = frappe.get_doc("Integration Request", doc.name)
doc.status = "Failed" doc.status = "Failed"
doc.error = frappe.get_traceback() doc.error = frappe.get_traceback()
frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
frappe.log_error(doc.error, '{0} Failed'.format(doc.name))

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

frappe.conf.converted_rupee_to_paisa = True

@frappe.whitelist(allow_guest=True)
def razorpay_subscription_callback():
try:
data = frappe.local.form_dict

validate_payment_callback(data)

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

doc = frappe.get_doc({
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
"integration_type": "Subscription Notification",
"status": "Queued"
}).insert(ignore_permissions=True)
frappe.db.commit()

frappe.enqueue(method='frappe.integrations.doctype.razorpay_settings.razorpay_settings.handle_subscription_notification',
queue='long', timeout=600, is_async=True, **{"doctype": "Integration Request", "docname": doc.name})

except frappe.InvalidStatusError:
pass
except Exception as e:
frappe.log(frappe.log_error(title=e))

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

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

if not(subscription_id):
_throw()

controller = frappe.get_doc("Razorpay Settings")

settings = controller.get_settings(data)

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

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

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

+ 200
- 5
payments/payment_gateways/doctype/stripe_settings/stripe_settings.json Ver arquivo

@@ -3,6 +3,7 @@
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"autoname": "field:gateway_name",
"beta": 0, "beta": 0,
"creation": "2017-03-09 17:18:29.458397", "creation": "2017-03-09 17:18:29.458397",
"custom": 0, "custom": 0,
@@ -13,6 +14,40 @@
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Gateway Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -24,7 +59,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Publishable Key", "label": "Publishable Key",
"length": 0, "length": 0,
@@ -39,9 +74,43 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -53,7 +122,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Secret Key", "label": "Secret Key",
"length": 0, "length": 0,
@@ -68,6 +137,133 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "header_img",
"fieldtype": "Attach Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Header Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "redirect_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
@@ -78,10 +274,10 @@
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1,
"issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-03-09 17:19:25.087475",
"modified": "2018-05-23 13:32:14.429916",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Integrations", "module": "Integrations",
"name": "Stripe Settings", "name": "Stripe Settings",
@@ -90,7 +286,6 @@
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,


+ 33
- 28
payments/payment_gateways/doctype/stripe_settings/stripe_settings.py Ver arquivo

@@ -29,9 +29,9 @@ class StripeSettings(Document):
'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50 'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50
} }


def validate(self):
create_payment_gateway('Stripe')
call_hook_method('payment_gateway_enabled', gateway='Stripe')
def on_update(self):
create_payment_gateway('Stripe-' + self.gateway_name, settings='Stripe Settings', controller=self.gateway_name)
call_hook_method('payment_gateway_enabled', gateway='Stripe-' + self.gateway_name)
if not self.flags.ignore_mandatory: if not self.flags.ignore_mandatory:
self.validate_stripe_credentails() self.validate_stripe_credentails()


@@ -55,50 +55,46 @@ class StripeSettings(Document):


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
self.data = frappe._dict(data) self.data = frappe._dict(data)
stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False)
stripe.default_http_client = stripe.http_client.RequestsClient()


try: try:
self.integration_request = create_request_log(self.data, "Host", "Stripe") self.integration_request = create_request_log(self.data, "Host", "Stripe")
return self.create_charge_on_stripe() return self.create_charge_on_stripe()

except Exception: except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
return{ 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.")),
"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 "status": 401
} }
def create_charge_on_stripe(self):
headers = {"Authorization":
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
data = {
"amount": cint(flt(self.data.amount)*100),
"currency": self.data.currency,
"source": self.data.stripe_token_id,
"description": self.data.description
}
redirect_to = self.data.get('redirect_to') or None
redirect_message = self.data.get('redirect_message') or None


def create_charge_on_stripe(self):
import stripe
try: try:
resp = make_post_request(url="https://api.stripe.com/v1/charges", headers=headers, data=data)
if resp.get("captured") == True:
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)

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(str(resp), 'Stripe Payment not completed')
frappe.log_error(charge.failure_message, 'Stripe Payment not completed')


except:
except Exception:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
# failed
pass


status = frappe.flags.integration_request.status_code
return self.finalize_request()


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


if self.flags.status_changed_to == "Completed": if self.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:
@@ -112,7 +108,11 @@ class StripeSettings(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'

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


@@ -125,3 +125,8 @@ class StripeSettings(Document):
"redirect_to": redirect_url, "redirect_to": redirect_url,
"status": status "status": status
} }

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

+ 23
- 0
payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.js Ver arquivo

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Stripe Settings", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially([
// insert a new Stripe Settings
() => frappe.tests.make('Stripe Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 9
- 0
payments/payment_gateways/doctype/stripe_settings/test_stripe_settings.py Ver arquivo

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import unittest

class TestStripeSettings(unittest.TestCase):
pass

+ 65
- 2
payments/payments/doctype/payment_gateway/payment_gateway.json Ver arquivo

@@ -13,6 +13,7 @@
"editable_grid": 1, "editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -24,7 +25,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Gateway", "label": "Gateway",
"length": 0, "length": 0,
@@ -40,6 +41,68 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_settings",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Gateway Settings",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "gateway_controller",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Gateway Controller",
"length": 0,
"no_copy": 0,
"options": "gateway_settings",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@@ -52,7 +115,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-03-09 12:40:56.176464",
"modified": "2018-02-05 14:24:33.526645",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Payment Gateway", "name": "Payment Gateway",


+ 23
- 0
payments/payments/doctype/payment_gateway/test_payment_gateway.js Ver arquivo

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Payment Gateway", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially([
// insert a new Payment Gateway
() => frappe.tests.make('Payment Gateway', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 55
- 0
payments/templates/includes/braintree_checkout.js Ver arquivo

@@ -0,0 +1,55 @@
$(document).ready(function() {

var button = document.querySelector('#submit-button');
var form = document.querySelector('#payment-form');
var data = {{ frappe.form_dict | json }};
var doctype = "{{ reference_doctype }}"
var docname = "{{ reference_docname }}"

braintree.dropin.create({
authorization: "{{ client_token }}",
container: '#bt-dropin',
paypal: {
flow: 'vault'
}
}, function(createErr, instance) {
form.addEventListener('submit', function(event) {
event.preventDefault();
instance.requestPaymentMethod(function(err, payload) {
if (err) {
console.log('Error', err);
return;
}
frappe.call({
method: "frappe.templates.pages.integrations.braintree_checkout.make_payment",
freeze: true,
headers: {
"X-Requested-With": "XMLHttpRequest"
},
args: {
"payload_nonce": payload.nonce,
"data": JSON.stringify(data),
"reference_doctype": doctype,
"reference_docname": docname
},
callback: function(r) {
if (r.message && r.message.status == "Completed") {
window.location.href = r.message.redirect_to
} else if (r.message && r.message.status == "Error") {
window.location.href = r.message.redirect_to
}
}
})
});
});

instance.on('paymentMethodRequestable', function (event) {
button.removeAttribute('disabled');
});

instance.on('noPaymentMethodRequestable', function () {
button.setAttribute('disabled', true);
});
});

})

+ 1
- 0
payments/templates/includes/razorpay_checkout.js Ver arquivo

@@ -5,6 +5,7 @@ $(document).ready(function(){
"amount": cint({{ amount }} * 100), // 2000 paise = INR 20 "amount": cint({{ amount }} * 100), // 2000 paise = INR 20
"name": "{{ title }}", "name": "{{ title }}",
"description": "{{ description }}", "description": "{{ description }}",
"subscription_id": "{{ subscription_id }}",
"handler": function (response){ "handler": function (response){
razorpay.make_payment_log(response, options, "{{ reference_doctype }}", "{{ reference_docname }}", "{{ token }}"); razorpay.make_payment_log(response, options, "{{ reference_doctype }}", "{{ reference_docname }}", "{{ token }}");
}, },


+ 81
- 43
payments/templates/includes/stripe_checkout.js Ver arquivo

@@ -1,47 +1,85 @@
$(document).ready(function(){
(function(e){
var handler = StripeCheckout.configure({
key: "{{ publishable_key }}",
token: function(token) {
// You can access the token ID with `token.id`.
// Get the token ID to your server-side code for use.
stripe.make_payment_log(token, {{ frappe.form_dict|json }}, "{{ reference_doctype }}", "{{ reference_docname }}");
var stripe = Stripe("{{ publishable_key }}");

var elements = stripe.elements();

var style = {
base: {
color: '#32325d',
lineHeight: '18px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};

var card = elements.create('card', {
hidePostalCode: true,
style: style
});

card.mount('#card-element');

function setOutcome(result) {

if (result.token) {
$('#submit').prop('disabled', true)
$('#submit').html(__('Processing...'))
frappe.call({
method:"frappe.templates.pages.integrations.stripe_checkout.make_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
args: {
"stripe_token_id": result.token.id,
"data": JSON.stringify({{ frappe.form_dict|json }}),
"reference_doctype": "{{ reference_doctype }}",
"reference_docname": "{{ reference_docname }}"
},
callback: function(r) {
if (r.message.status == "Completed") {
$('#submit').hide()
$('.success').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
} else {
$('#submit').hide()
$('.error').show()
setTimeout(function() {
window.location.href = r.message.redirect_to
}, 2000);
}
} }
}); });
handler.open({
name: "{{payer_name}}",
description: "{{description}}",
amount: cint("{{ amount }}" * 100), // 2000 paise = INR 20
email: "{{payer_email}}",
currency: "{{currency}}"
});
})();
})

frappe.provide('stripe');

stripe.make_payment_log = function(token, data, doctype, docname){
$('.stripe-loading').addClass('hidden');
$('.stripe-confirming').removeClass('hidden');
frappe.call({
method:"frappe.templates.pages.integrations.stripe_checkout.make_payment",
freeze:true,
headers: {"X-Requested-With": "XMLHttpRequest"},
args: {
"stripe_token_id": token.id,
"data": JSON.stringify(data),
"reference_doctype": doctype,
"reference_docname": docname
},
callback: function(r){
if (r.message && r.message.status == 200) {
window.location.href = r.message.redirect_to
}
else if (r.message && ([401,400,500].indexOf(r.message.status) > -1)) {
window.location.href = r.message.redirect_to
}

} else if (result.error) {
$('.error').html(result.error.message);
$('.error').show()
}
}

card.on('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});

frappe.ready(function() {
$('#submit').off("click").on("click", function(e) {
e.preventDefault();
var extraDetails = {
name: $('input[name=cardholder-name]').val(),
email: $('input[name=cardholder-email]').val()
} }
stripe.createToken(card, extraDetails).then(setOutcome);
}) })
}
});

+ 54
- 0
payments/templates/pages/braintree_checkout.html Ver arquivo

@@ -0,0 +1,54 @@
{% extends "templates/web.html" %}

{% block title %} Payment {% endblock %}

{%- block header -%}{% endblock %}

{% block script %}
<script src="https://js.braintreegateway.com/web/dropin/1.9.3/js/dropin.min.js"></script>
<script>{% include "templates/includes/integrations/braintree_checkout.js" %}</script>
{% endblock %}

{%- block page_content -%}
<div class="wrapper">
<div class="checkout container">

<header>
<div>
<img class="center" src="{{ header_img }}"></img>
</div>
</header>

<form id="payment-form">
<section>
<div class="bt-drop-in-wrapper">
<div id="bt-dropin"></div>
</div>
</section>

<button class="btn btn-primary" type="submit" id="submit-button" disabled><span>{{ _("Pay") }} {{ amount }} {{ currency }}</span></button>
</form>

</div>
</div>

<style>
.checkout {
max-width: 60%;
}

.center {
margin:auto;
display: block;
}

#payment-form {
margin-top: 40px;
}

#submit-button {
float: right;
}

</style>
{% endblock %}

+ 48
- 0
payments/templates/pages/braintree_checkout.py Ver arquivo

@@ -0,0 +1,48 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
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_sitemap = 1

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

def get_context(context):
context.no_cache = 1

# all these keys exist in form_dict
if not (set(expected_keys) - set(list(frappe.form_dict))):
for key in expected_keys:
context[key] = frappe.form_dict[key]

context.client_token = get_client_token(context.reference_docname)

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

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

else:
frappe.redirect_to_message(_('Some information is missing'),
_('Looks like someone sent you to an incomplete URL. Please ask them to look into it.'))
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect

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

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

gateway_controller = get_gateway_controller(reference_docname)
data = frappe.get_doc("Braintree Settings", gateway_controller).create_payment_request(data)
frappe.db.commit()
return data

+ 21
- 0
payments/templates/pages/gcalendar-success.html Ver arquivo

@@ -0,0 +1,21 @@
{% extends "templates/web.html" %}

{% block title %}{{ _("Connection Success") }}{% endblock %}

{%- block page_content -%}
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator green'>
{{ _("Success") }}</span>
</div>
<p>{{ _("Your connection request to Google Calendar was successfully accepted") }}</p>
<div><a href='{{ "/desk" }}' class='btn btn-primary btn-sm'>
{{ _("Back to Desk") }}</a></div>
</div>
<style>
.hero-and-content {
background-color: #f5f7fa;
}
{% include "templates/styles/card_style.css" %}
</style>
{% endblock %}

+ 1
- 0
payments/templates/pages/payment-cancel.html Ver arquivo

@@ -16,6 +16,7 @@
.hero-and-content { .hero-and-content {
background-color: #f5f7fa; background-color: #f5f7fa;
} }
{% include "templates/styles/card_style.css" %}
</style> </style>


{% endblock %} {% endblock %}

+ 1
- 0
payments/templates/pages/payment-failed.html Ver arquivo

@@ -16,6 +16,7 @@
.hero-and-content { .hero-and-content {
background-color: #f5f7fa; background-color: #f5f7fa;
} }
{% include "templates/styles/card_style.css" %}
</style> </style>


{% endblock %} {% endblock %}

+ 20
- 6
payments/templates/pages/payment-success.html Ver arquivo

@@ -8,13 +8,27 @@
<span class='indicator green'> <span class='indicator green'>
{{ _("Success") }}</span> {{ _("Success") }}</span>
</div> </div>
<p>{{ _("Your payment was successfully accepted") }}</p>
<div><a href='{{ frappe.form_dict.redirect_to or "/" }}' class='btn btn-primary btn-sm'>
{{ _("Continue") }}</a></div>
<p>{{ payment_message or _("Your payment was successfully accepted") }}</p>
{% if not payment_message %}
<div>
<a
href='{{ frappe.form_dict.redirect_to or "/" }}'
class='btn btn-primary btn-sm'>
{{ _("Continue") }}
</a>
</div>
{% endif %}
</div> </div>
<style> <style>
.hero-and-content {
background-color: #f5f7fa;
}
{% include "templates/styles/card_style.css" %}
</style> </style>
<script>
frappe.ready(function() {
if('{{ frappe.form_dict.redirect_to or "" }}'){
setTimeout(function(){
window.location.href = '{{ frappe.form_dict.redirect_to }}';
}, 4000);
}
})
</script>
{% endblock %} {% endblock %}

+ 8
- 1
payments/templates/pages/payment_success.py Ver arquivo

@@ -4,6 +4,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals


import frappe import frappe
no_cache = True


def get_context(context): def get_context(context):
token = frappe.local.form_dict.token
token = frappe.local.form_dict.token
doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.docname)

context.payment_message = ''
if hasattr(doc, 'get_payment_success_message'):
context.payment_message = doc.get_payment_success_message()


+ 3
- 1
payments/templates/pages/razorpay_checkout.py Ver arquivo

@@ -26,8 +26,10 @@ def get_context(context):


context['token'] = frappe.form_dict['token'] context['token'] = frappe.form_dict['token']
context['amount'] = flt(context['amount']) context['amount'] = flt(context['amount'])
context['subscription_id'] = payment_details['subscription_id'] \
if payment_details.get('subscription_id') else ''


except Exception:
except Exception as e:
frappe.redirect_to_message(_('Invalid Token'), frappe.redirect_to_message(_('Invalid Token'),
_('Seems token you are using is invalid!'), _('Seems token you are using is invalid!'),
http_status_code=400, indicator_color='red') http_status_code=400, indicator_color='red')


+ 113
- 0
payments/templates/pages/stripe_checkout.css Ver arquivo

@@ -0,0 +1,113 @@
.StripeElement {
background-color: white;
height: 40px;
padding: 10px 12px;
border-radius: 4px;
border: 1px solid transparent;
box-shadow: 0 1px 3px 0 #e6ebf1;
-webkit-transition: box-shadow 150ms ease;
transition: box-shadow 150ms ease;
}

.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}

.StripeElement--invalid {
border-color: #fa755a;
}

.StripeElement--webkit-autofill {
background-color: #fefde5;
}

.stripe #payment-form {
margin-top: 80px;
}

.stripe button {
float: right;
display: block;
background: #5e64ff;
color: white;
box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
border-radius: 4px;
border: 0;
margin-top: 20px;
font-size: 15px;
font-weight: 400;
max-width: 40%;
height: 40px;
line-height: 38px;
outline: none;
}

.stripe button:hover, .stripe button:focus {
background: #2b33ff;
border-color: #0711ff;
}

.stripe button:active {
background: #5e64ff;
}

.stripe button:disabled {
background: #515e80;
}

.stripe .group {
background: white;
box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
border-radius: 4px;
margin-bottom: 20px;
}

.stripe label {
position: relative;
color: #8898AA;
font-weight: 300;
height: 40px;
line-height: 40px;
margin-left: 20px;
display: block;
}

.stripe .group label:not(:last-child) {
border-bottom: 1px solid #F0F5FA;
}

.stripe label>span {
width: 20%;
text-align: right;
float: left;
}

.current-card {
margin-left: 20px;
}

.field {
background: transparent;
font-weight: 300;
border: 0;
color: #31325F;
outline: none;
padding-right: 10px;
padding-left: 10px;
cursor: text;
width: 70%;
height: 40px;
float: right;
}

.field::-webkit-input-placeholder {
color: #CFD7E0;
}

.field::-moz-placeholder {
color: #CFD7E0;
}

.field:-ms-input-placeholder {
color: #CFD7E0;
}

+ 45
- 15
payments/templates/pages/stripe_checkout.html Ver arquivo

@@ -2,27 +2,57 @@


{% block title %} Payment {% endblock %} {% block title %} Payment {% endblock %}


{%- block header -%}{% endblock %}
{%- block header -%}
{% endblock %}


{% block script %} {% block script %}
<script src="https://checkout.stripe.com/checkout.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>{% include "templates/includes/integrations/stripe_checkout.js" %}</script> <script>{% include "templates/includes/integrations/stripe_checkout.js" %}</script>
{% endblock %} {% endblock %}


{%- block page_content -%} {%- block page_content -%}


<p class='lead text-center centered'>
<span class='stripe-loading'>Loading Payment System</span>
<span class='stripe-confirming hidden'>Confirming Payment</span>
</p>

{% endblock %}
<div class="row stripe" style="min-height: 400px; padding-bottom: 50px; margin-top:100px;">
<div class="col-sm-8 col-sm-offset-2">
{% if image %}
<img src={{image}}>
{% endif %}
<h2 class="text-center">{{description}}</h2>
<form id="payment-form">
<div class="form-row">
<div class="group">
<div>
<label>
<span>{{ _("Name") }}</span>
<input id="cardholder-name" name="cardholder-name" class="field" placeholder="{{ _('John Doe') }}" value="{{payer_name}}"/>
</label>
</div>
</div>
<div class="group">
<div>
<label>
<span>{{ _("Email") }}</span>
<input id="cardholder-email" name="cardholder-email" class="field" placeholder="{{ _('john@doe.com') }}" value="{{payer_email}}"/>
</label>
</div>
</div>
<div class="group">
<label>
<span>{{ _("Card Details") }}</span>
<div id="card-element" name="card-element" class="field"></div>
<div id="card-errors" role="alert"></div>
</label>
</div>

</div>
<button type="submit" class="submit" id="submit">{{_('Pay')}} {{amount}}</button>
<div class="outcome text-center">
<div class="error" hidden>{{ _("An error occured during the payment process. Please contact us.") }}</div>
<div class="success" hidden>{{ _("Your payment has been successfully registered.") }}</div>
</div>
</form>
</div>
</div>


{% block style %}
<style>
header, footer {
display: none;
}


</style>
{% endblock %}
{% endblock %}

+ 30
- 8
payments/templates/pages/stripe_checkout.py Ver arquivo

@@ -3,8 +3,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, cint
from frappe.utils import cint, fmt_money
import json import json
from frappe.integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller


no_cache = 1 no_cache = 1
no_sitemap = 1 no_sitemap = 1
@@ -14,14 +15,23 @@ expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'referen


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


# all these keys exist in form_dict # all these keys exist in form_dict
if not (set(expected_keys) - set(frappe.form_dict.keys())):
if not (set(expected_keys) - set(list(frappe.form_dict))):
for key in expected_keys: for key in expected_keys:
context[key] = frappe.form_dict[key] context[key] = frappe.form_dict[key]


context['amount'] = flt(context['amount'])
gateway_controller = get_gateway_controller(context.reference_doctype, context.reference_docname)
context.publishable_key = get_api_key(context.reference_docname, gateway_controller)
context.image = get_header_image(context.reference_docname, gateway_controller)

context['amount'] = fmt_money(amount=context['amount'], currency=context['currency'])

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

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


else: else:
frappe.redirect_to_message(_('Some information is missing'), frappe.redirect_to_message(_('Some information is missing'),
@@ -29,13 +39,18 @@ def get_context(context):
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():
publishable_key = frappe.db.get_value("Stripe Settings", None, "publishable_key")
def get_api_key(doc, gateway_controller):
publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key")
if cint(frappe.form_dict.get("use_sandbox")): if cint(frappe.form_dict.get("use_sandbox")):
publishable_key = frappe.conf.sandbox_publishable_key publishable_key = frappe.conf.sandbox_publishable_key


return publishable_key return publishable_key


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

return header_image

@frappe.whitelist(allow_guest=True) @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)
@@ -44,6 +59,13 @@ def make_payment(stripe_token_id, data, reference_doctype=None, reference_docnam
"stripe_token_id": stripe_token_id "stripe_token_id": stripe_token_id
}) })


data = frappe.get_doc("Stripe Settings").create_request(data)
gateway_controller = get_gateway_controller(reference_doctype,reference_docname)

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

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

Carregando…
Cancelar
Salvar