From da2d5d3fa78bbe1a976b66af1e0c9cf1a1e8c7dd Mon Sep 17 00:00:00 2001 From: Giovanni Briggs Date: Tue, 19 Jul 2016 13:52:40 -0700 Subject: [PATCH] Refund functionality implemented --- actions/checkouts.js | 78 +++++++++++++++++++++++- components/Checkouts.js | 127 ++++++++++++++++++++++++++++++++++++++-- reducers/checkouts.js | 72 ++++++++++++++++++----- server.js | 50 +++++++++++++++- 4 files changed, 303 insertions(+), 24 deletions(-) diff --git a/actions/checkouts.js b/actions/checkouts.js index 5948fa7..26b1e5c 100644 --- a/actions/checkouts.js +++ b/actions/checkouts.js @@ -2,6 +2,9 @@ export const REQUEST = 'REQUEST_CHECKOUTS' export const RECEIVE = 'RECEIVE_CHECKOUTS' export const SEARCH = 'SEARCH_CHECKOUTS' export const INVALIDATE = 'INVALIDATE_CHECKOUTS' +export const REFUND = "REFUND_CHECKOUT" +export const RECEIVE_REFUND = "RECEIVE_REFUND_CHECKOUT" +export const CLEAR_REFUND = "CLEAR_REFUND_STATE" export function searchCheckout(email, account_id = null, checkout_id=null) { return { @@ -40,6 +43,43 @@ function receiveCheckout(email, account_id = null, checkout_id = null, json) { } } +function refundCheckout(email, checkout_id, amount = null, refund_reason) { + return { + type: REFUND, + email: email, + checkout_id: checkout_id, + amount:amount, + refund_reason:refund_reason + } +} + +function receiveRefund(email, checkout_id, data) { + return { + type: RECEIVE_REFUND, + email: email, + checkout_id:checkout_id, + refund: data + } +} + +function requestRefund(email, checkout_id, amount=null, refund_reason) { + return dispatch => { + dispatch(refundCheckout(email, checkout_id, amount, refund_reason)) + return $.post("/refund", {"email":email, "checkout_id": checkout_id, "amount":amount, "refund_reason":refund_reason}) + .fail(function(data){ + console.log("ERROR: ", data); + var error_data = JSON.parse(data.responseText); + }) + .done(function(data){ + //dispatch receive refund action + dispatch(receiveRefund(email, checkout_id, data)); + + // update the checkout data for this checkout + dispatch(fetchCheckoutIfNeeded(email, null, checkout_id)) + }) + } +} + function fetchCheckout(email, account_id = null, checkout_id = null) { return dispatch => { dispatch(requestCheckout(email, account_id, checkout_id)) @@ -50,7 +90,8 @@ function fetchCheckout(email, account_id = null, checkout_id = null) { var error_data = JSON.parse(data.responseText); }) .done(function(data){ - dispatch(receiveCheckout(email, account_id, checkout_id, data)) + //dispatch the receive reach + dispatch(receiveCheckout(email, account_id, checkout_id, data)); }) } } @@ -65,10 +106,41 @@ function shouldFetchCheckout(state, account_id) { return false; } -export function fetchCheckoutIfNeeded(email, account_id) { +export function fetchCheckoutIfNeeded(email, account_id=null, checkout_id=null) { return (dispatch, getState) => { if (shouldFetchCheckout(getState())) { - return dispatch(fetchCheckout(email, account_id)) + return dispatch(fetchCheckout(email, account_id, checkout_id)) + } + } +} + +function shouldRefundCheckout(state, checkout_id, amount) { + var checkout = null; + console.log("Searching for checkout_id: ", checkout_id) + for (var i =0; i < state.wepay_checkout.checkout.checkoutInfo.length; i++) { + if (state.wepay_checkout.checkout.checkoutInfo[i].checkout_id == checkout_id) { + console.log("Found checkout to refund: ", checkout); + checkout = state.wepay_checkout.checkout.checkoutInfo[i]; + } + } + if (checkout && checkout.amount - checkout.refund.amount_refunded > amount) { + console.log("Should refund"); + return true; } + console.log("Should NOT refund"); + return false; +} + +export function fetchRefundIfNeeded(email, checkout_id, amount, refund_reason) { + return(dispatch, getState) => { + if(shouldRefundCheckout(getState(), checkout_id, amount)) { + return dispatch(requestRefund(email, checkout_id, amount, refund_reason)) + } + } +} + +export function clearRefund() { + return { + type: CLEAR_REFUND } } \ No newline at end of file diff --git a/components/Checkouts.js b/components/Checkouts.js index 9f906a6..e121060 100644 --- a/components/Checkouts.js +++ b/components/Checkouts.js @@ -1,18 +1,105 @@ import React, { PropTypes } from 'react' -import {Grid, FormGroup, FormControl, Row, Col, ControlLabel, Table} from "react-bootstrap" +import {Grid, Label, FormGroup, FormControl, Row, Col, ControlLabel, Table, Button, Modal} from "react-bootstrap" import { connect } from 'react-redux' import {BootstrapTable} from "react-bootstrap-table" +import {fetchRefundIfNeeded, clearRefund} from "../actions/checkouts" + import Base from "./Base" var Checkouts = React.createClass({ getInitialState: function() { return { - checkoutInfo: {}, + showModal: false, + refund: { + selectedCheckoutId: null, + refundAmount: 0, + refundReason: "", + } + } + }, + openModal: function(event) { + console.log("OPENING MODAL", event.target.id); + this.setState({showModal:true, refund:{selectedCheckoutId: event.target.id}}); + }, + closeModal: function() { + this.props.dispatch(clearRefund()); + this.setState({showModal:false, refund:{}}); + }, + buildModal: function() { + var checkout = null; + for (var i = 0; i < this.props.checkoutInfo.length; i++) { + if(this.props.checkoutInfo[i].checkout_id == this.state.refund.selectedCheckoutId) { + checkout = this.props.checkoutInfo[i]; + break; + } + } + + // if no checkout exists, then don't build the modal + if (!checkout) { + return (
); + } + + // otherwise build it + var maxRefundableAmount = (checkout.amount - checkout.refund.amount_refunded); + var successful_refund; + if (this.props.successful_refund) { + successful_refund = (

); } + console.log(successful_refund); + return ( +
+ + + Refund Checkout + + +

Max Refundable Amount: ${maxRefundableAmount}

+
+ + Refund Amount + + + + Refund Reason + + + {successful_refund} + +
+
+
+
+ ); + }, + handleAmountChange: function(event) { + var current = this.state.refund; + current.refundAmount = event.target.value; + this.setState({refund: current}) + }, + handleReasonChange: function(event) { + var current = this.state.refund; + current.refundReason = event.target.value; + this.setState({refund: current}) }, - handleClick: function() { + refundCheckout: function(e) { + e.preventDefault(); + console.log("Performing refund for: ", this.state.refund.selectedCheckoutId); + var checkout_id = this.state.refund.selectedCheckoutId; + var refundAmount = $("#refundAmount").val(); + var refundReason = $("#refundReason").val(); + var current = this.state.refund; + this.setState({refund:current}) + this.props.dispatch(fetchRefundIfNeeded(this.props.email, checkout_id, refundAmount, refundReason)); }, formatCheckoutID: function(cell,row) { return "" + cell + ""; @@ -20,6 +107,28 @@ var Checkouts = React.createClass({ formatDate: function(cell, row) { return new Date(cell * 1000).toString(); }, + formatRefund: function(cell, row) { + // cell is the refund_amount_refunded value. If this is less than the amount of the original checkout then we want to include a refund button + // if the value is greater than 0, then someone initiated a refund, so we want to include the reason for the refund and how much it was + var d = null; + var refundString = (

Refunded: ${cell}

{row.refund_refund_reason}

); + var refundButton = () + if (cell > 0) { + if (cell == row.amount) { + return (
{refundString}
) + } + else { + return (
+ {refundString} + {refundButton} +
); + + } + } + return (
{refundButton}
); + + }, + serialize: function(info) { var array = []; @@ -29,6 +138,7 @@ var Checkouts = React.createClass({ return array; }, render: function() { + console.log("Rendering checkouts"); if (this.props.checkoutInfo == null || $.isEmptyObject(this.props.checkoutInfo) || $.isEmptyObject(this.props.error) == false) { return (
); } @@ -83,9 +193,16 @@ var Checkouts = React.createClass({ dataField = "payer_name" > Payer Name + + + Refund + {this.buildModal()} ); } @@ -96,7 +213,9 @@ const mapStateToProps = (state) => { return { checkoutInfo:state.wepay_checkout.checkout.checkoutInfo, email: state.wepay_user.searchedUser, - error: state.errors.info + error: state.errors.info, + submitted_refund: state.wepay_checkout.checkout.submitted_refund, + successful_refund: state.wepay_checkout.checkout.successful_refund, } } diff --git a/reducers/checkouts.js b/reducers/checkouts.js index 55fe331..e3fc6d0 100644 --- a/reducers/checkouts.js +++ b/reducers/checkouts.js @@ -5,7 +5,7 @@ import { combineReducers } from 'redux' import { SEARCH, INVALIDATE, - REQUEST, RECEIVE + REQUEST, RECEIVE, REFUND, RECEIVE_REFUND, CLEAR_REFUND } from '../actions/checkouts' function searchedCheckout(state = {}, action) { @@ -17,28 +17,69 @@ function searchedCheckout(state = {}, action) { } } +function updateSingleCheckout(checkout, action) { + return Object.assign({}, checkout, action.checkout); +} + +function updateCheckout(state, action) { + console.log("Updating checkout: ", action) + for (var i = 0; i < state.checkoutInfo.length; i++) { + if(state.checkoutInfo[i].checkout_id == action.checkout_id) { + state.checkoutInfo = [ + ...state.checkoutInfo.slice(0, i), + updateSingleCheckout(state.checkoutInfo[i], action), + ...state.checkoutInfo.slice(i+1) + ]; + break; + } + } + return state +} + + + function checkout_base(state = { isFetching: false, didInvalidate: false, + submitted_refund: false, + successful_refund:false, checkoutInfo: [] }, action) { switch (action.type) { case INVALIDATE: - return Object.assign({}, state, { - didInvalidate: true - }) + return Object.assign({}, state, { + didInvalidate: true + }) case REQUEST: - return Object.assign({}, state, { - isFetching: true, - didInvalidate: false - }) + return Object.assign({}, state, { + isFetching: true, + didInvalidate: false + }) case RECEIVE: - return Object.assign({}, state, { - isFetching: false, - didInvalidate: false, - checkoutInfo: action.checkout, - lastUpdated: action.receivedAt - }) + if (action.checkout_id) { + return Object.assign({}, state, updateCheckout(state, action)); + } + return Object.assign({}, state, { + isFetching: false, + didInvalidate: false, + checkoutInfo: action.checkout, + lastUpdated: action.receivedAt + }) + case REFUND: + return Object.assign({}, state, { + submitted_refund: true, + successful_refund:false, + }) + case RECEIVE_REFUND: + return Object.assign({}, state, { + submitted_refund: false, + successful_refund:true, + }) + case CLEAR_REFUND: + return Object.assign({}, state, { + submitted_refund: false, + successful_refund:false, + }) default: return state } @@ -49,6 +90,9 @@ function checkout(state = {}, action) { case INVALIDATE: case RECEIVE: case REQUEST: + case RECEIVE_REFUND: + case REFUND: + case CLEAR_REFUND: return Object.assign({}, state, checkout_base(state, action)) default: return state diff --git a/server.js b/server.js index 0f37b86..13eb8dc 100644 --- a/server.js +++ b/server.js @@ -60,13 +60,42 @@ function getData(req, res, wepay_endpoint) { } } +function getDataWithPackage(req, res, wepay_endpoint, package) { + email = req.body.email; + + console.log("Getting info for: ", wepay_endpoint, email, package); + res.setHeader('Content-Type', 'application/json'); + + if (email && email == "giovannib+test05171604KYC@wepay.com") { + var wepay_settings = { + "access_token": "STAGE_d452ad6379b3b60cdcc1e91e673906bac0922c3a143b53c12ef8f9c18c5f8228" + } + var wepay = new WePay(wepay_settings); + wepay.use_staging(); + + try { + wepay.call(wepay_endpoint, package, function(response) { + wepay_response = JSON.parse(response.toString()); + sendResponse(wepay_response, res); + }); + } + catch(error) { + console.log(error); + } + + } + else { + console.log("ERROR"); + + res.status(400).send(JSON.stringify({"error_code":400, "error_description":"database entry not found", "error_message":"No user exists with that email! Please try again."})); + } +} + function getDataWithAccountId(req, res, wepay_endpoint) { email = req.body.email; account_id = req.body.account_id; console.log("Getting info for: ", wepay_endpoint, email, account_id); - res.setHeader('Content-Type', 'application/json'); - if (email && email == "giovannib+test05171604KYC@wepay.com") { var wepay_settings = { "access_token": "STAGE_d452ad6379b3b60cdcc1e91e673906bac0922c3a143b53c12ef8f9c18c5f8228" @@ -88,6 +117,7 @@ function getDataWithAccountId(req, res, wepay_endpoint) { } else { console.log("ERROR"); + res.setHeader('Content-Type', 'application/json'); res.status(400).send(JSON.stringify({"error_code":400, "error_description":"database entry not found", "error_message":"No user exists with that email! Please try again."})); } } @@ -113,7 +143,13 @@ app.post('/account', function(req, res){ * By default this will load 50 of the most recent checkouts */ app.post("/checkout", function(req, res) { - getDataWithAccountId(req, res, "/checkout/find"); + if (req.body.checkout_id) { + var package = {"checkout_id":req.body.checkout_id} + getDataWithPackage(req, res, "/checkout", package); + } + else { + getDataWithAccountId(req, res, "/checkout/find"); + } }) app.post("/user/resend_confirmation", function(req, res){ @@ -124,6 +160,14 @@ app.post("/withdrawal", function(req, res){ getDataWithAccountId(req, res, "/withdrawal/find"); }) +app.post("/refund", function(req, res) { + var package = {"checkout_id":req.body.checkout_id, "refund_reason":req.body.refund_reason}; + if (req.body.amount) { + package['amount'] = req.body.amount; + } + getDataWithPackage(req, res, "/checkout/refund", package); +}) + app.listen(port, function(error) { if (error) { console.error(error)