Skip to content

feat(payments)!: replace frappe/payments with bwh_payments - #557

Open
harshtandiya wants to merge 8 commits into
developfrom
feat/bwh-payments
Open

harshtandiya wants to merge 8 commits into
developfrom
feat/bwh-payments

Conversation

@harshtandiya

Copy link
Copy Markdown
Collaborator

What changed

Buzz takes payments through bwh_payments (v1-stable) instead of frappe/payments, and no longer names a payment provider in its code.

  • Payment links, confirmation and refunds go through bwh_payments.api. Refunds now work for every gateway bwh_payments runs (Stripe, Razorpay, Telr, Tabby), not only Razorpay. A refund raised on a provider's dashboard is still recorded with the tickets it covers.
  • The booking confirmation page shows "Booking Confirmed" only for a paid booking (otherwise "Payment Pending" with a "Check again" button). The sponsorship page celebrates only once the enquiry is Paid. bwh_payments confirms the payment with the gateway before it sends the shopper back.
  • Gateway logos come from bwh_payments; the provider-keyed logo map is gone.
  • A patch moves the payment history: each Event Payment and Event Booking Refund gets a bwh_payments record, and existing fields are left unchanged. Gateways keep their names, so links stay valid, and old Razorpay payments stay refundable.

Decisions worth a look:

  • Event Payment stays as Buzz's own record and gains a payment_request link.
  • Paymob and PayPal are not supported by bwh_payments. Their history is kept under disabled gateways.
  • Tests run the real payment and refund code on bwh_payments' in-memory test gateway (bwh_payments.testing) instead of mocking a controller.

Demo

TODO: screenshot of the "Payment Pending" state on /b/booking-success/<id>.

Testing

  • bench run-tests --app buzz: 1126 tests. The 6 errors are in event-template and event-proposal tests and fail the same way on develop.
  • New: paying submits the booking, sponsorship payment creates the sponsor, refund webhooks (pending → processed/failed, raised outside Buzz), sync_refunds, the history patch (idempotent, keeps old fields, skips orphan rows).
  • yarn typecheck, yarn lint, yarn test:unit pass.
  • Not done: a live end-to-end payment with test keys.

Setup per site

What Where Value
Install bwh_payments bench bench get-app --branch v1-stable https://github.com/bwhtech/bwh_payments, then install-app bwh_payments before migrate
Webhook URL Razorpay / Stripe dashboard https://<site>/api/method/bwh_payments.bwh_payments.webhook.handle?gateway=<profile name>
Webhook events Razorpay payment_link.paid, refund.created, refund.processed, refund.failed
Webhook events Stripe checkout.session.completed, refund.created, refund.updated, refund.failed
Webhook secret <Gateway> Gateway Settings the secret from that webhook (Stripe has none to import)
Uninstall frappe/payments bench in a later update, after this migration has run

Buzz now talks to bwh_payments' API instead of frappe/payments, and no longer
names a payment provider anywhere in its code.

- Payment links: bwh_payments.api.create_payment opens the checkout; the
  per-gateway branches (Razorpay/Paymob order creation, the payment id key per
  provider read from Integration Request data) are gone. A currency the
  gateway cannot charge is refused before a checkout opens, and a disabled
  gateway is never offered. Event Payment links the Gateway Payment Request.
- Confirmation: on_payment_authorized marks the Event Payment received from the
  confirmed Gateway Payment Request (no more mid-request commit or in_test skip).
- Refunds: work for every gateway bwh_payments runs, not only Razorpay.
  Event Booking Refund links the bwh_payments Gateway Refund; the new
  on_refund_update callback replaces the frappe/payments-fork
  handle_refund_notification hook and its Razorpay-shaped payload, and amounts
  are major units (no more /100). Refunds raised on a provider's dashboard are
  still recorded, with the tickets they cover. sync_refunds asks the provider
  about refunds still pending.
- Desk refund messages no longer name Razorpay or assume rupees.
- Links that pointed at frappe/payments' Payment Gateway now point at
  bwh_payments' Payment Gateway Profile.
