Page MenuHomePhorge

D2239.1774856688.diff
No OneTemporary

Authored By
Unknown
Size
38 KB
Referenced Files
None
Subscribers
None

D2239.1774856688.diff

diff --git a/src/app/Http/Controllers/API/V4/PaymentsController.php b/src/app/Http/Controllers/API/V4/PaymentsController.php
--- a/src/app/Http/Controllers/API/V4/PaymentsController.php
+++ b/src/app/Http/Controllers/API/V4/PaymentsController.php
@@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use App\Providers\PaymentProvider;
use App\Wallet;
+use App\Payment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
@@ -55,6 +56,7 @@
$mandate = [
'currency' => 'CHF',
'description' => \config('app.name') . ' Auto-Payment Setup',
+ 'methodId' => $request->methodId
];
// Normally the auto-payment setup operation is 0, if the balance is below the threshold
@@ -217,8 +219,9 @@
$request = [
'type' => PaymentProvider::TYPE_ONEOFF,
- 'currency' => 'CHF',
+ 'currency' => $request->currency,
'amount' => $amount,
+ 'methodId' => $request->methodId,
'description' => \config('app.name') . ' Payment',
];
@@ -231,6 +234,32 @@
return response()->json($result);
}
+ /**
+ * Delete a pending payment.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function cancel(Request $request)
+ {
+ $current_user = Auth::guard()->user();
+
+ // TODO: Wallet selection
+ $wallet = $current_user->wallets()->first();
+
+ $paymentId = $request->payment;
+
+ $provider = PaymentProvider::factory($wallet);
+
+ $result = $provider->cancel($wallet, $paymentId);
+
+ //TODO handle errors?
+ $result['status'] = 'success';
+
+ return response()->json($result);
+ }
+
/**
* Update payment status (and balance).
*
@@ -325,4 +354,134 @@
return $mandate;
}
+
+
+ /**
+ * Return an array of whitelisted payment methods with override values.
+ *
+ * @param string $type The payment type for which we require a method.
+ *
+ * @return array Array of methods
+ */
+ private static function paymentMethodsWhitelist($type): ?array
+ {
+ switch ($type) {
+ case PaymentProvider::TYPE_ONEOFF:
+ return [
+ 'creditcard' => [
+ 'id' => 'creditcard',
+ 'icon' => ['prefix' => 'far', 'name' => 'credit-card']
+ ],
+ 'paypal' => [
+ 'id' => 'paypal',
+ 'icon' => ['prefix' => 'fab', 'name' => 'paypal']
+ ],
+ 'banktransfer' => [
+ 'id' => 'banktransfer',
+ 'icon' => ['prefix' => 'fas', 'name' => 'university']
+ ]
+ ];
+ case PaymentProvider::TYPE_RECURRING:
+ return [
+ 'creditcard' => [
+ 'id' => 'creditcard',
+ 'icon' => ['prefix' => 'far', 'name' => 'credit-card']
+ ],
+ 'paypal' => [
+ 'id' => 'paypal',
+ 'icon' => ['prefix' => 'fab', 'name' => 'paypal']
+ ]
+ ];
+ }
+ }
+
+
+ /**
+ * List supported payment methods.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public static function paymentMethods(Request $request)
+ {
+ $user = Auth::guard()->user();
+
+ // TODO: Wallet selection
+ $wallet = $user->wallets()->first();
+
+ $provider = PaymentProvider::factory($wallet);
+
+ $providerMethods = $provider->paymentMethods($request->type);
+
+ \Log::debug("Provider methods" . var_export(json_encode($providerMethods), true));
+
+ $whitelistMethods = self::paymentMethodsWhitelist($request->type);
+
+ $methods = [];
+
+ // Use only whitelisted methods, and apply values from whitelist (overriding the backend)
+ foreach ($whitelistMethods as $id => $whitelistMethod) {
+ if (array_key_exists($id, $providerMethods)) {
+ $methods[] = array_merge($providerMethods[$id], $whitelistMethod);
+ }
+ }
+ return response()->json($methods);
+ }
+
+
+ /**
+ * List pending payments.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public static function payments(Request $request)
+ {
+ $user = Auth::guard()->user();
+
+ // TODO: Wallet selection
+ $wallet = $user->wallets()->first();
+
+ $provider = PaymentProvider::factory($wallet);
+ $pageSize = 10;
+ $page = intval(request()->input('page')) ?: 1;
+ $hasMore = false;
+ $result = Payment::where('wallet_id', $wallet->id)
+ ->where('type', 'oneoff')
+ ->whereIn('status', ['open', 'pending', 'authorized'])
+ ->limit($pageSize + 1)
+ ->offset($pageSize * ($page - 1))
+ ->get();
+
+ if (count($result) > $pageSize) {
+ $result->pop();
+ $hasMore = true;
+ }
+
+ $result = $result->map(function ($item) use ($provider) {
+ $payment = $provider->getPayment($item->id);
+ $entry = [
+ 'id' => $item->id,
+ 'createdAt' => $item->created_at->format('Y-m-d H:i'),
+ 'type' => $item->type,
+ 'description' => $item->description,
+ 'amount' => $item->amount,
+ 'status' => $item->status,
+ 'isCancelable' => $payment['isCancelable'],
+ 'checkoutUrl' => $payment['checkoutUrl']
+ ];
+
+ return $entry;
+ });
+
+ return response()->json([
+ 'status' => 'success',
+ 'list' => $result,
+ 'count' => count($result),
+ 'hasMore' => $hasMore,
+ 'page' => $page,
+ ]);
+ }
}
diff --git a/src/app/Payment.php b/src/app/Payment.php
--- a/src/app/Payment.php
+++ b/src/app/Payment.php
@@ -7,11 +7,13 @@
/**
* A payment operation on a wallet.
*
- * @property int $amount Amount of money in cents
+ * @property int $amount Amount of money in cents of CHF
* @property string $description Payment description
* @property string $id Mollie's Payment ID
* @property \App\Wallet $wallet The wallet
* @property string $wallet_id The ID of the wallet
+ * @property int $amountInCurrency Amount of money in cents of $currency
+ * @property string $currency Currency of this payment
*/
class Payment extends Model
{
@@ -30,6 +32,8 @@
'provider',
'status',
'type',
+ 'currency',
+ 'amountInCurrency',
];
/**
diff --git a/src/app/Providers/Payment/Mollie.php b/src/app/Providers/Payment/Mollie.php
--- a/src/app/Providers/Payment/Mollie.php
+++ b/src/app/Providers/Payment/Mollie.php
@@ -66,7 +66,7 @@
'webhookUrl' => Utils::serviceUrl('/api/webhooks/payment/mollie'),
'redirectUrl' => Utils::serviceUrl('/wallet'),
'locale' => 'en_US',
- // 'method' => 'creditcard',
+ 'method' => $payment['methodId']
];
// Create the payment in Mollie
@@ -135,7 +135,8 @@
'id' => $mandate->id,
'isPending' => $mandate->isPending(),
'isValid' => $mandate->isValid(),
- 'method' => self::paymentMethod($mandate, 'Unknown method')
+ 'method' => self::paymentMethod($mandate, 'Unknown method'),
+ 'methodId' => $mandate->method
];
return $result;
@@ -151,6 +152,17 @@
return 'mollie';
}
+
+ private function exchangeRate(string $currency): float
+ {
+ //TODO pass in wallet and do conversion there?
+ if ($currency != 'CHF') {
+ //FIXME
+ return 0.8;
+ }
+ return 1.0;
+ }
+
/**
* Create a new payment.
*
@@ -174,20 +186,24 @@
// Register the user in Mollie, if not yet done
$customer_id = self::mollieCustomerId($wallet, true);
+ $amount = $payment['amount'];
+ $exchangeRate = $this->exchangeRate($wallet, $payment['currency']);
+ $amount = $amount * $exchangeRate;
+ $payment['amountInCurrency'] = $amount;
// Note: Required fields: description, amount/currency, amount/value
$request = [
'amount' => [
'currency' => $payment['currency'],
// a number with two decimals is required
- 'value' => sprintf('%.2f', $payment['amount'] / 100),
+ 'value' => sprintf('%.2f', $amount / 100),
],
'customerId' => $customer_id,
'sequenceType' => $payment['type'],
'description' => $payment['description'],
'webhookUrl' => Utils::serviceUrl('/api/webhooks/payment/mollie'),
'locale' => 'en_US',
- // 'method' => 'creditcard',
+ 'method' => $payment['methodId'],
'redirectUrl' => Utils::serviceUrl('/wallet') // required for non-recurring payments
];
@@ -210,6 +226,34 @@
];
}
+
+ /**
+ * Cancel a pending payment.
+ *
+ * @param \App\Wallet $wallet The wallet
+ * @param array $paymentId Payment Id
+ *
+ * @return array Provider payment data:
+ * - id: Operation identifier
+ * - redirectUrl: the location to redirect to
+ */
+ public function cancel(Wallet $wallet, $paymentId): ?array
+ {
+
+ $response = mollie()->payments()->delete($paymentId);
+
+ $payment['id'] = $response->id;
+ $payment['status'] = $response->status;
+
+ $this->storePayment($payment, $wallet->id);
+
+ return [
+ 'id' => $payment['id'],
+ 'redirectUrl' => $response->getCheckoutUrl(),
+ ];
+ }
+
+
/**
* Create a new automatic payment operation.
*
@@ -243,7 +287,7 @@
'description' => $payment['description'],
'webhookUrl' => Utils::serviceUrl('/api/webhooks/payment/mollie'),
'locale' => 'en_US',
- // 'method' => 'creditcard',
+ 'method' => $payment['methodId'],
'mandateId' => $mandate->id
];
@@ -333,6 +377,7 @@
'description' => $refund->description,
'amount' => round(floatval($refund->amount->value) * 100),
'type' => self::TYPE_REFUND,
+ //FIXME Convert amount from currency to wallet currency
// Note: we assume this is the original payment/wallet currency
];
}
@@ -348,6 +393,7 @@
'id' => $chargeback->id,
'amount' => round(floatval($chargeback->amount->value) * 100),
'type' => self::TYPE_CHARGEBACK,
+ //FIXME Convert amount from currency to wallet currency
// Note: we assume this is the original payment/wallet currency
];
}
@@ -510,4 +556,80 @@
return $default;
}
+
+ /**
+ * List supported payment methods.
+ *
+ * @param string $type type: oneoff/recurring
+ *
+ * @return array Array of array with available payment methods:
+ * - id: id of the method
+ * - name: User readable name of the payment method
+ * - minimumAmount: Minimum amount to be charged
+ * - currency: Currency used for the method
+ * - image|icon: An image (url) or icon (icon name) representing the method
+ */
+ public function paymentMethods($type): ?array
+ {
+ $availableMethods = [];
+
+ $mapResult = function ($result) use (&$availableMethods) {
+ foreach ($result as $method) {
+ $availableMethods[$method->id] = [
+ 'id' => $method->id,
+ 'name' => $method->description,
+ 'minimumAmount' => round(floatval($method->minimumAmount->value) * 100), // Converted to cents
+ 'currency' => $method->minimumAmount->currency,
+ 'exchangeRate' => $this->exchangeRate($method->minimumAmount->currency)
+ ];
+ }
+ };
+
+ // Fallback to EUR methods
+ $mapResult(mollie()->methods()->allActive(
+ [
+ 'sequenceType' => $type,
+ 'amount' => [
+ 'value' => '1.00',
+ 'currency' => 'EUR'
+ ]
+ ]
+ ));
+
+ // Prefer CHF methods
+ $mapResult(mollie()->methods()->allActive(
+ [
+ 'sequenceType' => $type,
+ 'amount' => [
+ 'value' => '1.00',
+ 'currency' => 'CHF'
+ ]
+ ]
+ ));
+
+ return $availableMethods;
+ }
+
+ /**
+ * Get a payment.
+ *
+ * @param string $paymentId Payment identifier
+ *
+ * @return array|null Payment information:
+ * - id: Payment identifier
+ * - status: Payment status
+ * - isCancelable: The payment can be canceled
+ * - chceckoutUrl: The checkout url to complete the payment or null if non
+ */
+ public function getPayment($paymentId): ?array
+ {
+ $payment = mollie()->payments()->get($paymentId);
+
+ return [
+ 'id' => $payment->id,
+ 'status' => $payment->status,
+ 'isCancelable' => $payment->isCancelable,
+ 'checkoutUrl' => $payment->getCheckoutUrl()
+ ];
+ }
}
diff --git a/src/app/Providers/Payment/Stripe.php b/src/app/Providers/Payment/Stripe.php
--- a/src/app/Providers/Payment/Stripe.php
+++ b/src/app/Providers/Payment/Stripe.php
@@ -470,4 +470,71 @@
return $default;
}
+
+ /**
+ * List supported payment methods.
+ *
+ * @param string $type type: oneoff/recurring
+ *
+ * @return array Array of array with available payment methods:
+ * - id: id of the method
+ * - name: User readable name of the payment method
+ * - minimumAmount: Minimum amount to be charged
+ * - currency: Currency used for the method
+ * - image|icon: An image (url) or icon (icon name) representing the method
+ */
+ public function paymentMethods($type): ?array
+ {
+ //TODO get this from the stripe API?
+ switch ($type) {
+ case self::TYPE_ONEOFF:
+ return [
+ [
+ 'id' => 'creditcard',
+ 'name' => "Credit Card",
+ 'minimumAmount' => "10.00",
+ 'currency' => 'CHF',
+ 'icon' => "credit-card"
+ ],
+ [
+ 'id' => 'paypal',
+ 'name' => "PayPal",
+ 'minimumAmount' => "10.00",
+ 'currency' => 'CHF',
+ 'icon' => "paypal"
+ ]
+ ];
+ case self::TYPE_RECURRING:
+ return [
+ [
+ 'id' => 'creditcard',
+ 'name' => "Credit Card",
+ 'minimumAmount' => "10.00",
+ 'currency' => 'CHF',
+ 'icon' => "credit-card"
+ ]
+ ];
+ }
+ }
+
+ /**
+ * Get a payment.
+ *
+ * @param string $paymentId Payment identifier
+ *
+ * @return array|null Payment information:
+ * - id: Payment identifier
+ * - status: Payment status
+ * - isCancelable: The payment can be canceled
+ * - chceckoutUrl: The checkout url to complete the payment or null if non
+ */
+ public function getPayment($paymentId): ?array
+ {
+ return [
+ 'id' => $paymentId,
+ 'status' => null,
+ 'isCancelable' => false,
+ 'checkoutUrl' => null
+ ];
+ }
}
diff --git a/src/app/Providers/PaymentProvider.php b/src/app/Providers/PaymentProvider.php
--- a/src/app/Providers/PaymentProvider.php
+++ b/src/app/Providers/PaymentProvider.php
@@ -194,4 +194,31 @@
$this->storePayment($refund, $wallet->id);
}
+
+ /**
+ * List supported payment methods.
+ *
+ * @param string $type type: oneoff/recurring
+ *
+ * @return array Array of array with available payment methods:
+ * - id: id of the method
+ * - name: User readable name of the payment method
+ * - minimumAmount: Minimum amount to be charged
+ * - currency: Currency used for the method
+ * - image|icon: An image (url) or icon (icon name) representing the method
+ */
+ abstract public function paymentMethods($type): ?array;
+
+ /**
+ * Get a payment.
+ *
+ * @param string $paymentId Payment identifier
+ *
+ * @return array|null Payment information:
+ * - id: Payment identifier
+ * - status: Payment status
+ * - isCancelable: The payment can be canceled
+ * - chceckoutUrl: The checkout url to complete the payment or null if non
+ */
+ abstract public function getPayment($paymentId): ?array;
}
diff --git a/src/resources/js/fontawesome.js b/src/resources/js/fontawesome.js
--- a/src/resources/js/fontawesome.js
+++ b/src/resources/js/fontawesome.js
@@ -15,6 +15,7 @@
faDownload,
faEnvelope,
faGlobe,
+ faUniversity,
faExclamationCircle,
faInfoCircle,
faLock,
@@ -30,6 +31,10 @@
faWallet
} from '@fortawesome/free-solid-svg-icons'
+import {
+ faPaypal
+} from '@fortawesome/free-brands-svg-icons'
+
// Register only these icons we need
library.add(
faCheck,
@@ -37,6 +42,8 @@
faCheckSquare,
faComments,
faCreditCard,
+ faPaypal,
+ faUniversity,
faDownload,
faEnvelope,
faExclamationCircle,
diff --git a/src/resources/themes/app.scss b/src/resources/themes/app.scss
--- a/src/resources/themes/app.scss
+++ b/src/resources/themes/app.scss
@@ -293,6 +293,33 @@
}
}
+#payment-method-selection {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ & > button {
+ padding: 1rem;
+ text-align: center;
+ white-space: nowrap;
+ margin: 0.25rem;
+ text-decoration: none;
+ width: 150px;
+ }
+
+ img {
+ width: 6rem;
+ height: 6rem;
+ margin: auto;
+ }
+
+ svg {
+ width: 6rem;
+ height: 6rem;
+ margin: auto;
+ }
+}
+
#logon-form {
flex-basis: auto; // Bootstrap issue? See logon page with width < 992
}
diff --git a/src/resources/vue/Wallet.vue b/src/resources/vue/Wallet.vue
--- a/src/resources/vue/Wallet.vue
+++ b/src/resources/vue/Wallet.vue
@@ -5,8 +5,48 @@
<div class="card-title">Account balance <span :class="wallet.balance < 0 ? 'text-danger' : 'text-success'">{{ $root.price(wallet.balance, wallet.currency) }}</span></div>
<div class="card-text">
<p v-if="wallet.notice" id="wallet-notice">{{ wallet.notice }}</p>
- <p>Add credit to your account or setup an automatic payment by using the button below.</p>
- <button type="button" class="btn btn-primary" @click="paymentDialog()">Add credit</button>
+
+ <div v-if="showPendingPayments" class="alert alert-warning">
+ You have a payments that are still in progress. See the payments tab below.
+ </div>
+ <p>
+ <button type="button" class="btn btn-primary" @click="paymentMethodForm('manual')">Add credit</button>
+ </p>
+ <div id="mandate-form" v-if="!mandate.isValid && !mandate.isPending">
+ <template v-if="mandate.id && !mandate.isValid">
+ <div class="alert alert-danger">
+ The setup of automatic payments failed. Restart the process to enable automatic top-ups.
+ </div>
+ <p>
+ <button type="button" class="btn btn-danger" @click="autoPaymentDelete">Cancel auto-payment</button>
+ </p>
+ </template>
+ <button type="button" class="btn btn-primary" @click="paymentMethodForm('auto')">Set up auto-payment</button>
+ </div>
+ <div id="mandate-info" v-else>
+ <div v-if="mandate.isDisabled" class="disabled-mandate alert alert-danger">
+ The configured auto-payment has been disabled. Top up your wallet or
+ raise the auto-payment amount.
+ </div>
+ <template v-else>
+ <p>
+ Auto-payment is <b>enabled</b> to add <b>{{ mandate.amount }} CHF</b>
+ to your account whenever your balance runs below <b>{{ mandate.balance }} CHF</b>.
+ </p>
+ <p>
+ Method of payment: {{ mandate.method }}
+ </p>
+ </template>
+ <div v-if="mandate.isPending" class="alert alert-warning">
+ The setup of the automatic payment is still in progress.
+ </div>
+ <p>
+ <button type="button" class="btn btn-danger" @click="autoPaymentDelete">Cancel auto-payment</button>
+ </p>
+ <p>
+ <button type="button" class="btn btn-primary" @click="autoPaymentChange">Change auto-payment</button>
+ </p>
+ </div>
</div>
</div>
</div>
@@ -22,6 +62,11 @@
History
</a>
</li>
+ <li v-if="showPendingPayments" class="nav-item">
+ <a class="nav-link" id="tab-payments" href="#wallet-payments" role="tab" aria-controls="wallet-payments" aria-selected="false">
+ Pending Payments
+ </a>
+ </li>
</ul>
<div class="tab-content">
<div class="tab-pane show active" id="wallet-receipts" role="tabpanel" aria-labelledby="tab-receipts">
@@ -53,6 +98,11 @@
<transaction-log v-if="walletId && loadTransactions" class="card-text" :wallet-id="walletId"></transaction-log>
</div>
</div>
+ <div class="tab-pane show" id="wallet-payments" role="tabpanel" aria-labelledby="tab-payments">
+ <div class="card-body">
+ <payment-log v-if="walletId && loadPayments" class="card-text" :wallet-id="walletId"></payment-log>
+ </div>
+ </div>
</div>
<div id="payment-dialog" class="modal" tabindex="-1" role="dialog">
@@ -65,7 +115,26 @@
</button>
</div>
<div class="modal-body">
- <div id="payment" v-if="paymentForm == 'init'">
+ <div id="payment-method" v-if="paymentForm == 'method'">
+ <form data-validation-prefix="mandate_">
+ <div id="payment-method-selection">
+ <button v-for="method in paymentMethods" :key="method.id" @click="selectPaymentMethod(method)" class="card link-profile">
+ <svg-icon v-if="method.icon" :icon="[method.icon.prefix, method.icon.name]" />
+ <img v-if="method.image" v-bind:src="method.image" />
+ <span class="name">{{ method.name }}</span>
+ </button>
+ </div>
+ </form>
+ </div>
+ <div id="manual-payment" v-if="paymentForm == 'manual'">
+ <p v-if="wallet.currency != selectedPaymentMethod.currency">
+ Here is how it works: You specify the amount by which you want to to up your wallet in {{ wallet.currency }}.
+ We will then convert this to {{ selectedPaymentMethod.currency }}, and on the next page you will be provided with the bank-details
+ to transfer the amount in {{ selectedPaymentMethod.currency }}.
+ </p>
+ <p v-if="selectedPaymentMethod.id == 'banktransfer'">
+ Please note that a bank transfer can take several days to complete.
+ </p>
<p>Choose the amount by which you want to top up your wallet.</p>
<form id="payment-form" @submit.prevent="payment">
<div class="form-group">
@@ -76,41 +145,15 @@
</span>
</div>
</div>
+ <div v-if="wallet.currency != selectedPaymentMethod.currency" class="alert alert-warning">
+ You will be charged for {{ amount * selectedPaymentMethod.exchangeRate }} {{ selectedPaymentMethod.currency }}
+ </div>
<div class="w-100 text-center">
<button type="submit" class="btn btn-primary">
<svg-icon :icon="['far', 'credit-card']"></svg-icon> Continue
</button>
</div>
</form>
- <div class="form-separator"><hr><span>or</span></div>
- <div id="mandate-form" v-if="!mandate.isValid && !mandate.isPending">
- <p>Add auto-payment, so you never run out.</p>
- <div v-if="mandate.id && !mandate.isValid" class="alert alert-danger">
- The setup of automatic payments failed. Restart the process to enable automatic top-ups.
- </div>
- <div class="w-100 text-center">
- <button type="button" class="btn btn-primary" @click="autoPaymentForm">Set up auto-payment</button>
- </div>
- </div>
- <div id="mandate-info" v-else>
- <p>
- Auto-payment is set to fill up your account by <b>{{ mandate.amount }} CHF</b>
- every time your account balance gets under <b>{{ mandate.balance }} CHF</b>.
- You will be charged via {{ mandate.method }}.
- </p>
- <div v-if="mandate.isPending" class="alert alert-warning">
- The setup of the automatic payment is still in progress.
- </div>
- <div v-else-if="mandate.isDisabled" class="disabled-mandate alert alert-danger">
- The configured auto-payment has been disabled. Top up your wallet or
- raise the auto-payment amount.
- </div>
- <p>You can cancel or change the auto-payment at any time.</p>
- <div class="form-group d-flex justify-content-around">
- <button type="button" class="btn btn-danger" @click="autoPaymentDelete">Cancel auto-payment</button>
- <button type="button" class="btn btn-primary" @click="autoPaymentChange">Change auto-payment</button>
- </div>
- </div>
</div>
<div id="auto-payment" v-if="paymentForm == 'auto'">
<form data-validation-prefix="mandate_">
@@ -173,22 +216,29 @@
<script>
import TransactionLog from './Widgets/TransactionLog'
+ import PaymentLog from './Widgets/PaymentLog'
export default {
components: {
- TransactionLog
+ TransactionLog,
+ PaymentLog
},
data() {
return {
amount: '',
- mandate: { amount: 10, balance: 0 },
+ mandate: { amount: 10, balance: 0, method: null },
paymentDialogTitle: null,
- paymentForm: 'init',
+ paymentForm: null,
+ nextForm: null,
receipts: [],
stripe: null,
loadTransactions: false,
+ loadPayments: false,
+ showPendingPayments: false,
wallet: {},
- walletId: null
+ walletId: null,
+ paymentMethods: [],
+ selectedPaymentMethod: null
}
},
mounted() {
@@ -220,17 +270,28 @@
})
.catch(this.$root.errorHandler)
+ this.loadMandate()
+
+ axios.get('/api/v4/payments/pending')
+ .then(response => {
+ this.showPendingPayments = (response.data.list.length > 0)
+ })
+
+ },
+ updated() {
$(this.$el).find('ul.nav-tabs a').on('click', e => {
e.preventDefault()
$(e.target).tab('show')
if ($(e.target).is('#tab-history')) {
this.loadTransactions = true
}
+ if ($(e.target).is('#tab-payments')) {
+ this.loadPayments = true
+ }
})
},
methods: {
- paymentDialog() {
- const dialog = $('#payment-dialog')
+ loadMandate() {
const mandate_form = $('#mandate-form')
this.$root.removeLoader(mandate_form)
@@ -246,14 +307,17 @@
this.$root.removeLoader(mandate_form)
})
}
+ },
+ selectPaymentMethod(method) {
+ this.formLock = false
+
+ this.selectedPaymentMethod = method
+ this.paymentForm = this.nextForm
+ this.paymentDialogTitle = 'Choose Amount'
this.formLock = false
- this.paymentForm = 'init'
- this.paymentDialogTitle = 'Top up your wallet'
- this.dialog = dialog.on('shown.bs.modal', () => {
- dialog.find('#amount').focus()
- }).modal()
+ setTimeout(() => { this.dialog.find('#mandate_amount').focus()}, 10)
},
payment() {
if (this.formLock) {
@@ -266,7 +330,7 @@
this.$root.clearFormValidation($('#payment-form'))
- axios.post('/api/v4/payments', {amount: this.amount}, { onFinish })
+ axios.post('/api/v4/payments', {amount: this.amount, methodId: this.selectedPaymentMethod.id, currency: this.selectedPaymentMethod.currency}, { onFinish })
.then(response => {
if (response.data.redirectUrl) {
location.href = response.data.redirectUrl
@@ -287,7 +351,9 @@
const method = this.mandate.id && (this.mandate.isValid || this.mandate.isPending) ? 'put' : 'post'
const post = {
amount: this.mandate.amount,
- balance: this.mandate.balance
+ balance: this.mandate.balance,
+ methodId: this.selectedPaymentMethod.id,
+ currency: this.selectedPaymentMethod.currency
}
this.$root.clearFormValidation($('#auto-payment form'))
@@ -324,10 +390,40 @@
}
})
},
+
+ paymentMethodForm(nextForm) {
+ const dialog = $('#payment-dialog')
+ this.formLock = false
+ this.paymentMethods = []
+
+ this.paymentForm = 'method'
+ this.paymentDialogTitle = 'Payment Method'
+ this.nextForm = nextForm
+
+ const methods = $('#payment-method')
+ this.$root.addLoader(methods)
+ axios.get('/api/v4/payments/methods', {params: {type: nextForm == 'manual' ? 'oneoff' : 'recurring'}})
+ .then(response => {
+ this.$root.removeLoader(methods)
+ this.paymentMethods = response.data
+ })
+ .catch(this.$root.errorHandler)
+
+ this.dialog = dialog.on('shown.bs.modal', () => {
+ dialog.find('#mandate_amount').focus()
+ }).modal()
+ },
autoPaymentForm(event, title) {
+ const dialog = $('#payment-dialog')
+
this.paymentForm = 'auto'
this.paymentDialogTitle = title || 'Add auto-payment'
this.formLock = false
+
+ this.dialog = dialog.on('shown.bs.modal', () => {
+ dialog.find('#mandate_amount').focus()
+ }).modal()
+
setTimeout(() => { this.dialog.find('#mandate_amount').focus()}, 10)
},
receiptDownload() {
diff --git a/src/resources/vue/Widgets/PaymentLog.vue b/src/resources/vue/Widgets/PaymentLog.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Widgets/PaymentLog.vue
@@ -0,0 +1,81 @@
+<template>
+ <div>
+ <table class="table table-sm m-0 payments">
+ <thead class="thead-light">
+ <tr>
+ <th scope="col">Date</th>
+ <th scope="col">Description</th>
+ <th scope="col" class="price">Amount</th>
+ <th scope="col">Status</th>
+ <th scope="col"></th>
+ <th scope="col"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="payment in payments" :id="'log' + payment.id" :key="payment.id">
+ <td class="datetime">{{ payment.createdAt }}</td>
+ <td class="description">{{ payment.description }}</td>
+ <td :class="'price ' + text-success">{{ amount(payment) }}</td>
+ <td>{{ payment.status }}</td>
+ <td><a v-if="payment.checkoutUrl" v-bind:href="payment.checkoutUrl">Details</a></td>
+ </tr>
+ </tbody>
+ <tfoot class="table-fake-body">
+ <tr>
+ <td :colspan="4">There are no payments for this account.</td>
+ </tr>
+ </tfoot>
+ </table>
+ <div class="text-center p-3" id="payments-loader" v-if="hasMore">
+ <button class="btn btn-secondary" @click="loadLog(true)">Load more</button>
+ </div>
+ </div>
+</template>
+
+<script>
+ export default {
+ props: {
+ },
+ data() {
+ return {
+ payments: [],
+ hasMore: false,
+ page: 1
+ }
+ },
+ mounted() {
+ this.loadLog()
+ },
+ methods: {
+ loadLog(more) {
+ let loader = $(this.$el)
+ let param = ''
+
+ if (more) {
+ param = '?page=' + (this.page + 1)
+ loader = $('#payments-loader')
+ }
+
+ this.$root.addLoader(loader)
+ axios.get('/api/v4/payments/pending' + param)
+ .then(response => {
+ this.$root.removeLoader(loader)
+ // Note: In Vue we can't just use .concat()
+ for (let i in response.data.list) {
+ this.$set(this.payments, this.payments.length, response.data.list[i])
+ }
+ this.hasMore = response.data.hasMore
+ this.page = response.data.page || 1
+ })
+ .catch(error => {
+ this.$root.removeLoader(loader)
+ })
+ },
+ amount(payment) {
+ return this.$root.price(payment.amount)
+ }
+
+
+ }
+ }
+</script>
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -76,10 +76,13 @@
Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload');
Route::post('payments', 'API\V4\PaymentsController@store');
+ Route::delete('payments', 'API\V4\PaymentsController@cancel');
Route::get('payments/mandate', 'API\V4\PaymentsController@mandate');
Route::post('payments/mandate', 'API\V4\PaymentsController@mandateCreate');
Route::put('payments/mandate', 'API\V4\PaymentsController@mandateUpdate');
Route::delete('payments/mandate', 'API\V4\PaymentsController@mandateDelete');
+ Route::get('payments/methods', 'API\V4\PaymentsController@paymentMethods');
+ Route::get('payments/pending', 'API\V4\PaymentsController@payments');
Route::get('openvidu/rooms', 'API\V4\OpenViduController@index');
Route::post('openvidu/rooms/{id}/close', 'API\V4\OpenViduController@closeRoom');

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 30, 7:44 AM (3 d, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18797478
Default Alt Text
D2239.1774856688.diff (38 KB)

Event Timeline