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(payment_providers): Add MoneyHash #3117

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

shahwan42
Copy link

@shahwan42 shahwan42 commented Jan 29, 2025

Roadmap Task

NA

Feature Request

https://getlago.canny.io/feature-requests/p/integration-with-moneyhash-for-payment-orchestration

Context

MoneyHash is a leading payment infrastructure software in Africa and the Middle East. Our biggest features are providing a superior payment orchestration for many payment providers in the region, smart payment routing among other features.

We have common customers, and it's a needed feature to have MoneyHash integrated into Lago.

This PR integrates MoneyHash as a payment provider for Lago's customers.

Description

This PR implements the following changes

  • When a new customer is created in Lago and the "sync with provider" checkbox is checked, a new customer is also created in MoneyHash.
  • Create a payment URL for invoices, allowing customers to pay their invoices via MoneyHash
  • Create Payment Requests that are processed using the customer's payment method.
  • Webhook Handling
    • Transaction Webhooks
      • On the "transaction.purchase.successful" callback, the invoice/payment status is set to "Succeeded."
      • On the "transaction.purchase.failed" callback, the invoice/payment status is set to "Failed."
    • Intent Webhooks
      • On the "intent.time_expired" callback, the invoice/payment status is set to "Failed."
    • Card Token Webhooks
      • On the "card_token.created" and "card_token.updated" callbacks, the customer’s payment method ID is added or updated.
      • On the "card_token.deleted" callback, the customer’s payment method ID is removed.

List any dependencies that are required.
NA

@jdenquin jdenquin added Feature Add a new feature to the app Contribution Contributions from the Lago Community Payment labels Jan 30, 2025
@julienbourdeau
Copy link
Contributor

Hi @shahwan42,

This is awesome! Thank you contributing such a large feature.

I had a quick look and it looks great but first, we need to confirm with the product team how they feel about adding MoneyHash support.

We'll review it but I had a quick look and it looks great.

We'll have to do some QA testing to feel comfortable maintaining it in the future. I'm sure you can help to get a testing account and for any question we'll have. 👍

@shahwan42
Copy link
Author

shahwan42 commented Feb 12, 2025

Hi @julienbourdeau

Thank you for seeing this.

Regarding this particular PR, I updated it and

  • no conflicts
  • linting passes
  • ran tests and they passed

Should I do anything else?
Appreciated.

Copy link
Contributor

@julienbourdeau julienbourdeau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I had a first pass and left some initial comments 🙏

Comment on lines 129 to 140
case object&.payment_provider&.to_sym
when :stripe
object.stripe_customer
when :gocardless
object.gocardless_customer
when :cashfree
object.cashfree_customer
when :adyen
object.adyen_customer
end
return unless object

provider_mapping = {
stripe: :stripe_customer,
gocardless: :gocardless_customer,
cashfree: :cashfree_customer,
adyen: :adyen_customer,
moneyhash: :moneyhash_customer
}

method = provider_mapping[object.payment_provider&.to_sym]
object.public_send(method) if method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather avoid this kind of refactoring. I understand the feeling but we use case/when everywhere for this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it because Rubocop complained because of the function complexity. Should I ignore it?


def perform(payable)
result = PaymentRequests::Payments::MoneyhashService.new(payable).create
PaymentRequestMailer.with(payment_request: payable).requested.deliver_later if result.payable&.payment_failed?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 8 to 9
PENDING_STATUSES = %w[processing]
.freeze
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PENDING_STATUSES = %w[processing]
.freeze
PENDING_STATUSES = %w[processing].freeze

typo I guess

end

def call
PaymentRequests::Payments::MoneyhashService.new(@invoice).create
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect the opposite. The PaymentRequests::Payments:: MoneyhashService service to rely on the PaymentProviders::Moneyhash::Payments::CreateService to create the payment and then do specific things (like sending the email).

I think all the attributes are leftovers too.

PaymentRequestMailer.with(payment_request: payment.payable).requested.deliver_later if result.payable.payment_failed?
result
rescue BaseService::FailedResult => e
result.fail_with_error!(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We recently fixed a bug where emails weren't sent if we couldn't create the payment, I think you want the same.
https://github.com/getlago/lago-api/pull/3155/files

@julienbourdeau
Copy link
Contributor

Hey @shahwan42 ,

We merged a big style change, nothing important but replaced all single quotes by double quotes for strings. This created tons of conflict so you might have some difficulties rebase your PR.

I'd recommend doing it this way:

  • squash all commits from your branch (we'll do this on merge any way)
  • rebase on top of main
  • to resolve all conflicts, take your changes: git checkout --theirs .
  • continue rebase
  • Then run bundle exec rubocop -x --only Style/StringLiterals
  • commit

I can do it for you if you prefer!

feat(moneyhash): generate graphql schema for moneyhash

fix(moneyhash) customer creation

fix(moneyhash): webhook

feat(moneyhash): update required fields for connection creation/update

feat(moneyhash): build webhook_end_point based on LAGO_API_URL

feat(moneyhash): Use flow_id if exists

fix(moneyhash): correct amount before sending it

feat(moneyhash): remove redirect_url

cleanup: remove commented code

feat(moneyhash): make flow_id required during connection creation

feat(moneyhash): Only use flow_id instead of operation

feat(moneyhash): add more to custom_fields

feat(moneyhash): Implement moneyhash/payments/create_service

fix(moneyhash): get agreement_id from subscription_external_id

fix(payment_providers): possible types

regenerate graphql schema

fix formatting according to rubocop

revert unrelated change

shorten Types::Customers::Object#provider_customer

better refactor Types::Customers::Object#provider_customer

feat(moneyhash): put-back-success-url-field

Expected by code shared between providers

feat(moneyhash): revert a refactor, disable rubocop rule

cleanup: remove unncessary newline

remove potential duplicate email calls

Send email when can't create a payment, too

Fix payment_url for one-off invoices

Remove expire_after_seconds for invoice payments

Revamp PaymentProviders::Moneyhash::Payments::CreateService
@shahwan42
Copy link
Author

Hi @julienbourdeau
Thank you for the review, I handled your comments. Please take a look again once you can. Appreciated.

For the Moneyhash::Payments::CreateService part, since it's a different payment scenario, I've introduced new logic to handle the payment in this case. However, I'm not sure how to test it yet since we're only supporting automatic payments for subscription invoices only. Do you know how to make this scenario work by hand or using commands?

There is some duplicate code since I have to move slowly and carefully because I'm still getting my hands into Ruby/Rails, and will improve it iteratively. Appreciate your effort and patience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Contribution Contributions from the Lago Community Feature Add a new feature to the app Payment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants