Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple payment methods #17

Merged
merged 22 commits into from
Feb 7, 2024
Merged

Conversation

agritheory
Copy link
Owner

@agritheory agritheory commented Nov 7, 2023

  • Ability to configure Mode of Payments / Payment Gateway on a per Customer basis
  • Ability to configure Mode of Payments / Payment Gateway on a per Supplier basis
  • Support a "Billed per Agreement" option - workflow needs more design To be handled with a separate PR
  • Support storage via tokenization for saved Electronic Payment Profile - integration required
  • Add default and Mode of Payment fields in customer/ supplier configuration
  • Payment Methods should support a service charge - percentage or flat fee including currency
  • Add credit limit check for Customer

wip: integration with Electronic Payment Profile, Payment Gateway and credit limit
@agritheory agritheory force-pushed the multiple_payment_methods branch from 23e823d to 9484692 Compare November 7, 2023 20:23
@agritheory agritheory linked an issue Nov 7, 2023 that may be closed by this pull request
@HKuz
Copy link
Collaborator

HKuz commented Nov 22, 2023

Quick update here - the internal wiring for both Authorize.net and Stripe supports multiple payment methods, including a credit check and calculation of fees. The desk-perspective is working, but I'm getting a Permissions error after clicking "Pay" on the Portal side. The API call goes through, but when the Journal Entry tries to save, the system seems to apply the portal user's permissions up the validation chain and throws an error.

For now I also put the Customer and Supplier field back in to hold the Customer's ID in the API. This is useful to have saved somewhere separate than an electronic payment profile for the logic branch to save payment info for the current transaction only. The code creates a customer profile and a temporary payment profile (then deletes once used) - having the customer ID saved separate and being able to access it prevents it from creating another one in the API every time.

The ERPNext credit limit code seemed to have an inconsistency in it - it would run a credit check as an after-save method for a Journal Entry, and ignored if the "exclude Sales Orders" option is checked. I was getting a credit limit error for any payment made if the sum total of Sales Orders were over the threshold (which is allowed with that option). I created an override so the credit check respected the Credit Limit configuration for a customer.

I'm working on the Billed as Agreed - there's still some plumbing to match a payment to the payment term in the Payment Entry (required when there's a payment template on a SO/SI).

@HKuz
Copy link
Collaborator

HKuz commented Nov 22, 2023

Permissions traceback error - same for both Payment Entry and Journal Entry:

Traceback (most recent call last):
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/stripe.py", line 365, in charge_customer_profile
    process_electronic_payment(doc, data, response.id)
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/common.py", line 34, in process_electronic_payment
    create_journal_entry(doc, data, transaction_id)
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/common.py", line 181, in create_journal_entry
    je.save()
  File "apps/frappe/frappe/model/document.py", line 309, in save
    return self._save(*args, **kwargs)
  File "apps/frappe/frappe/model/document.py", line 331, in _save
    return self.insert()
  File "apps/frappe/frappe/model/document.py", line 255, in insert
    self.check_permission("create")
  File "apps/frappe/frappe/model/document.py", line 196, in check_permission
    self.raise_no_permission_to(permtype)
  File "apps/frappe/frappe/model/document.py", line 218, in raise_no_permission_to
    raise frappe.PermissionError
frappe.exceptions.PermissionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/stripe.py", line 381, in charge_customer_profile
    frappe.log_error(message=frappe.get_traceback(), title=f"{e.code}: {e.type}. {e.message}")
AttributeError: 'PermissionError' object has no attribute 'code'

@HKuz
Copy link
Collaborator

HKuz commented Nov 22, 2023

Permissions update:

Using enqueue (with params is_async=True and now=False) avoids showing the error to the portal-user. However, the JE/PE still won't save, with error log shows the following traceback:

Traceback with variables (most recent call last):
  File "apps/frappe/frappe/utils/background_jobs.py", line 194, in execute_job
    retval = method(**kwargs)
      site = 'e14.test'
      method = <function process_electronic_payment at 0x11257f760>
      event = None
      job_name = '<function process_electronic_payment at 0x10c08aef0>'
      kwargs = {'doc': <SalesOrder: SAL-ORD-2023-00001 docstatus=1>, 'data': {'ppm_mop': 'Credit Card', 'ppm_name': '26fe609ec6', 'additional_charges': 0.98, 'mode_of_payment': 'Saved Payment Method', 'payment_profile_id': '523391228', 'customer_profile_id': '515486635'}, 'transaction_id': '80009172069'}
      user = '[email protected]'
      is_async = True
      retry = 0
      retval = None
      method_name = 'process_electronic_payment'
      before_job_task = 'frappe.monitor.start'
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/common.py", line 34, in process_electronic_payment
    create_journal_entry(doc, data, transaction_id)
      doc = <SalesOrder: SAL-ORD-2023-00001 docstatus=1>
      data = {'ppm_mop': 'Credit Card', 'ppm_name': '26fe609ec6', 'additional_charges': 0.98, 'mode_of_payment': 'Saved Payment Method', 'payment_profile_id': '523391228', 'customer_profile_id': '515486635'}
      transaction_id = '80009172069'
      settings = <ElectronicPaymentSettings: Chelsea Fruit Co-Authorize.net>
  File "apps/electronic_payments/electronic_payments/electronic_payments/doctype/electronic_payment_settings/common.py", line 181, in create_journal_entry
    je.save()
      doc = <SalesOrder: SAL-ORD-2023-00001 docstatus=1>
      data = {'ppm_mop': 'Credit Card', 'ppm_name': '26fe609ec6', 'additional_charges': 0.98, 'mode_of_payment': 'Saved Payment Method', 'payment_profile_id': '523391228', 'customer_profile_id': '515486635'}
      transaction_id = '80009172069'
      settings = <ElectronicPaymentSettings: Chelsea Fruit Co-Authorize.net>
      party_type = 'Customer'
      party = 'Andromeda Fruit Market'
      account = '1310 - Accounts Receivable - CFC'
      clearing_account = '1320 - Electronic Payments Receivable - CFC'
      account_key = ********
      account_currency_key = ********
      contra_account_key = ********
      contra_account_currency_key = ********
      is_advance = True
      fee_account = '5223 - Electronic Payments Provider Fees - CFC'
      fees = 0.98
      je = <CustomElectronicPaymentsJournalEntry: unsaved>
      ppm_mop = 'Credit Card'
  File "apps/frappe/frappe/model/document.py", line 309, in save
    return self._save(*args, **kwargs)
      self = <CustomElectronicPaymentsJournalEntry: unsaved>
      args = ()
      kwargs = {}
  File "apps/frappe/frappe/model/document.py", line 331, in _save
    return self.insert()
      self = <CustomElectronicPaymentsJournalEntry: unsaved>
      ignore_permissions = None
      ignore_version = None
  File "apps/frappe/frappe/model/document.py", line 255, in insert
    self.check_permission("create")
      self = <CustomElectronicPaymentsJournalEntry: unsaved>
      ignore_permissions = None
      ignore_links = None
      ignore_if_duplicate = False
      ignore_mandatory = None
      set_name = None
      set_child_names = True
  File "apps/frappe/frappe/model/document.py", line 196, in check_permission
    self.raise_no_permission_to(permtype)
      self = <CustomElectronicPaymentsJournalEntry: unsaved>
      permtype = 'create'
      permlevel = None
  File "apps/frappe/frappe/model/document.py", line 218, in raise_no_permission_to
    raise frappe.PermissionError
      self = <CustomElectronicPaymentsJournalEntry: unsaved>
      perm_type = 'create'
frappe.exceptions.PermissionError: 

@agritheory agritheory requested a review from fproldan January 17, 2024 14:35
@HKuz
Copy link
Collaborator

HKuz commented Jan 26, 2024

@agritheory @fproldan - permission error issue is fixed and this should be ready for a second set of eyes. I had to copy code logic one level up from enqueue where you can set the user. I've re-tested all logic paths for Authorize.net (from Portal as a user and paying an Invoice in Desk view as Admin) and didn't come across any issues. I also reinstalled my site to test the Stripe side and that's working.

I shared the test script in Slack that auto-loads payment info. Only (minor) thing not quite working is that after a successful API payment, is it's only creating the paid Journal Entry for the last customer in the loop. (The transactions all go through). May be an enqueued job thing where they're not distinguished as unique but I didn't want to hold this up trying to track it down.

@fproldan
Copy link
Collaborator

@HKuz

In the execution of the test script, I get the following errors in Stripe when tries to add a new payment method:

stripe.error.CardError: Request req_2Rdte3IYnTAEvA: Sending credit card numbers directly to the Stripe API is generally unsafe. We suggest you use test tokens that map to the test card you are using, see https://stripe.com/docs/testing. To enable raw card data APIs in test mode, see https://support.stripe.com/questions/enabling-access-to-raw-card-data-apis.

Captura de pantalla 2024-01-29 a la(s) 15 35 22

@HKuz
Copy link
Collaborator

HKuz commented Jan 30, 2024

All good comments, @fproldan thanks for the thorough review - I'll start addressing these today. For the Stripe error, I didn't get that when I reinstalled, maybe I enabled something in my account to bypass it? I'll look into creating a function that uses their test card token info instead of a raw number in the test script. I guess the bigger question is what happens in production when a user enters a card number (will we have to use Stripe's UI components?)...

@agritheory agritheory merged commit c383888 into version-14 Feb 7, 2024
1 check passed
@agritheory agritheory deleted the multiple_payment_methods branch February 11, 2024 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable Multiple Payment Gateways and Payment Methods
3 participants