- Tests run the real payment and refund code on bwh_payments' in-memory test
  gateway instead of mocking a controller; the provider-bound factories are gone.
- CI installs bwh_payments (v1-stable) instead of frappe/payments.

BREAKING CHANGE: sites need bwh_payments installed; frappe/payments is no longer
required.
…ents

A post-model-sync patch gives every existing payment and refund a bwh_payments
record, so old bookings stay refundable and their history stays readable. It
keeps every existing field; rows only gain a link.

- Gateways: bwh_payments imports each frappe/payments Payment Gateway as a
  Payment Gateway Profile with the same name (copying Razorpay/Stripe keys), so
  the gateway links on events, templates, settings and payments stay valid. A
  gateway no longer listed anywhere gets a disabled profile.
- Event Payment: a Gateway Payment Request per row, Paid when received and
  Expired otherwise, keyed on order_id/payment_id, with payment_id as the
  transaction ref (old Razorpay payments refund against it).
- Event Booking Refund: a Gateway Refund per row with its refund id, status
  and amount, referencing the refund. Nothing is settled again: no booking is
  resubmitted and no ticket is cancelled.
- Idempotent, and a row that cannot move (e.g. its booking was deleted) is
  logged and skipped without stopping the migration.

frappe/payments must still be installed when this runs; uninstall it in a later
update.
…ce paid

- Gateway logos now come from bwh_payments through the new guest endpoint
  buzz.api.payments.get_payment_gateway_logos. The dashboard's provider-keyed
  logo map (and its "Stripe-Main" name parsing) and buzz's copy of the
  Razorpay logos are gone, so any gateway bwh_payments runs shows its logo.
- The booking confirmation page said "Booking Confirmed" and fired confetti for
  anyone who landed on it. It now confirms only a paid (or free) booking;
  otherwise it shows "Payment Pending" with a "Check again" button.
- The sponsorship page celebrates a return from payment only once the enquiry
  is actually Paid, instead of on ?success=true alone.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

❌ UI Demo Check failed

This pull request changes the UI (9 file(s) under dashboard/src/),
but the description has no screenshot or demo. Reviewers should be able to see the
change without checking out the branch.

🛠️ How to fix

  • Edit the description and drag a screenshot or a short screen recording into it.
    Before/after images are ideal for visual tweaks. A Loom or Cap link works too.
  • Or apply the skip-demo label if a visual makes no sense here
    (pure refactor, copy change, dependency bump).

Either one re-runs this check automatically.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Backend test coverage: 92%

Commit c025247. Test modules are excluded.

