Commit 1bd98e42 authored by Michael Iseard's avatar Michael Iseard
Browse files

Initial work on checking if Mollie account can receive recurring payments

parent d2823a54
...@@ -255,7 +255,7 @@ class Settings { ...@@ -255,7 +255,7 @@ class Settings {
'amount_type' => 'both', 'amount_type' => 'both',
'fixed_amounts' => '1,5,20,50', 'fixed_amounts' => '1,5,20,50',
'campaign_goal' => '', 'campaign_goal' => '',
'donation_type' => 'both', 'donation_type' => 'oneoff',
'protected' => true, 'protected' => true,
], ],
], ],
......
...@@ -116,7 +116,7 @@ class ActivatorService { ...@@ -116,7 +116,7 @@ class ActivatorService {
} }
if ( version_compare( $old_version, '2.3.8', '<' ) ) { if ( version_compare( $old_version, '2.3.9', '<' ) ) {
// Setting now replaced by single 'vendor_mollie' setting // Setting now replaced by single 'vendor_mollie' setting
if(Settings::update_setting( 'vendor_mollie', [ if(Settings::update_setting( 'vendor_mollie', [
......
...@@ -325,10 +325,11 @@ class PaymentService extends AbstractService { ...@@ -325,10 +325,11 @@ class PaymentService extends AbstractService {
*/ */
public function check_api_keys( WP_REST_Request $request ) { public function check_api_keys( WP_REST_Request $request ) {
Settings::update_array( 'vendor_mollie', [ Settings::update_array( 'vendor_mollie',
'connected' => false, [
'recurring' => false 'connected' => false,
] ); 'recurring' => false,
] );
$mode = sanitize_text_field( $request['apiMode'] ); $mode = sanitize_text_field( $request['apiMode'] );
$api_key = sanitize_text_field( $request[ $mode . 'Key' ] ); $api_key = sanitize_text_field( $request[ $mode . 'Key' ] );
...@@ -342,25 +343,32 @@ class PaymentService extends AbstractService { ...@@ -342,25 +343,32 @@ class PaymentService extends AbstractService {
} }
// Test the api key. // Test the api key.
$result = $this->vendor->test_api_connection( $api_key ); $result = $this->vendor->refresh_api_connection( $api_key );
if ( $result ) { if ( $result ) {
Settings::update_array( 'vendor_mollie', [ Settings::update_array( 'vendor_mollie',
'mode' => $mode, [
$mode . '_key' => $api_key, 'mode' => $mode,
'connected' => 1, $mode . '_key' => $api_key,
'recurring' => $this->vendor->get_payment_methods()->count > 0 ?? 0 'connected' => 1,
] ); 'recurring' => $this->vendor->get_payment_methods()->count > 0 ?? 0,
] );
wp_send_json_success( wp_send_json_success(
/* translators: %s: API mode */ [
sprintf( __( '%s API key connection was successful!', 'kudos-donations' ), ucfirst( $mode ) ) /* translators: %s: API mode */
'message' => sprintf( __( '%s API key connection was successful!', 'kudos-donations' ),
ucfirst( $mode ) ),
'setting' => Settings::get_setting( 'vendor_mollie' ),
]
); );
} else { } else {
wp_send_json_error( wp_send_json_error(
/* translators: %s: API mode */ [
sprintf( __( 'Error connecting with Mollie, please check the %s API key and try again.', /* translators: %s: API mode */
'kudos-donations' ), 'message' => sprintf( __( 'Error connecting with Mollie, please check the %s API key and try again.',
ucfirst( $mode ) ) 'kudos-donations' ),
ucfirst( $mode ) ),
]
); );
} }
} }
......
...@@ -49,7 +49,7 @@ abstract class AbstractVendor extends AbstractService { ...@@ -49,7 +49,7 @@ abstract class AbstractVendor extends AbstractService {
* @return bool * @return bool
* @since 1.0.0 * @since 1.0.0
*/ */
abstract public function test_api_connection( string $api_key ): bool; abstract public function refresh_api_connection( string $api_key ): bool;
/** /**
* Gets specified payment * Gets specified payment
......
...@@ -57,10 +57,10 @@ class MollieVendor extends AbstractVendor { ...@@ -57,10 +57,10 @@ class MollieVendor extends AbstractVendor {
$this->mollie_api = new MollieApiClient(); $this->mollie_api = new MollieApiClient();
$settings = Settings::get_setting('vendor_mollie'); $settings = Settings::get_setting( 'vendor_mollie' );
$this->api_mode = $settings['mode']; $this->api_mode = $settings['mode'];
$this->api_key = isset($settings[ $this->api_mode . '_key' ]) ? $settings[ $this->api_mode . '_key' ] : ''; $this->api_key = isset( $settings[ $this->api_mode . '_key' ] ) ? $settings[ $this->api_mode . '_key' ] : '';
if ( $this->api_key ) { if ( $this->api_key ) {
try { try {
...@@ -109,7 +109,7 @@ class MollieVendor extends AbstractVendor { ...@@ -109,7 +109,7 @@ class MollieVendor extends AbstractVendor {
$mapper = new MapperService( SubscriptionEntity::class ); $mapper = new MapperService( SubscriptionEntity::class );
/** @var SubscriptionEntity $subscription */ /** @var SubscriptionEntity $subscription */
$subscription = $mapper->get_one_by( [ 'subscription_id' => $subscription_id ] ); $subscription = $mapper->get_one_by( [ 'subscription_id' => $subscription_id ] );
$customer_id = $subscription->customer_id; $customer_id = $subscription->customer_id;
$customer = $this->get_customer( $customer_id ); $customer = $this->get_customer( $customer_id );
...@@ -128,6 +128,7 @@ class MollieVendor extends AbstractVendor { ...@@ -128,6 +128,7 @@ class MollieVendor extends AbstractVendor {
} catch ( ApiException $e ) { } catch ( ApiException $e ) {
$this->logger->critical( $e->getMessage() ); $this->logger->critical( $e->getMessage() );
return false; return false;
} }
...@@ -141,7 +142,7 @@ class MollieVendor extends AbstractVendor { ...@@ -141,7 +142,7 @@ class MollieVendor extends AbstractVendor {
* @return bool * @return bool
* @since 1.0.0 * @since 1.0.0
*/ */
public function test_api_connection( string $api_key ): bool { public function refresh_api_connection( string $api_key ): bool {
if ( ! $api_key ) { if ( ! $api_key ) {
return false; return false;
...@@ -370,15 +371,15 @@ class MollieVendor extends AbstractVendor { ...@@ -370,15 +371,15 @@ class MollieVendor extends AbstractVendor {
* *
* @return BaseCollection|MethodCollection|null * @return BaseCollection|MethodCollection|null
*/ */
public function get_payment_methods($sequenceType='recurring') { public function get_payment_methods( $sequenceType = 'recurring' ) {
try { try {
return $this->mollie_api->methods->allActive([ return $this->mollie_api->methods->allActive( [
'sequenceType' => $sequenceType, 'sequenceType' => $sequenceType,
]); ] );
} catch (ApiException $e) { } catch ( ApiException $e ) {
$this->logger->critical( $e->getMessage(), [ 'payment' => $payment_array ] ); $this->logger->critical( $e->getMessage(), [ 'payment' => $payment_array ] );
return null; return null;
......
...@@ -69,12 +69,22 @@ const ButtonIcon = (props) => { ...@@ -69,12 +69,22 @@ const ButtonIcon = (props) => {
d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"/> d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"/>
</svg> </svg>
) )
case 'sync':
return (
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path fill="currentColor"
d="M440.65 12.57l4 82.77A247.16 247.16 0 0 0 255.83 8C134.73 8 33.91 94.92 12.29 209.82A12 12 0 0 0 24.09 224h49.05a12 12 0 0 0 11.67-9.26 175.91 175.91 0 0 1 317-56.94l-101.46-4.86a12 12 0 0 0-12.57 12v47.41a12 12 0 0 0 12 12H500a12 12 0 0 0 12-12V12a12 12 0 0 0-12-12h-47.37a12 12 0 0 0-11.98 12.57zM255.83 432a175.61 175.61 0 0 1-146-77.8l101.8 4.87a12 12 0 0 0 12.57-12v-47.4a12 12 0 0 0-12-12H12a12 12 0 0 0-12 12V500a12 12 0 0 0 12 12h47.35a12 12 0 0 0 12-12.6l-4.15-82.57A247.17 247.17 0 0 0 255.83 504c121.11 0 221.93-86.92 243.55-201.82a12 12 0 0 0-11.8-14.18h-49.05a12 12 0 0 0-11.67 9.26A175.86 175.86 0 0 1 255.83 432z"/>
</svg>
)
} }
} }
return ( return (
<Icon <Icon
size="16" size="16"
className={props.className}
icon={icon()} icon={icon()}
/> />
) )
......
const {Dashicon} = wp.components const {Dashicon} = wp.components
const Info = (props) => { const Info = ({level="info", children}) => {
let text = 'kd-text-gray-500'
let icon = 'info'
switch (level) {
case "warning":
text = 'kd-text-orange-700'
icon = 'warning'
break;
}
return ( return (
<div className="kudos-admin-info kd-text-gray-500 kd-flex kd-items-center kd-justify-start"> <div className={"kd-flex kd-items-center kd-justify-start " + text}>
<Dashicon className="kd-mr-1" icon={props.icon ?? "info"}/> <Dashicon className="kd-mr-1" icon={icon}/>
<i>{props.children}</i> <i>{children}</i>
</div> </div>
) )
......
...@@ -10,6 +10,7 @@ const { ...@@ -10,6 +10,7 @@ const {
CardFooter, CardFooter,
CheckboxControl, CheckboxControl,
ClipboardButton, ClipboardButton,
Disabled,
RadioControl, RadioControl,
TextControl, TextControl,
ToggleControl ToggleControl
...@@ -23,6 +24,21 @@ const CampaignPanel = ({settings, campaign, removeCampaign, handleInputChange, a ...@@ -23,6 +24,21 @@ const CampaignPanel = ({settings, campaign, removeCampaign, handleInputChange, a
setHasCopied(false) setHasCopied(false)
}, [campaign]) }, [campaign])
let donation_type = <RadioControl
selected={campaign.donation_type || 'both'}
disabled={settings._kudos_vendor_mollie.recurring ? null : true}
help={__('The donation type of the form, set to "both" to allow donor to choose.', 'kudos-donations')}
options={[
{label: __('One-off', 'kudos-donations'), value: 'oneoff'},
{label: __('Subscription', 'kudos-donations'), value: 'recurring'},
{label: __('Both', 'kudos-donations'), value: 'both'},
]}
onChange={(value) => {
campaign.donation_type = value
handleInputChange('_kudos_campaigns', settings._kudos_campaigns)
}}
/>
return ( return (
<div id={"campaign-" + campaign.id}> <div id={"campaign-" + campaign.id}>
<SettingCard title={__('General', 'kudos-donations')} id="campaignPanel" settings={settings} campaign={campaign} handleInputChange={handleInputChange}> <SettingCard title={__('General', 'kudos-donations')} id="campaignPanel" settings={settings} campaign={campaign} handleInputChange={handleInputChange}>
...@@ -111,19 +127,14 @@ const CampaignPanel = ({settings, campaign, removeCampaign, handleInputChange, a ...@@ -111,19 +127,14 @@ const CampaignPanel = ({settings, campaign, removeCampaign, handleInputChange, a
<SettingCard title={__('Donation type', 'kudos-donations')}> <SettingCard title={__('Donation type', 'kudos-donations')}>
<RadioControl {(!settings._kudos_vendor_mollie.recurring) ?
selected={campaign.donation_type || 'both'} <Disabled>
help={__('The donation type of the form, set to "both" to allow donor to choose.', 'kudos-donations')} {donation_type}
options={[ <Info level="warning">
{label: __('One-off', 'kudos-donations'), value: 'oneoff'}, {__('You need to enable SEPA Direct Debit or Credit card in your Mollie account to use supscription payments', 'kudos-donations')}
{label: __('Subscription', 'kudos-donations'), value: 'recurring'}, </Info>
{label: __('Both', 'kudos-donations'), value: 'both'}, </Disabled>
]} : donation_type }
onChange={(value) => {
campaign.donation_type = value
handleInputChange('_kudos_campaigns', settings._kudos_campaigns)
}}
/>
</SettingCard> </SettingCard>
......
import {Btn} from "../Btn" import {Btn} from "../Btn"
import {SettingCard} from "../SettingCard" import {SettingCard} from "../SettingCard"
import {ButtonIcon} from "../ButtonIcon"
const {__} = wp.i18n const {__} = wp.i18n
const {BaseControl, ButtonGroup, PanelRow} = wp.components const {BaseControl, Button, ButtonGroup, PanelRow} = wp.components
const {useState} = wp.element
const MollieApiModePanel = (props) => { const MollieApiModePanel = (props) => {
const [isBusy, setIsBusy] = useState(false)
const vendorMollie = props.settings._kudos_vendor_mollie
const selected = vendorMollie['mode']
const handleChange = (id, value) => { const handleChange = (id, value) => {
props.mollieChanged() props.mollieChanged()
props.handleInputChange(id, value) props.handleInputChange(id, value)
} }
const vendorMollie = props.settings._kudos_vendor_mollie const refresh = () => {
const selected = vendorMollie['mode'] setIsBusy(true)
props.checkApiKey(() => setIsBusy(false))
}
return ( return (
<SettingCard title={__('API mode', 'kudos-donations')}> <SettingCard title={__('API mode', 'kudos-donations')}>
...@@ -53,6 +62,18 @@ const MollieApiModePanel = (props) => { ...@@ -53,6 +62,18 @@ const MollieApiModePanel = (props) => {
</PanelRow> </PanelRow>
</BaseControl> </BaseControl>
<BaseControl
help={__("Use this if you have made changes in Mollie such as enabling SEPA Direct Debit or Credit Card.", 'kudos-donations')}
>
<Button
isLink
icon={(<ButtonIcon icon='sync' className={(isBusy ? 'kd-animate-spin' : '')}/>)}
onClick={() => refresh()}
>
{__('Refresh API', 'kudos-donations')}
</Button>
</BaseControl>
</SettingCard> </SettingCard>
) )
} }
......
...@@ -10,6 +10,7 @@ const MollieTab = (props) => { ...@@ -10,6 +10,7 @@ const MollieTab = (props) => {
<MollieApiModePanel <MollieApiModePanel
settings={props.settings} settings={props.settings}
mollieChanged={props.mollieChanged} mollieChanged={props.mollieChanged}
checkApiKey={props.checkApiKey}
handleInputChange={props.handleInputChange} handleInputChange={props.handleInputChange}
/> />
<CardDivider/> <CardDivider/>
......
...@@ -66,12 +66,6 @@ class KudosAdmin extends Component { ...@@ -66,12 +66,6 @@ class KudosAdmin extends Component {
mollieChanged() { mollieChanged() {
this.setState({ this.setState({
isMollieEdited: true, isMollieEdited: true,
settings: {
_kudos_vendor_mollie: {
...this.state.settings._kudos_vendor_mollie,
connected: false
}
},
}) })
} }
...@@ -79,7 +73,8 @@ class KudosAdmin extends Component { ...@@ -79,7 +73,8 @@ class KudosAdmin extends Component {
updateQueryParameter('tab_name', tab) updateQueryParameter('tab_name', tab)
} }
checkApiKey() { checkApiKey(callback) {
this.setState({ this.setState({
checkingApi: true, checkingApi: true,
isAPISaving: true, isAPISaving: true,
...@@ -99,17 +94,24 @@ class KudosAdmin extends Component { ...@@ -99,17 +94,24 @@ class KudosAdmin extends Component {
}, },
}) })
.then((response) => { .then((response) => {
this.showNotice(response.data.data) this.showNotice(response.data.data.message)
this.setState({ this.setState({
settings: {
_kudos_vendor_mollie: {
...this.state.settings._kudos_vendor_mollie,
connected: response.data.success
}
},
checkingApi: false, checkingApi: false,
isAPISaving: false, isAPISaving: false,
}) })
if(response.data.success) {
this.setState({
settings: {
...this.state.settings,
_kudos_vendor_mollie: {
...response.data.data.setting
}
}
})
}
if(typeof callback === "function") {
callback()
}
}) })
} }
...@@ -249,6 +251,8 @@ class KudosAdmin extends Component { ...@@ -249,6 +251,8 @@ class KudosAdmin extends Component {
<MollieTab <MollieTab
settings={this.state.settings} settings={this.state.settings}
mollieChanged={this.mollieChanged} mollieChanged={this.mollieChanged}
showNotice={this.showNotice}
checkApiKey={this.checkApiKey}
handleInputChange={this.handleInputChange} handleInputChange={this.handleInputChange}
/> />
}, },
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment