+ );
+}
+
+export default Card;
diff --git a/web/src/components/Common/PaymentInput/Country.js b/web/src/components/Common/PaymentInput/Country.js
new file mode 100644
index 00000000..848b1d19
--- /dev/null
+++ b/web/src/components/Common/PaymentInput/Country.js
@@ -0,0 +1,46 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import React from 'react';
+
+import classnames from 'classnames';
+
+import CountrySelect from './CountrySelect';
+import styles from './PaymentInput.module.scss';
+
+function NameOnCard({ value, onUpdate, containerClassName, labelClassName }) {
+ return (
+
+ );
+}
+
+export default NameOnCard;
diff --git a/web/src/components/Subscription/Checkout/CountrySelect.js b/web/src/components/Common/PaymentInput/CountrySelect.js
similarity index 100%
rename from web/src/components/Subscription/Checkout/CountrySelect.js
rename to web/src/components/Common/PaymentInput/CountrySelect.js
diff --git a/web/src/components/Subscription/Checkout/CountrySelect.module.scss b/web/src/components/Common/PaymentInput/CountrySelect.module.scss
similarity index 100%
rename from web/src/components/Subscription/Checkout/CountrySelect.module.scss
rename to web/src/components/Common/PaymentInput/CountrySelect.module.scss
diff --git a/web/src/components/Common/PaymentInput/NameOnCard.js b/web/src/components/Common/PaymentInput/NameOnCard.js
new file mode 100644
index 00000000..3e9f985f
--- /dev/null
+++ b/web/src/components/Common/PaymentInput/NameOnCard.js
@@ -0,0 +1,49 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import React from 'react';
+
+import classnames from 'classnames';
+
+import styles from './PaymentInput.module.scss';
+
+function NameOnCard({ value, onUpdate, containerClassName, labelClassName }) {
+ return (
+
+
+
+ );
+}
+
+export default NameOnCard;
diff --git a/web/src/components/Common/PaymentInput/PaymentInput.module.scss b/web/src/components/Common/PaymentInput/PaymentInput.module.scss
new file mode 100644
index 00000000..289fbdab
--- /dev/null
+++ b/web/src/components/Common/PaymentInput/PaymentInput.module.scss
@@ -0,0 +1,49 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+@import '../../App/responsive';
+@import '../../App/theme';
+@import '../../App/font';
+@import '../../App/rem';
+
+.input {
+ margin-top: rem(8px);
+}
+
+.card-row {
+ display: flex;
+}
+
+.number {
+ flex-grow: 1;
+}
+
+.card-number {
+ // match the height of the stripe element with other inputs
+ padding: rem(10.4px) rem(12px);
+ border: 2px solid $border-color;
+
+ &.card-number-active {
+ border: 2px solid $third;
+ }
+}
+
+.countries-select {
+ width: 100%;
+ display: block;
+}
diff --git a/web/src/components/Settings/Billing/PaymentMethodModal/Form.js b/web/src/components/Settings/Billing/PaymentMethodModal/Form.js
new file mode 100644
index 00000000..a0c442ce
--- /dev/null
+++ b/web/src/components/Settings/Billing/PaymentMethodModal/Form.js
@@ -0,0 +1,135 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import React, { useState, useRef } from 'react';
+import { injectStripe } from 'react-stripe-elements';
+
+import Button from '../../../Common/Button';
+import NameOnCardInput from '../../../Common/PaymentInput/NameOnCard';
+import CardInput from '../../../Common/PaymentInput/Card';
+import CountryInput from '../../../Common/PaymentInput/Country';
+
+import settingsStyles from '../../Settings.module.scss';
+import * as paymentService from '../../../../services/payment';
+import styles from './Form.module.scss';
+
+function Form({
+ stripe,
+ nameOnCard,
+ setNameOnCard,
+ billingCountry,
+ setBillingCountry,
+ inProgress,
+ onDismiss,
+ setSuccessMsg,
+ setInProgress,
+ doGetSource,
+ setErrMessage
+}) {
+ const [cardElementLoaded, setCardElementLoaded] = useState(false);
+ const cardElementRef = useRef(null);
+
+ async function handleSubmit(e) {
+ e.preventDefault();
+
+ if (!cardElementLoaded) {
+ return;
+ }
+ if (!nameOnCard) {
+ setErrMessage('Please enter the name on card');
+ return;
+ }
+ if (!billingCountry) {
+ setErrMessage('Please enter the country');
+ return;
+ }
+
+ setSuccessMsg('');
+ setErrMessage('');
+ setInProgress(true);
+
+ try {
+ const { source, error } = await stripe.createSource({
+ type: 'card',
+ currency: 'usd',
+ owner: {
+ name: nameOnCard
+ }
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ await paymentService.updateSource({ source, country: billingCountry });
+ await doGetSource();
+
+ setSuccessMsg('Your payment method was successfully updated.');
+ setInProgress(false);
+ onDismiss();
+ } catch (err) {
+ setErrMessage(`An error occurred: ${err.message}`);
+ setInProgress(false);
+ }
+ }
+
+ return (
+
+ );
+}
+
+export default injectStripe(Form);
diff --git a/web/src/components/Settings/Billing/PaymentMethodModal/Form.module.scss b/web/src/components/Settings/Billing/PaymentMethodModal/Form.module.scss
new file mode 100644
index 00000000..b076ad4d
--- /dev/null
+++ b/web/src/components/Settings/Billing/PaymentMethodModal/Form.module.scss
@@ -0,0 +1,27 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+@import '../../../App/rem';
+@import '../../../App/font';
+@import '../../../App/theme';
+
+.input-row {
+ &:not(:first-child) {
+ margin-top: rem(10px);
+ }
+}
diff --git a/web/src/components/Settings/Billing/PaymentMethodModal/index.js b/web/src/components/Settings/Billing/PaymentMethodModal/index.js
new file mode 100644
index 00000000..bb977656
--- /dev/null
+++ b/web/src/components/Settings/Billing/PaymentMethodModal/index.js
@@ -0,0 +1,93 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * This file is part of Dnote.
+ *
+ * Dnote is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Dnote is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Dnote. If not, see .
+ */
+
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+import { StripeProvider, Elements } from 'react-stripe-elements';
+
+import Modal, { Header, Body } from '../../../Common/Modal';
+import Flash from '../../../Common/Flash';
+import Form from './Form';
+
+function PaymentMethodModal({
+ isOpen,
+ onDismiss,
+ setSuccessMsg,
+ doGetSource,
+ stripe
+}) {
+ const [nameOnCard, setNameOnCard] = useState('');
+ const [billingCountry, setBillingCountry] = useState('');
+ const [inProgress, setInProgress] = useState(false);
+ const [errMessage, setErrMessage] = useState('');
+
+ const labelId = 'payment-method-modal';
+
+ function handleDismiss() {
+ setNameOnCard('');
+ setBillingCountry('');
+ onDismiss();
+ }
+
+ return (
+
+
+
+ {errMessage && (
+ {
+ setErrMessage('');
+ }}
+ >
+ {errMessage}
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const mapDispatchToProps = {};
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(PaymentMethodModal);
diff --git a/web/src/components/Settings/Billing/index.js b/web/src/components/Settings/Billing/index.js
index 93f22e41..ae3e33b4 100644
--- a/web/src/components/Settings/Billing/index.js
+++ b/web/src/components/Settings/Billing/index.js
@@ -25,6 +25,7 @@ import Header from '../../Common/Page/Header';
import Body from '../../Common/Page/Body';
import Flash from '../../Common/Flash';
import CancelPlanModal from './CancelPlanModal';
+import PaymentMethodModal from './PaymentMethodModal';
import {
getSubscription,
clearSubscription,
@@ -35,6 +36,7 @@ import SettingRow from '../SettingRow';
import PlanRow from './PlanRow';
import Placeholder from './Placeholder';
import * as paymentService from '../../../services/payment';
+import { useScript } from '../../../libs/hooks';
import settingsStyles from '../Settings.module.scss';
@@ -104,7 +106,11 @@ function CancelRow({ setIsPlanModalOpen }) {
);
}
-function PaymentMethodRow({ source }) {
+function PaymentMethodRow({
+ stripeLoaded,
+ source,
+ setIsPaymentMethodModalOpen
+}) {
let value;
if (source.brand) {
value = `${source.brand} ending in ${source.last4}. expiry ${
@@ -114,18 +120,39 @@ function PaymentMethodRow({ source }) {
value = 'No payment method';
}
- return ;
+ return (
+ {
+ setIsPaymentMethodModalOpen(true);
+ }}
+ disabled={!stripeLoaded}
+ >
+ Update
+
+ }
+ />
+ );
}
function Content({
subscription,
source,
setIsPlanModalOpen,
+ setIsPaymentMethodModalOpen,
successMsg,
failureMsg,
setSuccessMsg,
setFailureMsg,
- doGetSubscription
+ doGetSubscription,
+ stripeLoaded
}) {
return (