Coverage by file
Name Stmts Miss Cover
buzz/__init__.py 5 5 0%
buzz/api/account/__init__.py 30 2 93%
buzz/api/account/exceptions.py 8 0 100%
buzz/api/account/schemas.py 4 0 100%
buzz/api/account/services.py 15 1 93%
buzz/api/auth/__init__.py 11 0 100%
buzz/api/auth/schemas.py 3 0 100%
buzz/api/booking/__init__.py 30 1 97%
buzz/api/booking/coupons.py 30 3 90%
buzz/api/booking/details.py 93 7 92%
buzz/api/booking/event_data.py 42 4 90%
buzz/api/booking/exceptions.py 21 0 100%
buzz/api/booking/guests.py 71 10 86%
buzz/api/booking/my_bookings.py 9 3 67%
buzz/api/booking/schemas.py 38 0 100%
buzz/api/booking/services.py 212 19 91%
buzz/api/campaigns/__init__.py 34 19 44%
buzz/api/campaigns/exceptions.py 11 0 100%
buzz/api/campaigns/schemas.py 2 0 100%
buzz/api/checkin/__init__.py 9 0 100%
buzz/api/checkin/exceptions.py 14 0 100%
buzz/api/checkin/schemas.py 8 0 100%
buzz/api/checkin/services.py 62 1 98%
buzz/api/communications/__init__.py 16 0 100%
buzz/api/communications/exceptions.py 11 0 100%
buzz/api/communications/schemas.py 6 0 100%
buzz/api/communications/services.py 65 1 98%
buzz/api/communities/__init__.py 49 0 100%
buzz/api/communities/exceptions.py 14 0 100%
buzz/api/communities/notifications.py 34 2 94%
buzz/api/communities/queries.py 36 0 100%
buzz/api/communities/reviews.py 39 1 97%
buzz/api/communities/schemas.py 13 0 100%
buzz/api/communities/submissions.py 54 1 98%
buzz/api/events/__init__.py 49 0 100%
buzz/api/events/exceptions.py 20 0 100%
buzz/api/events/schemas.py 85 0 100%
buzz/api/events/services.py 221 13 94%
buzz/api/events/taxes.py 19 0 100%
buzz/api/events/ticket_types.py 43 0 100%
buzz/api/events/zoom.py 15 1 93%
buzz/api/exceptions.py 23 0 100%
buzz/api/filters/conditions.py 106 2 98%
buzz/api/filters/exceptions.py 5 0 100%
buzz/api/filters/schemas.py 6 0 100%
buzz/api/forms/__init__.py 24 0 100%
buzz/api/forms/answers.py 77 14 82%
buzz/api/forms/exceptions.py 27 0 100%
buzz/api/forms/fields.py 82 3 96%
buzz/api/forms/schemas.py 7 0 100%
buzz/api/forms/services.py 119 1 99%
buzz/api/maps/__init__.py 16 0 100%
buzz/api/maps/exceptions.py 11 0 100%
buzz/api/maps/schemas.py 9 0 100%
buzz/api/maps/services.py 57 0 100%
buzz/api/payments/__init__.py 14 1 93%
buzz/api/payments/currencies.py 7 2 71%
buzz/api/payments/schemas.py 3 0 100%
buzz/api/proposals/__init__.py 18 0 100%
buzz/api/proposals/exceptions.py 5 0 100%
buzz/api/proposals/schemas.py 26 0 100%
buzz/api/proposals/services.py 104 1 99%
buzz/api/schemas.py 13 0 100%
buzz/api/sponsorships/__init__.py 41 0 100%
buzz/api/sponsorships/enquiries.py 74 2 97%
buzz/api/sponsorships/exceptions.py 22 0 100%
buzz/api/sponsorships/forms.py 64 2 97%
buzz/api/sponsorships/manage.py 21 1 95%
buzz/api/sponsorships/schemas.py 20 0 100%
buzz/api/sponsorships/services.py 67 1 99%
buzz/api/sponsorships/sponsors.py 29 0 100%
buzz/api/tags/__init__.py 9 0 100%
buzz/api/tags/desk.py 35 9 74%
buzz/api/tags/exceptions.py 8 0 100%
buzz/api/tags/schemas.py 2 0 100%
buzz/api/tags/services.py 56 0 100%
buzz/api/teams/__init__.py 47 2 96%
buzz/api/teams/calendar.py 28 0 100%
buzz/api/teams/exceptions.py 24 0 100%
buzz/api/teams/invitations.py 34 0 100%
buzz/api/teams/public_page.py 26 15 42%
buzz/api/teams/schemas.py 14 0 100%
buzz/api/teams/services.py 100 1 99%
buzz/api/tickets/__init__.py 15 0 100%
buzz/api/tickets/emails.py 14 9 36%
buzz/api/tickets/exceptions.py 29 0 100%
buzz/api/tickets/schemas.py 4 0 100%
buzz/api/tickets/services.py 85 0 100%
buzz/api/tickets/windows.py 12 0 100%
buzz/buzz/doctype/buzz_custom_field/buzz_custom_field.py 20 4 80%
buzz/buzz/web_form/apply_for_sponsorship/apply_for_sponsorship.py 3 3 0%
buzz/buzz/web_form/propose_a_talk/propose_a_talk.py 3 3 0%
buzz/buzz_marketing/doctype/buzz_campaign/buzz_campaign.py 21 7 67%
buzz/emails.py 29 3 90%
buzz/events/banner_pattern.py 35 0 100%
buzz/events/doctype/additional_event_page/additional_event_page.py 15 1 93%
buzz/events/doctype/buzz_event/buzz_event.py 267 23 91%
buzz/events/doctype/buzz_event_form/buzz_event_form.py 11 4 64%
buzz/events/doctype/buzz_form_field/buzz_form_field.py 3 0 100%
buzz/events/doctype/buzz_price/buzz_price.py 10 0 100%
buzz/events/doctype/buzz_settings/buzz_settings.py 13 2 85%
buzz/events/doctype/buzz_tag/buzz_tag.py 30 2 93%
buzz/events/doctype/buzz_tag_link/buzz_tag_link.py 14 2 86%
buzz/events/doctype/buzz_team/buzz_team.py 75 3 96%
buzz/events/doctype/buzz_team/team_map.py 23 0 100%
buzz/events/doctype/buzz_team/team_page.py 74 2 97%
buzz/events/doctype/buzz_team_membership/buzz_team_membership.py 59 0 100%
buzz/events/doctype/buzz_team_settings/buzz_team_settings.py 44 0 100%
buzz/events/doctype/buzz_theme/buzz_theme.py 42 2 95%
buzz/events/doctype/buzz_theme/tokens.py 29 1 97%
buzz/events/doctype/buzz_theme_token/buzz_theme_token.py 3 0 100%
buzz/events/doctype/community_event_request/community_event_request.py 56 12 79%
buzz/events/doctype/event_category/event_category.py 9 0 100%
buzz/events/doctype/event_check_in/event_check_in.py 10 1 90%
buzz/events/doctype/event_cohost/event_cohost.py 4 0 100%
buzz/events/doctype/event_communication/event_communication.py 16 0 100%
buzz/events/doctype/event_external_link/event_external_link.py 4 0 100%
buzz/events/doctype/event_featured_speaker/event_featured_speaker.py 4 0 100%
buzz/events/doctype/event_feedback/event_feedback.py 4 4 0%
buzz/events/doctype/event_host/event_host.py 4 0 100%
buzz/events/doctype/event_payment_gateway/event_payment_gateway.py 4 0 100%
buzz/events/doctype/event_sponsor/event_sponsor.py 15 2 87%
buzz/events/doctype/event_talk/event_talk.py 8 0 100%
buzz/events/doctype/event_template/event_template.py 38 4 89%
buzz/events/doctype/event_template_add_on/event_template_add_on.py 4 0 100%
buzz/events/doctype/event_template_custom_field/event_template_custom_field.py 4 0 100%
buzz/events/doctype/event_template_ticket_type/event_template_ticket_type.py 4 0 100%
buzz/events/doctype/event_track/event_track.py 4 4 0%
buzz/events/doctype/event_user_preferences/event_user_preferences.py 4 4 0%
buzz/events/doctype/event_venue/event_venue.py 53 1 98%
buzz/events/doctype/event_venue/geocode.py 69 9 87%
buzz/events/doctype/event_venue/map_link.py 106 5 95%
buzz/events/doctype/offline_payment_method/offline_payment_method.py 11 0 100%
buzz/events/doctype/schedule_item/schedule_item.py 4 0 100%
buzz/events/doctype/social_media_link/social_media_link.py 3 0 100%
buzz/events/doctype/speaker_profile/speaker_profile.py 8 1 88%
buzz/events/doctype/sponsor_enquiry_form/sponsor_enquiry_form.py 54 5 91%
buzz/events/doctype/sponsorship_tier/sponsorship_tier.py 27 0 100%
buzz/events/doctype/talk_speaker/talk_speaker.py 3 0 100%
buzz/events/doctype/utm_parameter/utm_parameter.py 4 0 100%
buzz/events/og_image.py 141 7 95%
buzz/events/og_text.py 66 1 98%
buzz/events/online_meeting.py 42 1 98%
buzz/events/report/event_attendance_summary/event_attendance_summary.py 61 61 0%
buzz/events/report/event_overview/event_overview.py 25 25 0%
buzz/hooks.py 31 0 100%
buzz/install.py 60 28 53%
buzz/integrations/zoom.py 3 3 0%
buzz/islands.py 10 5 50%
buzz/payments.py 58 10 83%
buzz/permissions.py 137 12 91%
buzz/proposals/doctype/event_proposal/event_proposal.py 71 5 93%
buzz/proposals/doctype/proposal_speaker/proposal_speaker.py 4 0 100%
buzz/proposals/doctype/sponsorship_deck_item/sponsorship_deck_item.py 4 0 100%
buzz/proposals/doctype/sponsorship_enquiry/sponsorship_enquiry.py 81 5 94%
buzz/proposals/doctype/talk_proposal/talk_proposal.py 92 6 93%
buzz/proposals/doctype/talk_proposal_status/talk_proposal_status.py 4 0 100%
buzz/tasks.py 4 4 0%
buzz/telemetry.py 46 0 100%
buzz/telemetry_scan.py 55 8 85%
buzz/ticketing/doctype/additional_field/additional_field.py 4 0 100%
buzz/ticketing/doctype/attendee_ticket_add_on/attendee_ticket_add_on.py 3 0 100%
buzz/ticketing/doctype/buzz_coupon_code/buzz_coupon_code.py 99 4 96%
buzz/ticketing/doctype/coupon_free_add_on/coupon_free_add_on.py 3 0 100%
buzz/ticketing/doctype/event_booking/event_booking.py 350 20 94%
buzz/ticketing/doctype/event_booking_attendee/event_booking_attendee.py 20 7 65%
buzz/ticketing/doctype/event_booking_refund/event_booking_refund.py 62 1 98%
buzz/ticketing/doctype/event_booking_refund_ticket/event_booking_refund_ticket.py 4 0 100%
buzz/ticketing/doctype/event_payment/event_payment.py 4 0 100%
buzz/ticketing/doctype/event_ticket/event_ticket.py 80 10 88%
buzz/ticketing/doctype/event_ticket_type/event_ticket_type.py 69 0 100%
buzz/ticketing/doctype/ticket_add_on/ticket_add_on.py 9 1 89%
buzz/ticketing/doctype/ticket_add_on_value/ticket_add_on_value.py 3 0 100%
buzz/ticketing/doctype/ticket_cancellation_item/ticket_cancellation_item.py 4 0 100%
buzz/ticketing/doctype/ticket_cancellation_request/ticket_cancellation_request.py 19 1 95%
buzz/ticketing/report/detailed_event_registrations/detailed_event_registrations.py 111 5 95%
buzz/ticketing/report/event_add_ons_overview/event_add_ons_overview.py 20 20 0%
buzz/uninstall.py 10 2 80%
buzz/utils.py 138 22 84%
buzz/www/dashboard.py 24 3 88%
buzz/www/event/date_range.py 42 5 88%
buzz/www/event/index.py 151 6 96%
buzz/www/event/link_icons.py 1 0 100%
buzz/www/event/meta.py 65 4 94%
buzz/www/event/venue_map.py 35 1 97%
buzz/www/events.py 69 6 91%
buzz/www/site_header.py 25 0 100%
TOTAL 6970 573 92%

91 empty files skipped.

The booking call returns the checkout URL before the gateway page loads,
so the button came back while the browser was still navigating. Reset on
a back-forward cache restore so going back does not leave it stuck.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

useCall runs onSuccess before it sets data, so the paid check read null
and skipped the celebration. Check the response passed to onSuccess.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

…unds

Each PaymentGatewayLogo made its own request; one shared call now serves
every logo. sync_refunds returns only the pending count Desk reads.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

A pending refund after a settled partial one left the booking at
Partially Refunded, so the Desk alert claimed the refund had settled.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your trial has ended. Reactivate Greptile to resume code reviews.

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.

1 participant