您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

250 行
8.2 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  3. # License: GNU General Public License v3. See license.txt
  4. from __future__ import unicode_literals
  5. import frappe
  6. import json
  7. from frappe import _
  8. from urllib import urlencode
  9. from frappe.utils import get_url, call_hook_method
  10. from frappe.integration_broker.integration_controller import IntegrationController
  11. """
  12. # Integrating PayPal
  13. ### 1. Validate Currency Support
  14. Example:
  15. from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller
  16. controller = get_integration_controller("PayPal", setup=False)
  17. controller().validate_transaction_currency(currency)
  18. ### 2. Redirect for payment
  19. Example:
  20. payment_details = {
  21. "amount": 600,
  22. "title": "Payment for bill : 111",
  23. "description": "payment via cart",
  24. "reference_doctype": "Payment Request",
  25. "reference_docname": "PR0001",
  26. "payer_email": "NuranVerkleij@example.com",
  27. "payer_name": "Nuran Verkleij",
  28. "order_id": "111",
  29. "currency": "USD"
  30. }
  31. # redirect the user to this url
  32. url = controller().get_payment_url(**payment_details)
  33. ### 3. On Completion of Payment
  34. Write a method for `on_payment_authorized` in the reference doctype
  35. Example:
  36. def on_payment_authorized(payment_status):
  37. # your code to handle callback
  38. ##### Note:
  39. payment_status - payment gateway will put payment status on callback.
  40. For paypal payment status parameter is one from: [Completed, Cancelled, Failed]
  41. """
  42. class Controller(IntegrationController):
  43. service_name = 'PayPal'
  44. parameters_template = [
  45. {
  46. "label": "API Username",
  47. 'fieldname': 'api_username',
  48. 'fieldtype': 'Data',
  49. 'reqd': 1
  50. },
  51. {
  52. "label": "API Password",
  53. 'fieldname': 'api_password',
  54. 'fieldtype': "Password",
  55. 'reqd': 1
  56. },
  57. {
  58. "label": "Signature",
  59. "fieldname": "signature",
  60. 'fieldtype': "Data",
  61. "reqd": 1
  62. }
  63. ]
  64. js = "assets/frappe/js/integrations/paypal.js"
  65. supported_currencies = ["AUD", "BRL", "CAD", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN",
  66. "TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "TRY", "USD"]
  67. def enable(self, parameters, use_test_account=0):
  68. call_hook_method('payment_gateway_enabled', gateway=self.service_name)
  69. self.parameters = parameters
  70. self.validate_paypal_credentails(use_test_account)
  71. def get_settings(self):
  72. return frappe._dict(self.parameters)
  73. def validate_transaction_currency(self, currency):
  74. if currency not in self.supported_currencies:
  75. frappe.throw(_("Please select another payment method. {0} does not support transactions in currency '{1}'").format(self.service_name, currency))
  76. def get_paypal_params_and_url(self, use_test_account):
  77. paypal_settings = frappe._dict(self.get_settings())
  78. params = {
  79. "USER": paypal_settings.api_username,
  80. "PWD": paypal_settings.api_password,
  81. "SIGNATURE": paypal_settings.signature,
  82. "VERSION": "98",
  83. "METHOD": "GetPalDetails"
  84. }
  85. api_url = "https://api-3t.sandbox.paypal.com/nvp" if use_test_account else "https://api-3t.paypal.com/nvp"
  86. return params, api_url
  87. def validate_paypal_credentails(self, use_test_account):
  88. params, url = self.get_paypal_params_and_url(use_test_account)
  89. params = urlencode(params)
  90. try:
  91. res = self.post_request(url=url, data=params.encode("utf-8"))
  92. if res["ACK"][0] == "Failure":
  93. raise Exception
  94. except Exception:
  95. frappe.throw(_("Invalid payment gateway credentials"))
  96. def get_payment_url(self, **kwargs):
  97. use_test_account, custom_settings_json = frappe.db.get_value("Integration Service", self.service_name, ["use_test_account", "custom_settings_json"])
  98. self.parameters = json.loads(custom_settings_json)
  99. response = self.execute_set_express_checkout(kwargs["amount"], kwargs["currency"], use_test_account)
  100. if use_test_account:
  101. return_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}"
  102. else:
  103. return_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token={0}"
  104. kwargs.update({
  105. "token": response.get("TOKEN")[0],
  106. "correlation_id": response.get("CORRELATIONID")[0]
  107. })
  108. self.integration_request = self.create_request(kwargs, "Remote", self.service_name, response.get("TOKEN")[0])
  109. return return_url.format(kwargs["token"])
  110. def execute_set_express_checkout(self, amount, currency, use_test_account):
  111. params, url = self.get_paypal_params_and_url(use_test_account)
  112. params.update({
  113. "METHOD": "SetExpressCheckout",
  114. "PAYMENTREQUEST_0_PAYMENTACTION": "SALE",
  115. "PAYMENTREQUEST_0_AMT": amount,
  116. "PAYMENTREQUEST_0_CURRENCYCODE": currency,
  117. "returnUrl": get_url("/api/method/frappe.integrations.paypal.get_express_checkout_details"),
  118. "cancelUrl": get_url("/payment-cancel")
  119. })
  120. params = urlencode(params)
  121. response = self.post_request(url, data=params.encode("utf-8"))
  122. if response.get("ACK")[0] != "Success":
  123. frappe.throw("Looks like something is wrong with this site's Paypal configuration.")
  124. return response
  125. @frappe.whitelist(allow_guest=True, xss_safe=True)
  126. def get_express_checkout_details(token):
  127. use_test_account, custom_settings_json = frappe.db.get_value("Integration Service", "PayPal", ["use_test_account", "custom_settings_json"])
  128. Controller.parameters = json.loads(custom_settings_json)
  129. params, url = Controller().get_paypal_params_and_url(use_test_account)
  130. params.update({
  131. "METHOD": "GetExpressCheckoutDetails",
  132. "TOKEN": token
  133. })
  134. response = Controller().post_request(url, data=params)
  135. if response.get("ACK")[0] != "Success":
  136. frappe.respond_as_web_page(_("Something went wrong"),
  137. _("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]),
  138. success=False,
  139. http_status_code=frappe.ValidationError.http_status_code)
  140. return
  141. update_integration_request_status(token, {
  142. "payerid": response.get("PAYERID")[0],
  143. "payer_email": response.get("EMAIL")[0]
  144. }, "Authorized")
  145. frappe.local.response["type"] = "redirect"
  146. frappe.local.response["location"] = get_url( \
  147. "/api/method/frappe.integrations.paypal.confirm_payment?token={0}".format(token))
  148. @frappe.whitelist(allow_guest=True, xss_safe=True)
  149. def confirm_payment(token):
  150. redirect = True
  151. status_changed_to, redirect_to = None, None
  152. use_test_account = frappe.db.get_value("Integration Service", "PayPal", "use_test_account")
  153. integration_request = frappe.get_doc("Integration Request", token)
  154. data = json.loads(integration_request.data)
  155. params, url = Controller().get_paypal_params_and_url(use_test_account)
  156. params.update({
  157. "METHOD": "DoExpressCheckoutPayment",
  158. "PAYERID": data.get("payerid"),
  159. "TOKEN": token,
  160. "PAYMENTREQUEST_0_PAYMENTACTION": "SALE",
  161. "PAYMENTREQUEST_0_AMT": data.get("amount"),
  162. "PAYMENTREQUEST_0_CURRENCYCODE": data.get("currency")
  163. })
  164. response = Controller().post_request(url, data=params)
  165. if response.get("ACK")[0] == "Success":
  166. update_integration_request_status(token, {
  167. "transaction_id": response.get("PAYMENTINFO_0_TRANSACTIONID")[0],
  168. "correlation_id": response.get("CORRELATIONID")[0]
  169. }, "Completed")
  170. if data.get("reference_doctype") and data.get("reference_docname"):
  171. redirect_to = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed")
  172. redirect_to = redirect_to or get_url("/integrations/payment-success")
  173. else:
  174. redirect_to = get_url("/integrations/payment-failed")
  175. # this is done so that functions called via hooks can update flags.redirect_to
  176. if redirect:
  177. frappe.local.response["type"] = "redirect"
  178. frappe.local.response["location"] = redirect_to
  179. def update_integration_request_status(token, data, status, error=False):
  180. frappe.get_doc("Integration Request", token).update_status(data, status)
  181. @frappe.whitelist(allow_guest=True, xss_safe=True)
  182. def get_checkout_url(**kwargs):
  183. try:
  184. return Controller().get_payment_url(**kwargs)
  185. except Exception:
  186. frappe.respond_as_web_page(_("Something went wrong"),
  187. _("Looks like something is wrong with this site's Paypal configuration. Don't worry! No payment has been made from your Paypal account."),
  188. success=False,
  189. http_status_code=frappe.ValidationError.http_status_code)