From 1e1e8176290ac3112e31b691075015de21c66b0a Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Mon, 6 Apr 2026 13:03:08 -0300 Subject: [PATCH 1/8] feat: invoice payment - WIP --- src/actions/sponsor-cart-actions.js | 61 +++++++ src/i18n/en.json | 5 + .../sponsor-cart-tab/components/cart-view.js | 162 +++++++++--------- .../components/invoice-view.js | 48 ++++++ .../tabs/sponsor-cart-tab/index.js | 33 +++- .../sponsor-page-cart-list-reducer.js | 12 +- 6 files changed, 235 insertions(+), 86 deletions(-) create mode 100644 src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index f606db371..811a7c3c9 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -45,6 +45,8 @@ export const FORM_CART_SAVED = "FORM_CART_SAVED"; export const SPONSOR_CART_NOTE_ADDED = "SPONSOR_CART_NOTE_ADDED"; export const SPONSOR_CART_NOTE_UPDATED = "SPONSOR_CART_NOTE_UPDATED"; export const SPONSOR_CART_NOTE_DELETED = "SPONSOR_CART_NOTE_DELETED"; +export const OFFLINE_PAYMENT_CREATED = "OFFLINE_PAYMENT_CREATED"; +export const CART_STATUS_UPDATED = "CART_STATUS_UPDATED"; const customErrorHandler = (err, res) => (dispatch, state) => { const code = err.status; @@ -443,3 +445,62 @@ export const deleteSponsorCartNote = (noteId) => async (dispatch, getState) => { dispatch(stopLoading()); }); }; + +/* ************************************************************************* */ +/* PAYMENTS */ +/* ************************************************************************* */ + +export const checkoutCart = () => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return putRequest( + null, + createAction(CART_STATUS_UPDATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`, + {}, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); +}; + +export const payWithInvoice = () => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState, sponsorPageCartListState } = + getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const { cart } = sponsorPageCartListState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + const payload = { + type: "Offline", + cart_id: cart.id + }; + + return postRequest( + null, + createAction(OFFLINE_PAYMENT_CREATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments`, + payload, + snackbarErrorHandler + )(params)(dispatch) + .then(() => { + getSponsorCart()(dispatch, getState); + }) + .catch(console.log); +}; diff --git a/src/i18n/en.json b/src/i18n/en.json index 1663df0ac..c9c5f5259 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -51,6 +51,7 @@ "save": "Save", "confirm": "Confirm", "cancel": "Cancel", + "return": "Return", "ingest": "Ingest", "save_and_publish": "Save & Publish", "save_and_add_next": "Save & Add Next", @@ -2557,6 +2558,10 @@ "title": "Order Notes", "placeholder": "Enter internal note...", "deleted": "Note deleted successfully." + }, + "invoice_view": { + "title": "Purchase confirmed", + "subtitle": "The administrators will contact you to complete the payment." } }, "purchase_tab": { diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js index d7b1a8680..c5eb5d726 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js @@ -29,11 +29,11 @@ import MuiTable, { TotalRow } from "openstack-uicore-foundation/lib/components/m import SearchInput from "../../../../../../components/mui/search-input"; import { deleteSponsorCartForm, + deleteSponsorCartNote, getSponsorCart, lockSponsorCartForm, - unlockSponsorCartForm, saveSponsorCartNote, - deleteSponsorCartNote + unlockSponsorCartForm } from "../../../../../../actions/sponsor-cart-actions"; import CartNote from "./cart-note"; import { SPONSOR_CART_NOTE_TYPES } from "../../../../../../utils/constants"; @@ -48,7 +48,9 @@ const CartView = ({ onEdit, onAddForm, saveSponsorCartNote, - deleteSponsorCartNote + deleteSponsorCartNote, + onPayCC, + onPayInvoice }) => { useEffect(() => { getSponsorCart(); @@ -75,11 +77,11 @@ const CartView = ({ }; const handlePayCreditCard = () => { - console.log("PAY CREDIT CARD"); + onPayCC(); }; const handlePayInvoice = () => { - console.log("PAY INVOICE"); + onPayInvoice(); }; const cartData = cart?.forms.map((form) => ({ @@ -180,88 +182,90 @@ const CartView = ({ {!cart && ( - + {T.translate("edit_sponsor.cart_tab.no_cart")} )} {!!cart && ( - - - T.translate("edit_sponsor.cart_tab.delete_form_confirm", { - form: formName ?? "" - }) - } - confirmButtonColor="error" - > - + + - - - - - - + + + + + n.type === SPONSOR_CART_NOTE_TYPES.SPONSOR + )} + placeholder={T.translate( + "edit_sponsor.cart_tab.sponsor_note.placeholder" + )} + onSave={(note) => + saveSponsorCartNote(note, SPONSOR_CART_NOTE_TYPES.SPONSOR) + } + onDelete={deleteSponsorCartNote} + /> + n.type === SPONSOR_CART_NOTE_TYPES.INTERNAL + )} + placeholder={T.translate( + "edit_sponsor.cart_tab.order_note.placeholder" + )} + onSave={(note) => + saveSponsorCartNote(note, SPONSOR_CART_NOTE_TYPES.INTERNAL) + } + onDelete={deleteSponsorCartNote} + multiple + /> + )} - n.type === SPONSOR_CART_NOTE_TYPES.SPONSOR - )} - placeholder={T.translate( - "edit_sponsor.cart_tab.sponsor_note.placeholder" - )} - onSave={(note) => - saveSponsorCartNote(note, SPONSOR_CART_NOTE_TYPES.SPONSOR) - } - onDelete={deleteSponsorCartNote} - /> - n.type === SPONSOR_CART_NOTE_TYPES.INTERNAL - )} - placeholder={T.translate( - "edit_sponsor.cart_tab.order_note.placeholder" - )} - onSave={(note) => - saveSponsorCartNote(note, SPONSOR_CART_NOTE_TYPES.INTERNAL) - } - onDelete={deleteSponsorCartNote} - multiple - /> ); }; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js new file mode 100644 index 000000000..2e4bd11cd --- /dev/null +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js @@ -0,0 +1,48 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { Box, Button, Card, CardContent, Typography } from "@mui/material"; + +const InvoiceView = ({ onCancel }) => ( + + + + + + {T.translate("edit_sponsor.cart_tab.invoice_view.title")} + + + {T.translate("edit_sponsor.cart_tab.invoice_view.subtitle")} + + + + + + +); + +const mapStateToProps = ({ showAccessState }) => ({ + ...showAccessState +}); + +export default connect(mapStateToProps, {})(InvoiceView); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js index 742342ab0..8cd34ef64 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 OpenStack Foundation + * Copyright 2026 OpenStack Foundation * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,17 +12,26 @@ * */ import React, { useState } from "react"; -import { Box } from "@mui/material"; import { connect } from "react-redux"; +import { Box } from "@mui/material"; import SelectFormDialog from "./components/select-form-dialog"; import CartView from "./components/cart-view"; import NewCartForm from "./components/edit-form/new-cart-form"; import EditCartForm from "./components/edit-form/edit-cart-form"; +import InvoiceView from "./components/invoice-view"; +import { + checkoutCart, + payWithInvoice +} from "../../../../../actions/sponsor-cart-actions"; -const SponsorCartTab = ({ sponsor, currentSummit }) => { +const SponsorCartTab = ({ sponsor, currentSummit, checkoutCart, payWithInvoice }) => { const [openAddFormDialog, setOpenAddFormDialog] = useState(false); const [formEdit, setFormEdit] = useState(null); const [newForm, setNewForm] = useState(null); + const [invoiceView, setInvoiceView] = useState(null); + const [payCCView, setPayCCView] = useState(null); + + const showCartView = !formEdit && !newForm && !invoiceView && !payCCView; const handleFormSelected = (form, addOn) => { setNewForm({ formId: form.id, addon: addOn }); @@ -37,6 +46,14 @@ const SponsorCartTab = ({ sponsor, currentSummit }) => { setFormEdit(null); }; + const handlePayInvoice = () => { + checkoutCart().then(() => { + payWithInvoice().then(() => { + setInvoiceView(true); + }); + }); + }; + return ( {newForm && ( @@ -57,10 +74,13 @@ const SponsorCartTab = ({ sponsor, currentSummit }) => { onSaveCallback={handleOnFormUpdated} /> )} - {!formEdit && !newForm && ( + {invoiceView && setInvoiceView(null)} />} + {showCartView && ( setOpenAddFormDialog(true)} + onPayCC={() => setPayCCView(true)} + onPayInvoice={handlePayInvoice} /> )} ({ sponsor: currentSponsorState.entity }); -export default connect(mapStateToProps, {})(SponsorCartTab); +export default connect(mapStateToProps, { + checkoutCart, + payWithInvoice +})(SponsorCartTab); diff --git a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js index ffe84acd7..4825dcf18 100644 --- a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js @@ -30,7 +30,9 @@ import { SPONSOR_CART_FORM_LOCKED, SPONSOR_CART_NOTE_ADDED, SPONSOR_CART_NOTE_DELETED, - SPONSOR_CART_NOTE_UPDATED + SPONSOR_CART_NOTE_UPDATED, + OFFLINE_PAYMENT_CREATED, + CART_STATUS_UPDATED } from "../../actions/sponsor-cart-actions"; import { DISCOUNT_TYPES } from "../../utils/constants"; @@ -48,7 +50,8 @@ const DEFAULT_STATE = { orderDir: 1 }, sponsorForm: null, - cartForm: null + cartForm: null, + offlinePayment: null }; const mapForm = (formData) => ({ @@ -74,6 +77,7 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { summitTZ }; } + case CART_STATUS_UPDATED: case RECEIVE_SPONSOR_CART: { const cart = payload.response; cart.forms = cart.forms.map((form) => { @@ -217,6 +221,10 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { } }; } + case OFFLINE_PAYMENT_CREATED: { + const offlinePayment = payload.response; + return { ...state, offlinePayment }; + } default: return state; } From b14d30ac62bb4c875a3c307c43dcae0c339e9f84 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 7 Apr 2026 19:54:14 -0300 Subject: [PATCH 2/8] fix: adjust to new routes schema --- src/i18n/en.json | 2 +- src/pages/sponsors/sponsor-page/tabDefs.js | 1 - .../sponsor-cart-tab/components/cart-view.js | 23 +++- .../__tests__/edit-cart-form.test.js | 42 ++++--- .../components/edit-form/edit-cart-form.js | 28 +++-- .../components/invoice-view.js | 79 +++++++++---- .../components/payment-view.js | 28 +++++ .../tabs/sponsor-cart-tab/index.js | 108 +++++++++--------- 8 files changed, 201 insertions(+), 110 deletions(-) create mode 100644 src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js diff --git a/src/i18n/en.json b/src/i18n/en.json index c9c5f5259..11ab38719 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2561,7 +2561,7 @@ }, "invoice_view": { "title": "Purchase confirmed", - "subtitle": "The administrators will contact you to complete the payment." + "go_to_orders": "Go to orders" } }, "purchase_tab": { diff --git a/src/pages/sponsors/sponsor-page/tabDefs.js b/src/pages/sponsors/sponsor-page/tabDefs.js index aea4dcc45..1ccb7d88f 100644 --- a/src/pages/sponsors/sponsor-page/tabDefs.js +++ b/src/pages/sponsors/sponsor-page/tabDefs.js @@ -71,7 +71,6 @@ export const SPONSOR_PAGE_TABS = [ { labelKey: "edit_sponsor.tab.cart", path: "/cart", - exact: true, component: SponsorCartTab, accessRoute: ACCESS_ROUTES.ADMIN_SPONSORS }, diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js index c5eb5d726..4fa5b95c7 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js @@ -26,12 +26,15 @@ import AddIcon from "@mui/icons-material/Add"; import LockOpenIcon from "@mui/icons-material/LockOpen"; import LockClosedIcon from "@mui/icons-material/Lock"; import MuiTable, { TotalRow } from "openstack-uicore-foundation/lib/components/mui/table"; +import history from "../../../../../../history"; import SearchInput from "../../../../../../components/mui/search-input"; import { + checkoutCart, deleteSponsorCartForm, deleteSponsorCartNote, getSponsorCart, lockSponsorCartForm, + payWithInvoice, saveSponsorCartNote, unlockSponsorCartForm } from "../../../../../../actions/sponsor-cart-actions"; @@ -45,12 +48,12 @@ const CartView = ({ deleteSponsorCartForm, lockSponsorCartForm, unlockSponsorCartForm, - onEdit, onAddForm, saveSponsorCartNote, deleteSponsorCartNote, onPayCC, - onPayInvoice + checkoutCart, + payWithInvoice }) => { useEffect(() => { getSponsorCart(); @@ -68,6 +71,10 @@ const CartView = ({ console.log("MANAGE ITEMS : ", item); }; + const handleEditForm = (form) => { + history.push(`cart/forms/${form.id}`); + }; + const handleLock = (form) => { if (form.is_locked) { unlockSponsorCartForm(form.id); @@ -81,7 +88,11 @@ const CartView = ({ }; const handlePayInvoice = () => { - onPayInvoice(); + checkoutCart().then(() => { + payWithInvoice().then(() => { + history.push("cart/invoice"); + }); + }); }; const cartData = cart?.forms.map((form) => ({ @@ -193,7 +204,7 @@ const CartView = ({ columns={tableColumns} data={cartData} options={{}} - onEdit={onEdit} + onEdit={handleEditForm} onDelete={handleDelete} deleteDialogBody={(formName) => T.translate("edit_sponsor.cart_tab.delete_form_confirm", { @@ -280,5 +291,7 @@ export default connect(mapStateToProps, { lockSponsorCartForm, unlockSponsorCartForm, saveSponsorCartNote, - deleteSponsorCartNote + deleteSponsorCartNote, + checkoutCart, + payWithInvoice })(CartView); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js index 967e2303f..2f7cf4ad5 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/__tests__/edit-cart-form.test.js @@ -16,6 +16,15 @@ jest.mock("../../../../../../../../actions/sponsor-cart-actions", () => ({ })); // Mock foundation components used by EditForm + +// Mock history +const mockHistoryPush = jest.fn(); +jest.mock("../../../../../../../../history", () => ({ + __esModule: true, + default: { push: (...args) => mockHistoryPush(...args) } +})); + +// Mock sub-components used by FormItemTable jest.mock( "openstack-uicore-foundation/lib/components/mui/form-item-table", () => { @@ -155,8 +164,16 @@ const mockCartForm = { ] }; +const buildMatch = (formId, url) => ({ + params: { form_id: formId }, + url: url || `/app/events/1/sponsors/2/cart/forms/${formId}/edit` +}); + // Helper function to render the component with Redux store -const renderWithStore = (props, storeState = {}) => { +const renderWithStore = ( + { formId = 1, ...restProps } = {}, + storeState = {} +) => { const defaultState = { sponsorPageCartListState: { cartForm: @@ -176,16 +193,9 @@ const renderWithStore = (props, storeState = {}) => { const store = mockStore(defaultState); - const defaultProps = { - formId: 1, - onCancel: jest.fn(), - onSaveCallback: jest.fn(), - ...props - }; - return render( - + ); }; @@ -281,9 +291,8 @@ describe("EditCartForm", () => { }); describe("Cancel Functionality", () => { - test("clicking CANCEL returns to cart tab", async () => { - const onCancel = jest.fn(); - renderWithStore({ onCancel }); + test("clicking CANCEL navigates back", async () => { + renderWithStore(); await waitFor(() => { expect(screen.getByText(/general.cancel/)).toBeInTheDocument(); @@ -292,7 +301,7 @@ describe("EditCartForm", () => { const cancelButton = screen.getByText(/general.cancel/); await userEvent.click(cancelButton); - expect(onCancel).toHaveBeenCalledTimes(1); + expect(mockHistoryPush).toHaveBeenCalledTimes(1); }); }); @@ -352,11 +361,10 @@ describe("EditCartForm", () => { }); }); - test("on success shows snackbar + returns to tab + triggers refresh", async () => { - const onSaveCallback = jest.fn(); + test("on success navigates back to cart tab", async () => { mockUpdateCartForm.mockReturnValue(() => Promise.resolve()); - renderWithStore({ formId: 123, onSaveCallback }); + renderWithStore({ formId: 123 }); await waitFor(() => { expect(screen.getByText(/general.save/)).toBeInTheDocument(); @@ -367,7 +375,7 @@ describe("EditCartForm", () => { await waitFor(() => { expect(mockUpdateCartForm).toHaveBeenCalled(); - expect(onSaveCallback).toHaveBeenCalledTimes(1); + expect(mockHistoryPush).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js index 34225b10b..c06219a84 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js @@ -17,29 +17,43 @@ import { getSponsorCartForm, updateCartForm } from "../../../../../../../actions/sponsor-cart-actions"; +import history from "../../../../../../../history"; import EditForm from "./index"; const EditCartForm = ({ - formId, + match, cartForm, - onCancel, - onSaveCallback, getSponsorCartForm, updateCartForm }) => { + const formId = match.params.form_id; + useEffect(() => { - getSponsorCartForm(formId); - }, []); + if (formId) getSponsorCartForm(formId); + }, [formId]); + + const backToCart = () => { + const backUrl = match.url + .split("/") + .filter((segment) => segment.length > 0) + // eslint-disable-next-line no-magic-numbers + .slice(0, -2) + .join("/"); + + history.push(`/${backUrl}`); + }; const saveForm = (values) => { updateCartForm(formId, values).then(() => { - onSaveCallback(); + backToCart(); }); }; if (!cartForm) return null; - return ; + return ( + + ); }; const mapStateToProps = ({ sponsorPageCartListState }) => ({ diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js index 2e4bd11cd..e88bf73c4 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js @@ -15,31 +15,64 @@ import React from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; import { Box, Button, Card, CardContent, Typography } from "@mui/material"; +import history from "../../../../../../history"; -const InvoiceView = ({ onCancel }) => ( - - - - - - {T.translate("edit_sponsor.cart_tab.invoice_view.title")} - - - {T.translate("edit_sponsor.cart_tab.invoice_view.subtitle")} - - +const InvoiceView = ({ match }) => { + const rootUrl = match.url + .split("/") + .filter((segment) => segment.length > 0) + // eslint-disable-next-line no-magic-numbers + .slice(0, -2) + .join("/"); + + const handleCancel = () => { + history.push(`/${rootUrl}/cart`); + }; + + const handleToOrders = () => { + history.push(`/${rootUrl}/purchases`); + }; + + return ( + + + + + + {T.translate("edit_sponsor.cart_tab.invoice_view.title")} + + + + + + - - - -); + + + ); +}; const mapStateToProps = ({ showAccessState }) => ({ ...showAccessState diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js new file mode 100644 index 000000000..89bc08336 --- /dev/null +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -0,0 +1,28 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { connect } from "react-redux"; +import { Card, CardContent } from "@mui/material"; + +const PaymentView = () => ( + + PAYMENT VIEW + +); + +const mapStateToProps = ({ showAccessState }) => ({ + ...showAccessState +}); + +export default connect(mapStateToProps, {})(PaymentView); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js index 8cd34ef64..2affa88bd 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/index.js @@ -13,25 +13,19 @@ import React, { useState } from "react"; import { connect } from "react-redux"; +import { Route, Switch } from "react-router-dom"; import { Box } from "@mui/material"; +import { Breadcrumb } from "react-breadcrumbs"; import SelectFormDialog from "./components/select-form-dialog"; import CartView from "./components/cart-view"; import NewCartForm from "./components/edit-form/new-cart-form"; import EditCartForm from "./components/edit-form/edit-cart-form"; import InvoiceView from "./components/invoice-view"; -import { - checkoutCart, - payWithInvoice -} from "../../../../../actions/sponsor-cart-actions"; +import PaymentView from "./components/payment-view"; -const SponsorCartTab = ({ sponsor, currentSummit, checkoutCart, payWithInvoice }) => { +const SponsorCartTab = ({ sponsor, currentSummit, match }) => { const [openAddFormDialog, setOpenAddFormDialog] = useState(false); - const [formEdit, setFormEdit] = useState(null); const [newForm, setNewForm] = useState(null); - const [invoiceView, setInvoiceView] = useState(null); - const [payCCView, setPayCCView] = useState(null); - - const showCartView = !formEdit && !newForm && !invoiceView && !payCCView; const handleFormSelected = (form, addOn) => { setNewForm({ formId: form.id, addon: addOn }); @@ -42,54 +36,59 @@ const SponsorCartTab = ({ sponsor, currentSummit, checkoutCart, payWithInvoice } setNewForm(null); }; - const handleOnFormUpdated = () => { - setFormEdit(null); - }; - - const handlePayInvoice = () => { - checkoutCart().then(() => { - payWithInvoice().then(() => { - setInvoiceView(true); - }); - }); + const handleOnAddForm = () => { + setOpenAddFormDialog(true); }; return ( - {newForm && ( - + setNewForm(null)} - onSaveCallback={handleOnFormAdded} /> - )} - {formEdit && ( - setFormEdit(null)} - onSaveCallback={handleOnFormUpdated} - /> - )} - {invoiceView && setInvoiceView(null)} />} - {showCartView && ( - setOpenAddFormDialog(true)} - onPayCC={() => setPayCCView(true)} - onPayInvoice={handlePayInvoice} - /> - )} - setOpenAddFormDialog(false)} - /> + + ( + <> + {newForm ? ( + setNewForm(null)} + onSaveCallback={handleOnFormAdded} + /> + ) : ( + + )} + + setOpenAddFormDialog(false)} + /> + + )} + /> + + + + + ); }; @@ -99,7 +98,4 @@ const mapStateToProps = ({ currentSummitState, currentSponsorState }) => ({ sponsor: currentSponsorState.entity }); -export default connect(mapStateToProps, { - checkoutCart, - payWithInvoice -})(SponsorCartTab); +export default connect(mapStateToProps, {})(SponsorCartTab); From 81d9a8256c5be8a9aae841aeba91bd48cc62f641 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Thu, 9 Apr 2026 18:40:44 -0300 Subject: [PATCH 3/8] feat: pay with cc and uicore integration --- .env.example | 2 +- package.json | 2 + src/actions/sponsor-cart-actions.js | 187 +++++++++++++++--- src/app.js | 5 +- src/i18n/en.json | 16 +- .../sponsor-cart-tab/components/cart-view.js | 19 +- .../components/invoice-view.js | 7 +- .../components/payment-view.js | 156 ++++++++++++++- .../tabs/sponsor-cart-tab/helpers.js | 56 ++++++ .../sponsor-page-cart-list-reducer.js | 26 ++- src/utils/constants.js | 11 ++ yarn.lock | 12 ++ 12 files changed, 450 insertions(+), 49 deletions(-) create mode 100644 src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js diff --git a/.env.example b/.env.example index 1ac7e9a39..066181d62 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,7 @@ PUB_API_BASE_URL= OS_BASE_URL= SCOPES_BASE_REALM=${API_BASE_URL} PURCHASES_API_URL=https://purchases-api.dev.fnopen.com -PURCHASES_API_SCOPES="purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write customized-form/write customized-form/read carts/read carts/write purchases/read cart-notes/write payment/write" +PURCHASES_API_SCOPES="purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write customized-form/write customized-form/read carts/read carts/write purchases/read cart-notes/write payment/write payment-profile/read" SPONSOR_USERS_API_URL=https://sponsor-users-api.dev.fnopen.com SPONSOR_USERS_SCOPES="show-medata/read show-medata/write access-requests/read access-requests/write sponsor-users/read sponsor-users/write groups/read groups/write media-upload/write" EMAIL_SCOPES="clients/read templates/read templates/write emails/read" diff --git a/package.json b/package.json index 87ece9ac0..b9e6f3c17 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "@react-pdf/renderer": "^3.1.11", "@sentry/react": "^8.32.0", "@sentry/webpack-plugin": "^2.22.4", + "@stripe/react-stripe-js": "^5.4.1", + "@stripe/stripe-js": "^8.5.3", "@types/googlemaps": "^3.39.3", "@types/markerclustererplus": "^2.1.29", "@types/react": "^16.9.32", diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index 811a7c3c9..fa305e813 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -23,7 +23,11 @@ import { } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react"; import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; -import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; +import { + setSnackbarMessage, + snackbarErrorHandler, + snackbarSuccessHandler +} from "./base-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, @@ -45,19 +49,26 @@ export const FORM_CART_SAVED = "FORM_CART_SAVED"; export const SPONSOR_CART_NOTE_ADDED = "SPONSOR_CART_NOTE_ADDED"; export const SPONSOR_CART_NOTE_UPDATED = "SPONSOR_CART_NOTE_UPDATED"; export const SPONSOR_CART_NOTE_DELETED = "SPONSOR_CART_NOTE_DELETED"; -export const OFFLINE_PAYMENT_CREATED = "OFFLINE_PAYMENT_CREATED"; export const CART_STATUS_UPDATED = "CART_STATUS_UPDATED"; - -const customErrorHandler = (err, res) => (dispatch, state) => { - const code = err.status; - dispatch(stopLoading()); - switch (code) { - case ERROR_CODE_404: - break; - default: - authErrorHandler(err, res)(dispatch, state); - } -}; +export const RECEIVE_PAYMENT_PROFILE = "RECEIVE_PAYMENT_PROFILE"; +export const OFFLINE_PAYMENT_CREATED = "OFFLINE_PAYMENT_CREATED"; +export const PAYMENT_INTENT_CREATED = "PAYMENT_INTENT_CREATED"; +export const PAYMENT_INTENT_UPDATED = "PAYMENT_INTENT_UPDATED"; +export const PAYMENT_CONFIRMED = "PAYMENT_CONFIRMED"; + +const customErrorHandler = + (err, res, callback = null) => + (dispatch, getState) => { + const code = err.status; + dispatch(stopLoading()); + switch (code) { + case ERROR_CODE_404: + if (callback) callback()(dispatch, getState); + break; + default: + authErrorHandler(err, res)(dispatch, getState); + } + }; export const getSponsorCart = (term = "") => @@ -79,7 +90,8 @@ export const getSponsorCart = } const params = { - access_token: accessToken + access_token: accessToken, + expand: "forms,forms.items,forms.items.type,forms.items.meta_fields,notes" }; if (filter.length > 0) { @@ -459,18 +471,24 @@ export const checkoutCart = () => async (dispatch, getState) => { dispatch(startLoading()); const params = { - access_token: accessToken + access_token: accessToken, + expand: "forms,forms.items,forms.items.type,forms.items.meta_fields,notes" }; - return putRequest( - null, - createAction(CART_STATUS_UPDATED), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`, - {}, - snackbarErrorHandler - )(params)(dispatch).finally(() => { - dispatch(stopLoading()); - }); + return ( + putRequest( + null, + createAction(CART_STATUS_UPDATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`, + {}, + snackbarErrorHandler + )(params)(dispatch) + // this swallows the error neither rejecting or resolving, so we don't need to handle it down the pipe + .catch(() => new Promise(() => {})) + .finally(() => { + dispatch(stopLoading()); + }) + ); }; export const payWithInvoice = () => async (dispatch, getState) => { @@ -504,3 +522,124 @@ export const payWithInvoice = () => async (dispatch, getState) => { }) .catch(console.log); }; + +const createPaymentIntent = () => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState, sponsorPageCartListState } = + getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const { cart } = sponsorPageCartListState; + const accessToken = await getAccessTokenSafely(); + + const params = { + access_token: accessToken + }; + + const payload = { + type: "Online", + cart_id: cart.id + }; + + return postRequest( + null, + createAction(PAYMENT_INTENT_CREATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments`, + payload, + snackbarErrorHandler + )(params)(dispatch); +}; + +const PaymentProfileNotFound = () => (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const summitName = currentSummit.name; + + setSnackbarMessage({ + title: T.translate("errors.payment_profile_not_found_title"), + html: T.translate("errors.payment_profile_not_found", { summitName }), + type: "error" + })(dispatch, getState); +}; + +export const getPaymentProfile = () => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return getRequest( + null, + createAction(RECEIVE_PAYMENT_PROFILE), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/SponsorServices`, + (err, res) => (dispatch) => + customErrorHandler(err, res, PaymentProfileNotFound)(dispatch, getState) + )(params)(dispatch) + .then(() => createPaymentIntent()(dispatch, getState)) + .catch(console.log) + .finally(() => { + dispatch(stopLoading()); + }); +}; + +export const updatePaymentIntent = + (paymentMethod) => async (dispatch, getState) => { + const { + currentSummitState, + currentSponsorState, + sponsorPageCartListState + } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const { cart, paymentIntent } = sponsorPageCartListState; + const accessToken = await getAccessTokenSafely(); + + const params = { + access_token: accessToken + }; + + const payload = { + payment_method: paymentMethod, + cart_id: cart.id + }; + + return putRequest( + null, + createAction(PAYMENT_INTENT_UPDATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentIntent.id}/reprice`, + payload, + snackbarErrorHandler + )(params)(dispatch); + }; + +export const confirmPayment = () => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState, sponsorPageCartListState } = + getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const { paymentIntent } = sponsorPageCartListState; + const accessToken = await getAccessTokenSafely(); + + const params = { + access_token: accessToken + }; + + return putRequest( + null, + createAction(PAYMENT_CONFIRMED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentIntent.id}/confirm`, + {}, + snackbarErrorHandler + )(params)(dispatch).then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.cart_tab.payment_view.payment_success") + }) + ); + }); +}; diff --git a/src/app.js b/src/app.js index 90bb88fc1..3ea24d257 100644 --- a/src/app.js +++ b/src/app.js @@ -14,6 +14,7 @@ import React from "react"; import { Switch, Route, Router } from "react-router-dom"; import { connect } from "react-redux"; +import { setAppTexts } from "openstack-uicore-foundation/lib/i18n"; import { AjaxLoader } from "openstack-uicore-foundation/lib/components"; import { getBackURL } from "openstack-uicore-foundation/lib/utils/methods"; import { resetLoading } from "openstack-uicore-foundation/lib/utils/actions"; @@ -67,8 +68,8 @@ if (language.length > LANGUAGE_CODE_LENGTH) { } // DISABLED language - ONLY ENGLISH - -T.setTexts(require("./i18n/en.json")); +// use this method so that uicore translations are not overridden +setAppTexts(require("./i18n/en.json")); // move all env var to global scope so ui core has access to this diff --git a/src/i18n/en.json b/src/i18n/en.json index 11ab38719..44936bed2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -9,7 +9,9 @@ "not_allowed": "You are not allowed here, please login with another user to access this page.", "not_found": "Not Found!", "entity_not_found": "The entity you are looking for was not found.", - "maximum_files": "Maximum number of files has been reached" + "maximum_files": "Maximum number of files has been reached", + "payment_profile_not_found_title": "Payment Profile not found", + "payment_profile_not_found": "Missing payment profile for summit {{summitName}}. Please contact support." }, "general": { "summit": "Event", @@ -83,6 +85,8 @@ "sort_asc_label": "A-Z", "sort_desc_label": "Z-A", "n_a": "N/A", + "to": "To", + "from": "From", "placeholders": { "search_speakers": "Search Speakers by Name, Email, Speaker Id or Member Id", "select_acceptance_criteria": "Select acceptance criteria", @@ -2562,6 +2566,16 @@ "invoice_view": { "title": "Purchase confirmed", "go_to_orders": "Go to orders" + }, + "payment_view": { + "code": "From code", + "contents": "Contents", + "addon": "Add-on", + "discount": "Discount", + "amount": "Amount", + "total": "Total", + "rate": "Rate", + "payment_success": "Payment successful" } }, "purchase_tab": { diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js index 4fa5b95c7..17010103d 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js @@ -39,7 +39,10 @@ import { unlockSponsorCartForm } from "../../../../../../actions/sponsor-cart-actions"; import CartNote from "./cart-note"; -import { SPONSOR_CART_NOTE_TYPES } from "../../../../../../utils/constants"; +import { + SPONSOR_CART_NOTE_TYPES, + SPONSOR_CART_STATUS +} from "../../../../../../utils/constants"; const CartView = ({ cart, @@ -51,10 +54,12 @@ const CartView = ({ onAddForm, saveSponsorCartNote, deleteSponsorCartNote, - onPayCC, checkoutCart, payWithInvoice }) => { + const cartIsPendingPayment = + cart?.status === SPONSOR_CART_STATUS.PENDING_PAYMENT; + useEffect(() => { getSponsorCart(); }, []); @@ -84,11 +89,17 @@ const CartView = ({ }; const handlePayCreditCard = () => { - onPayCC(); + const promise = cartIsPendingPayment ? Promise.resolve() : checkoutCart(); + + promise.then(() => { + history.push("cart/payment"); + }); }; const handlePayInvoice = () => { - checkoutCart().then(() => { + const promise = cartIsPendingPayment ? Promise.resolve() : checkoutCart(); + + promise.then(() => { payWithInvoice().then(() => { history.push("cart/invoice"); }); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js index e88bf73c4..a0f828b5f 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/invoice-view.js @@ -12,7 +12,6 @@ * */ import React from "react"; -import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; import { Box, Button, Card, CardContent, Typography } from "@mui/material"; import history from "../../../../../../history"; @@ -74,8 +73,4 @@ const InvoiceView = ({ match }) => { ); }; -const mapStateToProps = ({ showAccessState }) => ({ - ...showAccessState -}); - -export default connect(mapStateToProps, {})(InvoiceView); +export default InvoiceView; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 89bc08336..295964e2b 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -11,18 +11,154 @@ * limitations under the License. * */ -import React from "react"; +import React, { useEffect } from "react"; import { connect } from "react-redux"; -import { Card, CardContent } from "@mui/material"; +import T from "i18n-react/dist/i18n-react"; +import { Box, Card, CardContent } from "@mui/material"; +import { + MuiTable, + MuiNotesRow, + MuiOrderSummary, + MuiStripePayment, + MuiTotalRow +} from "openstack-uicore-foundation/lib/components"; +import history from "../../../../../../history"; +import { + confirmPayment, + getPaymentProfile, + updatePaymentIntent +} from "../../../../../../actions/sponsor-cart-actions"; +import { mapCartData } from "../helpers"; +import { useSnackbarMessage } from "../../../../../../components/mui/SnackbarNotification/Context"; -const PaymentView = () => ( - - PAYMENT VIEW - -); +const PaymentView = ({ + cart, + currentSummit, + sponsor, + paymentIntent, + paymentProfile, + getPaymentProfile, + updatePaymentIntent, + confirmPayment +}) => { + const { errorMessage } = useSnackbarMessage(); -const mapStateToProps = ({ showAccessState }) => ({ - ...showAccessState + useEffect(() => { + if (cart) { + getPaymentProfile(cart.id); + } + }, [cart]); + + const redirectUrl = `/app/summits/${currentSummit.id}/sponsors/${sponsor.id}/cart`; + + const cartData = mapCartData(cart, true); + + const cartColumns = [ + { + columnKey: "code", + header: T.translate("edit_sponsor.cart_tab.payment_view.code") + }, + { + columnKey: "name", + header: T.translate("edit_sponsor.cart_tab.payment_view.contents") + }, + { columnKey: "item_name", header: "" }, + { + columnKey: "addon_name", + header: T.translate("edit_sponsor.cart_tab.payment_view.addon") + }, + { + columnKey: "discount", + header: T.translate("edit_sponsor.cart_tab.payment_view.discount") + }, + { + columnKey: "amount", + header: T.translate("edit_sponsor.cart_tab.payment_view.amount") + } + ]; + + const handlePaymentSuccess = (paymentData) => + confirmPayment(paymentData.id).then(() => { + history.push(redirectUrl); + }); + + const handlePaymentError = (error) => { + errorMessage(error); + }; + + return ( + <> + + {cart?.notes.map((note) => ( + + ))} + + + + + + + + + + + + + + + + ); +}; + +const mapStateToProps = ({ + currentSummitState, + currentSponsorState, + sponsorPageCartListState +}) => ({ + currentSummit: currentSummitState.currentSummit, + sponsor: currentSponsorState.entity, + ...sponsorPageCartListState }); -export default connect(mapStateToProps, {})(PaymentView); +export default connect(mapStateToProps, { + getPaymentProfile, + updatePaymentIntent, + confirmPayment +})(PaymentView); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js new file mode 100644 index 000000000..e4d843b4b --- /dev/null +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/helpers.js @@ -0,0 +1,56 @@ +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import { currencyAmountFromCents } from "openstack-uicore-foundation/lib/utils/money"; +import { SPONSOR_FORMS_METAFIELD_CLASS } from "../../../../../utils/constants"; + +export const mapCartData = (cart, showItemDescription = false) => { + if (!cart?.forms) return []; + + return cart.forms + .map((form) => ({ + ...form, + discount: form.discount === "0%" ? "" : form.discount + })) + .reduce((res, f) => { + f.items.forEach((it) => { + const formMetaFields = it.meta_fields.filter( + (mf) => mf.class_field === SPONSOR_FORMS_METAFIELD_CLASS.FORM + ); + + const item_name = [it.type.name]; + + if (showItemDescription) { + item_name.push( + ...formMetaFields.map((mf) => { + const val = + mf.values.length > 0 + ? mf.values.find((v) => v.id === mf.current_value)?.name + : mf.current_value; + return ( +
+ {mf.name}: {val} +
+ ); + }) + ); + + item_name.push(
); // spacer + item_name.push( +
+ {T.translate("edit_sponsor.cart_tab.payment_view.total")}:{" "} + {it.quantity} +
+ ); + item_name.push( +
+ {T.translate("edit_sponsor.cart_tab.payment_view.rate")}:{" "} + {currencyAmountFromCents(it.current_rate)} +
+ ); + } + + res.push({ ...f, item_name }); + }); + return res; + }, []); +}; diff --git a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js index 4825dcf18..70f749b99 100644 --- a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js @@ -32,7 +32,11 @@ import { SPONSOR_CART_NOTE_DELETED, SPONSOR_CART_NOTE_UPDATED, OFFLINE_PAYMENT_CREATED, - CART_STATUS_UPDATED + CART_STATUS_UPDATED, + RECEIVE_PAYMENT_PROFILE, + PAYMENT_INTENT_UPDATED, + PAYMENT_INTENT_CREATED, + PAYMENT_CONFIRMED } from "../../actions/sponsor-cart-actions"; import { DISCOUNT_TYPES } from "../../utils/constants"; @@ -51,6 +55,8 @@ const DEFAULT_STATE = { }, sponsorForm: null, cartForm: null, + paymentProfile: null, + paymentIntent: null, offlinePayment: null }; @@ -221,6 +227,24 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { } }; } + case RECEIVE_PAYMENT_PROFILE: { + const paymentProfile = payload.response; + return { ...state, paymentProfile }; + } + case PAYMENT_INTENT_UPDATED: + case PAYMENT_INTENT_CREATED: { + const paymentIntent = payload.response; + return { ...state, paymentIntent }; + } + case PAYMENT_CONFIRMED: { + return { + ...state, + cart: null, + paymentProfile: null, + paymentIntent: null, + offlinePayment: null + }; + } case OFFLINE_PAYMENT_CREATED: { const offlinePayment = payload.response; return { ...state, offlinePayment }; diff --git a/src/utils/constants.js b/src/utils/constants.js index 876acd3a5..5c7b72fcd 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -314,3 +314,14 @@ export const ACCESS_ROUTES = { ADMIN_SPONSORS: "admin-sponsors", SPONSORS: "sponsors" }; + +export const SPONSOR_FORMS_METAFIELD_CLASS = { + FORM: "Form", + ITEM: "Item" +}; + +export const SPONSOR_CART_STATUS = { + PENDING_PAYMENT: "PendingPayment", + OPEN: "Open", + CHECKED_OUT: "CheckedOut" +}; diff --git a/yarn.lock b/yarn.lock index a7d130a7c..4bb8f71d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2487,6 +2487,18 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@stripe/react-stripe-js@^5.4.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-5.6.1.tgz#e6f6989651ce65b39bf59ac0b5a2cea459ec3070" + integrity sha512-5xBrjkGmFvKvpMod6VvpOaFaa67eRbmieKeFTePZyOr/sUXzm7A3YY91l330pS0usUst5PxTZDUZHWfOc0v1GA== + dependencies: + prop-types "^15.7.2" + +"@stripe/stripe-js@^8.5.3": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-8.11.0.tgz#1f17759a4040e553746e76bd673ba27cb85e5a63" + integrity sha512-3fVF4z3efsgwgyj64nFK+6F4/vMw0mUXD2TBbOfftJtKVNx4JNv3CSfe1fY4DCtCk0JFp8/YPNcRkzgV0HJ8cg== + "@swc/helpers@^0.5.12": version "0.5.21" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.21.tgz#0b1b020317ee1282860ca66f7e9a7c7790f05ae0" From ed9f6ee4de5c702e4dd5333b7b117be3dfa7e15d Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 10 Apr 2026 13:26:27 -0300 Subject: [PATCH 4/8] fix: PR review --- src/actions/sponsor-cart-actions.js | 16 ++++++----- src/i18n/en.json | 2 +- .../sponsor-cart-tab/components/cart-view.js | 27 ++++++++++--------- .../components/edit-form/edit-cart-form.js | 10 ++----- .../components/payment-view.js | 4 ++- .../tabs/sponsor-cart-tab/helpers.js | 4 +-- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index fa305e813..b86ee4cb9 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -483,11 +483,11 @@ export const checkoutCart = () => async (dispatch, getState) => { {}, snackbarErrorHandler )(params)(dispatch) - // this swallows the error neither rejecting or resolving, so we don't need to handle it down the pipe - .catch(() => new Promise(() => {})) .finally(() => { dispatch(stopLoading()); }) + // this swallows the error neither rejecting or resolving, so we don't need to handle it down the pipe + .catch(() => new Promise(() => {})) ); }; @@ -507,7 +507,7 @@ export const payWithInvoice = () => async (dispatch, getState) => { const payload = { type: "Offline", - cart_id: cart.id + cart_id: cart?.id }; return postRequest( @@ -520,7 +520,9 @@ export const payWithInvoice = () => async (dispatch, getState) => { .then(() => { getSponsorCart()(dispatch, getState); }) - .catch(console.log); + .finally(() => { + dispatch(stopLoading()); + }); }; const createPaymentIntent = () => async (dispatch, getState) => { @@ -537,7 +539,7 @@ const createPaymentIntent = () => async (dispatch, getState) => { const payload = { type: "Online", - cart_id: cart.id + cart_id: cart?.id }; return postRequest( @@ -604,13 +606,13 @@ export const updatePaymentIntent = const payload = { payment_method: paymentMethod, - cart_id: cart.id + cart_id: cart?.id }; return putRequest( null, createAction(PAYMENT_INTENT_UPDATED), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentIntent.id}/reprice`, + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentIntent?.id}/reprice`, payload, snackbarErrorHandler )(params)(dispatch); diff --git a/src/i18n/en.json b/src/i18n/en.json index 44936bed2..b4724eaab 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2568,7 +2568,7 @@ "go_to_orders": "Go to orders" }, "payment_view": { - "code": "From code", + "code": "Form code", "contents": "Contents", "addon": "Add-on", "discount": "Discount", diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js index 17010103d..2514cb1bc 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/cart-view.js @@ -88,22 +88,23 @@ const CartView = ({ } }; - const handlePayCreditCard = () => { - const promise = cartIsPendingPayment ? Promise.resolve() : checkoutCart(); - - promise.then(() => { + const handlePayCreditCard = async () => { + try { + if (!cartIsPendingPayment) await checkoutCart(); history.push("cart/payment"); - }); + } catch (err) { + console.error("Failed to checkout cart for credit card payment:", err); + } }; - const handlePayInvoice = () => { - const promise = cartIsPendingPayment ? Promise.resolve() : checkoutCart(); - - promise.then(() => { - payWithInvoice().then(() => { - history.push("cart/invoice"); - }); - }); + const handlePayInvoice = async () => { + try { + if (!cartIsPendingPayment) await checkoutCart(); + await payWithInvoice(); + history.push("cart/invoice"); + } catch (err) { + console.error("Failed to process invoice payment:", err); + } }; const cartData = cart?.forms.map((form) => ({ diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js index c06219a84..62b37cd93 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/edit-form/edit-cart-form.js @@ -33,14 +33,8 @@ const EditCartForm = ({ }, [formId]); const backToCart = () => { - const backUrl = match.url - .split("/") - .filter((segment) => segment.length > 0) - // eslint-disable-next-line no-magic-numbers - .slice(0, -2) - .join("/"); - - history.push(`/${backUrl}`); + const backUrl = match.url.replace(/\/forms\/[^/]+$/, ""); + history.push(backUrl); }; const saveForm = (values) => { diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 295964e2b..7369430c4 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -49,6 +49,8 @@ const PaymentView = ({ } }, [cart]); + if (!currentSummit || !sponsor?.company || !cart) return null; + const redirectUrl = `/app/summits/${currentSummit.id}/sponsors/${sponsor.id}/cart`; const cartData = mapCartData(cart, true); @@ -89,7 +91,7 @@ const PaymentView = ({ return ( <> - {cart?.notes.map((note) => ( + {cart?.notes?.map((note) => ( { })) .reduce((res, f) => { f.items.forEach((it) => { - const formMetaFields = it.meta_fields.filter( + const formMetaFields = (it.meta_fields ?? []).filter( (mf) => mf.class_field === SPONSOR_FORMS_METAFIELD_CLASS.FORM ); @@ -23,7 +23,7 @@ export const mapCartData = (cart, showItemDescription = false) => { item_name.push( ...formMetaFields.map((mf) => { const val = - mf.values.length > 0 + mf.values?.length > 0 ? mf.values.find((v) => v.id === mf.current_value)?.name : mf.current_value; return ( From ae021058658b8c7f26a46c0f9d52989c7ab4aeec Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Mon, 13 Apr 2026 16:31:47 -0300 Subject: [PATCH 5/8] chore: add billing info form and send to stripe --- src/actions/member-actions.js | 101 ++++++++++++------ src/i18n/en.json | 2 + .../components/client-form/index.js | 66 ++++++++++++ .../components/payment-view.js | 68 +++++++----- .../sponsor-page-cart-list-reducer.js | 14 ++- 5 files changed, 192 insertions(+), 59 deletions(-) create mode 100644 src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/client-form/index.js diff --git a/src/actions/member-actions.js b/src/actions/member-actions.js index d39a40116..35b1c5d6a 100644 --- a/src/actions/member-actions.js +++ b/src/actions/member-actions.js @@ -1,5 +1,5 @@ /** - * Copyright 2018 OpenStack Foundation + * Copyright 2026 OpenStack Foundation * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -9,7 +9,8 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - **/ + * */ + import { getRequest, getCSV, @@ -23,16 +24,25 @@ import { } from "openstack-uicore-foundation/lib/utils/actions"; import moment from "moment-timezone"; import { getAccessTokenSafely } from "../utils/methods"; +import { snackbarErrorHandler } from "./base-actions"; +import { DEFAULT_PER_PAGE } from "../utils/constants"; export const REQUEST_MEMBERS = "REQUEST_MEMBERS"; export const RECEIVE_MEMBERS = "RECEIVE_MEMBERS"; +export const RECEIVE_MEMBER = "RECEIVE_MEMBER"; export const AFFILIATION_SAVED = "AFFILIATION_SAVED"; export const AFFILIATION_DELETED = "AFFILIATION_DELETED"; export const AFFILIATION_ADDED = "AFFILIATION_ADDED"; export const ORGANIZATION_ADDED = "ORGANIZATION_ADDED"; export const getMembers = - (term = null, page = 1, perPage = 10, order = "id", orderDir = 1) => + ( + term = null, + page = 1, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = 1 + ) => async (dispatch, getState) => { const { currentSummitState } = getState(); const accessToken = await getAccessTokenSafely(); @@ -46,7 +56,7 @@ export const getMembers = } const params = { - page: page, + page, per_page: perPage, access_token: accessToken }; @@ -58,7 +68,7 @@ export const getMembers = // order if (order != null && orderDir != null) { const orderDirSign = orderDir === 1 ? "+" : "-"; - params["order"] = `${orderDirSign}${order}`; + params.order = `${orderDirSign}${order}`; } return getRequest( @@ -72,6 +82,29 @@ export const getMembers = }); }; +export const getMemberByExternalId = (externalId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken, + relations: "none", + fields: "id,first_name,last_name,email" + }; + + return getRequest( + null, + createAction(RECEIVE_MEMBER), + `${window.API_BASE_URL}/api/v1/members/external/${externalId}`, + snackbarErrorHandler + )(params)(dispatch) + .catch(console.log) + .finally(() => { + dispatch(stopLoading()); + }); +}; + export const getMembersForEventCSV = (event) => async (dispatch, getState) => { const { currentSummitState } = getState(); const accessToken = await getAccessTokenSafely(); @@ -83,8 +116,9 @@ export const getMembersForEventCSV = (event) => async (dispatch, getState) => { const momentEndDate = moment(event.endDate).tz(currentSummit.time_zone_id); const date = momentStartDate.format("dddd Do"); - const time = - momentStartDate.format("h:mm a") + " - " + momentEndDate.format("h:mm a"); + const time = `${momentStartDate.format("h:mm a")} - ${momentEndDate.format( + "h:mm a" + )}`; const roomName = event.location && event.location.venueroom ? event.location.venueroom.name @@ -111,31 +145,32 @@ export const getMembersForEventCSV = (event) => async (dispatch, getState) => { ); }; -/****************************** AFFILIATIONS **************************************************/ +/* ************************************************************************** */ +/* AFFILIATIONS */ +/* ************************************************************************** */ -export const addOrganization = - (organization, callback) => async (dispatch, getState) => { - const accessToken = await getAccessTokenSafely(); +export const addOrganization = (organization, callback) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); - const params = { - access_token: accessToken - }; + const params = { + access_token: accessToken + }; - dispatch(startLoading()); + dispatch(startLoading()); - postRequest( - null, - createAction(ORGANIZATION_ADDED), - `${window.API_BASE_URL}/api/v1/organizations`, - { name: organization }, - authErrorHandler - )(params)(dispatch).then((payload) => { - dispatch(stopLoading()); - callback(payload.response); - }); - }; + postRequest( + null, + createAction(ORGANIZATION_ADDED), + `${window.API_BASE_URL}/api/v1/organizations`, + { name: organization }, + authErrorHandler + )(params)(dispatch).then((payload) => { + dispatch(stopLoading()); + callback(payload.response); + }); +}; -export const addAffiliation = (affiliation) => async (dispatch, getState) => { +export const addAffiliation = (affiliation) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); dispatch(startLoading()); @@ -154,12 +189,12 @@ export const addAffiliation = (affiliation) => async (dispatch, getState) => { normalizedEntity, authErrorHandler, affiliation - )(params)(dispatch).then((payload) => { + )(params)(dispatch).then(() => { dispatch(stopLoading()); }); }; -export const saveAffiliation = (affiliation) => async (dispatch, getState) => { +export const saveAffiliation = (affiliation) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); dispatch(startLoading()); @@ -176,13 +211,13 @@ export const saveAffiliation = (affiliation) => async (dispatch, getState) => { `${window.API_BASE_URL}/api/v1/members/${affiliation.owner_id}/affiliations/${affiliation.id}`, normalizedEntity, authErrorHandler - )(params)(dispatch).then((payload) => { + )(params)(dispatch).then(() => { dispatch(stopLoading()); }); }; export const deleteAffiliation = - (ownerId, affiliationId) => async (dispatch, getState) => { + (ownerId, affiliationId) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); const params = { @@ -203,13 +238,13 @@ export const deleteAffiliation = const normalizeEntity = (entity) => { const normalizedEntity = { ...entity }; - if (!normalizedEntity.end_date) delete normalizedEntity["end_date"]; + if (!normalizedEntity.end_date) delete normalizedEntity.end_date; normalizedEntity.organization_id = normalizedEntity.organization != null ? normalizedEntity.organization.id : 0; - delete normalizedEntity["organization"]; + delete normalizedEntity.organization; return normalizedEntity; }; diff --git a/src/i18n/en.json b/src/i18n/en.json index b4724eaab..4599c5b52 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -29,6 +29,7 @@ "file": "File", "logo": "Logo", "secondary_logo": "Secondary Logo", + "full_name": "Full Name", "first_name": "First Name", "last_name": "Last Name", "member": "Member", @@ -2575,6 +2576,7 @@ "amount": "Amount", "total": "Total", "rate": "Rate", + "billing_info": "Billing Information", "payment_success": "Payment successful" } }, diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/client-form/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/client-form/index.js new file mode 100644 index 000000000..4280f5926 --- /dev/null +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/client-form/index.js @@ -0,0 +1,66 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React, { useEffect, useMemo } from "react"; +import { debounce } from "lodash"; +import T from "i18n-react/dist/i18n-react"; +import { FormikProvider, useFormik } from "formik"; +import * as yup from "yup"; +import Box from "@mui/material/Box"; +import { MuiFormikTextField } from "openstack-uicore-foundation/lib/components"; +import { DEBOUNCE_WAIT_250 } from "../../../../../../../utils/constants"; + +const ClientForm = ({ initialValues, onChange }) => { + const formik = useFormik({ + initialValues: { + full_name: initialValues?.full_name || "", + email: initialValues?.email || "" + }, + validationSchema: yup.object({ + full_name: yup.string().required(T.translate("validation.required")), + email: yup + .string() + .email(T.translate("validation.email")) + .required(T.translate("validation.required")) + }), + enableReinitialize: true + }); + + const debouncedOnChange = useMemo( + () => debounce(onChange, DEBOUNCE_WAIT_250), + [onChange] + ); + + useEffect(() => { + debouncedOnChange(formik.values); + }, [formik.values]); + + return ( + + + + + + + ); +}; + +export default ClientForm; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 7369430c4..66a9d4bba 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -11,41 +11,47 @@ * limitations under the License. * */ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; -import { Box, Card, CardContent } from "@mui/material"; +import { Box, Card, CardContent, Typography } from "@mui/material"; import { - MuiTable, MuiNotesRow, MuiOrderSummary, - MuiStripePayment, + MuiTable, MuiTotalRow } from "openstack-uicore-foundation/lib/components"; +import StripePayment from "openstack-uicore-foundation/lib/components/mui/stripe-payment"; import history from "../../../../../../history"; import { confirmPayment, getPaymentProfile, updatePaymentIntent } from "../../../../../../actions/sponsor-cart-actions"; +import { getMemberByExternalId } from "../../../../../../actions/member-actions"; import { mapCartData } from "../helpers"; import { useSnackbarMessage } from "../../../../../../components/mui/SnackbarNotification/Context"; +import ClientForm from "./client-form"; const PaymentView = ({ cart, + cartOwner, currentSummit, sponsor, paymentIntent, paymentProfile, getPaymentProfile, + getMemberByExternalId, updatePaymentIntent, confirmPayment }) => { const { errorMessage } = useSnackbarMessage(); + const [client, setClient] = useState({}); useEffect(() => { if (cart) { getPaymentProfile(cart.id); + getMemberByExternalId(cart.owner_id); } }, [cart]); @@ -104,39 +110,50 @@ const PaymentView = ({ targetCol="amount" /> - - - - + + + + + {T.translate("edit_sponsor.cart_tab.payment_view.billing_info")} + + + + + + + + + - ({ @@ -249,6 +251,16 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { const offlinePayment = payload.response; return { ...state, offlinePayment }; } + case RECEIVE_MEMBER: { + const member = payload.response; + return { + ...state, + cartOwner: { + ...member, + full_name: `${member.first_name} ${member.last_name}` + } + }; + } default: return state; } From 0aecd75db4769dab28028858cd7bcae367eeac6e Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 21 Apr 2026 11:58:06 -0300 Subject: [PATCH 6/8] fix: PR review --- src/actions/sponsor-cart-actions.js | 23 ++++++++----------- .../components/payment-view.js | 6 ++--- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index b86ee4cb9..867ad9754 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -475,20 +475,15 @@ export const checkoutCart = () => async (dispatch, getState) => { expand: "forms,forms.items,forms.items.type,forms.items.meta_fields,notes" }; - return ( - putRequest( - null, - createAction(CART_STATUS_UPDATED), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`, - {}, - snackbarErrorHandler - )(params)(dispatch) - .finally(() => { - dispatch(stopLoading()); - }) - // this swallows the error neither rejecting or resolving, so we don't need to handle it down the pipe - .catch(() => new Promise(() => {})) - ); + return putRequest( + null, + createAction(CART_STATUS_UPDATED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/checkout`, + {}, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); }; export const payWithInvoice = () => async (dispatch, getState) => { diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 66a9d4bba..3c3845b41 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -50,7 +50,7 @@ const PaymentView = ({ useEffect(() => { if (cart) { - getPaymentProfile(cart.id); + getPaymentProfile(); getMemberByExternalId(cart.owner_id); } }, [cart]); @@ -85,8 +85,8 @@ const PaymentView = ({ } ]; - const handlePaymentSuccess = (paymentData) => - confirmPayment(paymentData.id).then(() => { + const handlePaymentSuccess = () => + confirmPayment().then(() => { history.push(redirectUrl); }); From 5f83832ade47dad94e8ecbb958990292895af835 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 21 Apr 2026 12:34:52 -0300 Subject: [PATCH 7/8] fix: tests --- .../tabs/sponsor-cart-tab/components/payment-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 3c3845b41..239e06361 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -22,6 +22,7 @@ import { MuiTotalRow } from "openstack-uicore-foundation/lib/components"; import StripePayment from "openstack-uicore-foundation/lib/components/mui/stripe-payment"; +import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification-context"; import history from "../../../../../../history"; import { confirmPayment, @@ -30,7 +31,6 @@ import { } from "../../../../../../actions/sponsor-cart-actions"; import { getMemberByExternalId } from "../../../../../../actions/member-actions"; import { mapCartData } from "../helpers"; -import { useSnackbarMessage } from "../../../../../../components/mui/SnackbarNotification/Context"; import ClientForm from "./client-form"; const PaymentView = ({ From 8f2038a6758ce40f36d6803c34299413a0529be9 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 21 Apr 2026 22:29:27 -0300 Subject: [PATCH 8/8] fix: use new uicore lib and fix bugs --- .../sponsor-cart-tab/components/payment-view.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js index 239e06361..59bd2f0ea 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-cart-tab/components/payment-view.js @@ -22,7 +22,7 @@ import { MuiTotalRow } from "openstack-uicore-foundation/lib/components"; import StripePayment from "openstack-uicore-foundation/lib/components/mui/stripe-payment"; -import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification-context"; +import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification"; import history from "../../../../../../history"; import { confirmPayment, @@ -123,7 +123,10 @@ const PaymentView = ({ - +