web/satellite: token payments logic (#3581)
This commit is contained in:
parent
1aa2bc0a83
commit
8234e24d13
@ -2,7 +2,7 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
|
||||
import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge } from '@/types/payments';
|
||||
import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge, TokenDeposit } from '@/types/payments';
|
||||
import { HttpClient } from '@/utils/httpClient';
|
||||
|
||||
/**
|
||||
@ -194,6 +194,7 @@ export class PaymentsHttpApi implements PaymentsApi {
|
||||
item.id,
|
||||
item.description,
|
||||
item.amount,
|
||||
item.tokenReceived,
|
||||
item.status,
|
||||
item.link,
|
||||
new Date(item.start),
|
||||
@ -204,4 +205,26 @@ export class PaymentsHttpApi implements PaymentsApi {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* makeTokenDeposit process coin payments
|
||||
* @param amount
|
||||
* @throws Error
|
||||
*/
|
||||
public async makeTokenDeposit(amount: number): Promise<TokenDeposit> {
|
||||
const path = `${this.ROOT_PATH}/tokens/deposit`;
|
||||
const response = await this.client.post(path, JSON.stringify({ amount }));
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new ErrorUnauthorized();
|
||||
}
|
||||
|
||||
throw new Error('can not process coin payment');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
return new TokenDeposit(result.amount, result.address);
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import AccountBalance from '@/components/account/billing/balance/AccountBalance.vue';
|
||||
import DepositAndBilling from '@/components/account/billing/depositAndBilling/DepositAndBilling.vue';
|
||||
import DepositAndBilling from '@/components/account/billing/billingHistory/DepositAndBilling.vue';
|
||||
import MonthlyBillingSummary from '@/components/account/billing/monthlySummary/MonthlyBillingSummary.vue';
|
||||
import PaymentMethods from '@/components/account/billing/paymentMethods/PaymentMethods.vue';
|
||||
|
||||
|
@ -31,8 +31,8 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import BillingItem from '@/components/account/billing/depositAndBilling/BillingItem.vue';
|
||||
import SortingHeader from '@/components/account/billing/depositAndBilling/SortingHeader.vue';
|
||||
import BillingItem from '@/components/account/billing/billingHistory/BillingItem.vue';
|
||||
import SortingHeader from '@/components/account/billing/billingHistory/SortingHeader.vue';
|
||||
import VPagination from '@/components/common/VPagination.vue';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
@ -0,0 +1,96 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="countdown-container">
|
||||
<div v-if="isExpired">{{date}}</div>
|
||||
<div class="row" v-else>
|
||||
<p>Expires in </p>
|
||||
<p class="digit margin">{{ hours | leadingZero }}</p>
|
||||
<p>:</p>
|
||||
<p class="digit">{{ minutes | leadingZero }}</p>
|
||||
<p>:</p>
|
||||
<p class="digit">{{ seconds | leadingZero }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { BillingHistoryItemType } from '@/types/payments';
|
||||
|
||||
@Component
|
||||
export default class BillingHistoryDate extends Vue {
|
||||
@Prop({default: () => new Date()})
|
||||
private readonly expiration: Date;
|
||||
@Prop({default: () => new Date()})
|
||||
private readonly start: Date;
|
||||
@Prop({default: 0})
|
||||
private readonly type: BillingHistoryItemType;
|
||||
|
||||
private readonly expirationTimeInSeconds: number;
|
||||
private nowInSeconds = Math.trunc(new Date().getTime() / 1000);
|
||||
private intervalID: number;
|
||||
|
||||
public isExpired: boolean;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.expirationTimeInSeconds = Math.trunc(new Date(this.expiration).getTime() / 1000);
|
||||
this.isExpired = (this.expirationTimeInSeconds - this.nowInSeconds) < 0;
|
||||
|
||||
this.ready();
|
||||
}
|
||||
|
||||
public get date(): string {
|
||||
if (this.type === BillingHistoryItemType.Transaction) {
|
||||
return this.start.toLocaleDateString();
|
||||
}
|
||||
|
||||
return `${this.start.toLocaleDateString()} - ${this.expiration.toLocaleDateString()}`;
|
||||
}
|
||||
|
||||
public get seconds(): number {
|
||||
return (this.expirationTimeInSeconds - this.nowInSeconds) % 60;
|
||||
}
|
||||
|
||||
public get minutes(): number {
|
||||
return Math.trunc((this.expirationTimeInSeconds - this.nowInSeconds) / 60) % 60;
|
||||
}
|
||||
|
||||
public get hours(): number {
|
||||
return Math.trunc((this.expirationTimeInSeconds - this.nowInSeconds) / 3600) % 24;
|
||||
}
|
||||
|
||||
private ready(): void {
|
||||
this.intervalID = window.setInterval(() => {
|
||||
if ((this.expirationTimeInSeconds - this.nowInSeconds) < 0) {
|
||||
this.isExpired = true;
|
||||
clearInterval(this.intervalID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.nowInSeconds = Math.trunc(new Date().getTime() / 1000);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.digit {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<BillingHistoryItemDate
|
||||
class="container__item"
|
||||
:start="billingItem.start"
|
||||
:expiration="billingItem.end"
|
||||
:type="billingItem.type"
|
||||
/>
|
||||
<p class="container__item description">{{billingItem.description}}</p>
|
||||
<p class="container__item status">{{billingItem.formattedStatus}}</p>
|
||||
<p class="container__item amount">
|
||||
<b>
|
||||
{{billingItem.quantity.currency}}
|
||||
<span v-if="billingItem.type === 1">
|
||||
{{billingItem.quantity.received}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{billingItem.quantity.total}}
|
||||
</span>
|
||||
</b>
|
||||
<span v-if="billingItem.type === 1">
|
||||
of {{billingItem.quantity.total}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="container__item download" v-html="billingItem.downloadLinkHtml()"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import BillingHistoryItemDate from '@/components/account/billing/billingHistory/BillingHistoryItemDate.vue';
|
||||
|
||||
import { BillingHistoryItem } from '@/types/payments';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
BillingHistoryItemDate,
|
||||
},
|
||||
})
|
||||
export default class BillingItem extends Vue {
|
||||
@Prop({default: () => new BillingHistoryItem()})
|
||||
private readonly billingItem: BillingHistoryItem;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.download-link {
|
||||
color: #2683ff;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
|
||||
&:hover {
|
||||
color: #0059d0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 0 30px;
|
||||
align-items: center;
|
||||
width: calc(100% - 60px);
|
||||
border-top: 1px solid rgba(169, 181, 193, 0.3);
|
||||
|
||||
&__item {
|
||||
width: 20%;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
color: #61666b;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 31%;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.amount {
|
||||
width: 27%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.download {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
min-width: 142px;
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 175px;
|
||||
}
|
||||
</style>
|
@ -19,8 +19,8 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import BillingItem from '@/components/account/billing/depositAndBilling/BillingItem.vue';
|
||||
import SortingHeader from '@/components/account/billing/depositAndBilling/SortingHeader.vue';
|
||||
import BillingItem from '@/components/account/billing/billingHistory/BillingItem.vue';
|
||||
import SortingHeader from '@/components/account/billing/billingHistory/SortingHeader.vue';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { BillingHistoryItem } from '@/types/payments';
|
@ -3,10 +3,10 @@
|
||||
|
||||
<template>
|
||||
<div class="sort-header-container">
|
||||
<div class="sort-header-container__item">
|
||||
<div class="sort-header-container__item date">
|
||||
<p class="sort-header-container__item__name">Date</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item">
|
||||
<div class="sort-header-container__item description">
|
||||
<p class="sort-header-container__item__name">Description</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item status">
|
||||
@ -35,8 +35,7 @@ export default class SortingHeader extends Vue {}
|
||||
|
||||
&__item {
|
||||
text-align: left;
|
||||
width: 35%;
|
||||
margin-right: 10px;
|
||||
width: 20%;
|
||||
|
||||
&__name {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
@ -47,17 +46,22 @@ export default class SortingHeader extends Vue {}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 31%;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 15%;
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.amount {
|
||||
width: 15%;
|
||||
width: 27%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.download {
|
||||
margin: 0;
|
||||
min-width: 130px;
|
||||
width: 10%;
|
||||
}
|
||||
</style>
|
@ -1,67 +0,0 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<p class="container__item">{{billingItem.date()}}</p>
|
||||
<p class="container__item">{{billingItem.description}}</p>
|
||||
<p class="container__item status">{{billingItem.status}}</p>
|
||||
<p class="container__item amount"><b>{{billingItem.amountDollars()}}</b></p>
|
||||
<p class="container__item download" v-html="billingItem.downloadLinkHtml()"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { BillingHistoryItem } from '@/types/payments';
|
||||
|
||||
@Component
|
||||
export default class BillingItem extends Vue {
|
||||
@Prop({default: new BillingHistoryItem()})
|
||||
private readonly billingItem: BillingHistoryItem;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.download-link {
|
||||
color: #2683ff;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
|
||||
&:hover {
|
||||
color: #0059d0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
padding: 0 30px;
|
||||
align-items: center;
|
||||
width: calc(100% - 60px);
|
||||
border-top: 1px solid rgba(169, 181, 193, 0.3);
|
||||
|
||||
&__item {
|
||||
width: 35%;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
margin-right: 10px;
|
||||
color: #61666b;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.amount {
|
||||
width: 15%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.download {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
min-width: 142px;
|
||||
}
|
||||
</style>
|
@ -30,7 +30,7 @@
|
||||
<div class="payment-methods-area__adding-container storj" v-if="isAddingStorjState">
|
||||
<div class="storj-container">
|
||||
<p class="storj-container__label">Deposit STORJ Tokens via Coin Payments</p>
|
||||
<StorjInput class="form"/>
|
||||
<TokenDepositSelection class="form" @onChangeTokenValue="onChangeTokenValue"/>
|
||||
</div>
|
||||
<VButton
|
||||
label="Continue to Coin Payments"
|
||||
@ -41,9 +41,9 @@
|
||||
</div>
|
||||
<div class="payment-methods-area__adding-container card" v-if="isAddingCardState">
|
||||
<p class="payment-methods-area__adding-container__label">Add Credit or Debit Card</p>
|
||||
<StripeInput
|
||||
<StripeCardInput
|
||||
class="payment-methods-area__adding-container__stripe"
|
||||
ref="stripeInput"
|
||||
ref="stripeCardInput"
|
||||
:on-stripe-response-callback="addCard"
|
||||
/>
|
||||
<VButton
|
||||
@ -67,8 +67,8 @@
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import CardComponent from '@/components/account/billing/paymentMethods/CardComponent.vue';
|
||||
import StorjInput from '@/components/account/billing/paymentMethods/StorjInput.vue';
|
||||
import StripeInput from '@/components/account/billing/paymentMethods/StripeInput.vue';
|
||||
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
|
||||
import TokenDepositSelection from '@/components/account/billing/paymentMethods/TokenDepositSelection.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
@ -79,6 +79,8 @@ import { PaymentMethodsBlockState } from '@/utils/constants/billingEnums';
|
||||
const {
|
||||
ADD_CREDIT_CARD,
|
||||
GET_CREDIT_CARDS,
|
||||
MAKE_TOKEN_DEPOSIT,
|
||||
GET_BILLING_HISTORY,
|
||||
} = PAYMENTS_ACTIONS;
|
||||
|
||||
interface StripeForm {
|
||||
@ -89,13 +91,16 @@ interface StripeForm {
|
||||
components: {
|
||||
VButton,
|
||||
CardComponent,
|
||||
StorjInput,
|
||||
StripeInput,
|
||||
TokenDepositSelection,
|
||||
StripeCardInput,
|
||||
},
|
||||
})
|
||||
export default class PaymentMethods extends Vue {
|
||||
private areaState: number = PaymentMethodsBlockState.DEFAULT;
|
||||
private isLoading: boolean = false;
|
||||
private readonly DEFAULT_TOKEN_DEPOSIT_VALUE = 20;
|
||||
private readonly MAX_TOKEN_AMOUNT_IN_DOLLARS = 1000000;
|
||||
private tokenDepositValue: number = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
|
||||
|
||||
public mounted() {
|
||||
try {
|
||||
@ -106,7 +111,7 @@ export default class PaymentMethods extends Vue {
|
||||
}
|
||||
|
||||
public $refs!: {
|
||||
stripeInput: StripeInput & StripeForm;
|
||||
stripeCardInput: StripeCardInput & StripeForm;
|
||||
};
|
||||
|
||||
public get creditCards(): CreditCard[] {
|
||||
@ -123,6 +128,10 @@ export default class PaymentMethods extends Vue {
|
||||
return this.areaState === PaymentMethodsBlockState.ADDING_CARD;
|
||||
}
|
||||
|
||||
public onChangeTokenValue(value: number): void {
|
||||
this.tokenDepositValue = value;
|
||||
}
|
||||
|
||||
public onAddSTORJ(): void {
|
||||
this.areaState = PaymentMethodsBlockState.ADDING_STORJ;
|
||||
|
||||
@ -135,16 +144,43 @@ export default class PaymentMethods extends Vue {
|
||||
}
|
||||
public onCancel(): void {
|
||||
this.areaState = PaymentMethodsBlockState.DEFAULT;
|
||||
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public onConfirmAddSTORJ(): void {
|
||||
/**
|
||||
* onConfirmAddSTORJ checks if amount is valid and if so process token
|
||||
* payment and return state to default
|
||||
*/
|
||||
public async onConfirmAddSTORJ(): Promise<void> {
|
||||
if (this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT_IN_DOLLARS || this.tokenDepositValue === 0) {
|
||||
await this.$notify.error('Deposit amount must be more than 0 and less then 1000000');
|
||||
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
|
||||
this.areaState = PaymentMethodsBlockState.DEFAULT;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenResponse = await this.$store.dispatch(MAKE_TOKEN_DEPOSIT, this.tokenDepositValue * 100);
|
||||
await this.$notify.success(`Successfully created new deposit transaction! \nAddress:${tokenResponse.address} \nAmount:${tokenResponse.amount}`);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
|
||||
try {
|
||||
await this.$store.dispatch(GET_BILLING_HISTORY);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
this.areaState = PaymentMethodsBlockState.DEFAULT;
|
||||
}
|
||||
|
||||
public async onConfirmAddStripe(): Promise<void> {
|
||||
await this.$refs.stripeInput.onSubmit();
|
||||
await this.$refs.stripeCardInput.onSubmit();
|
||||
}
|
||||
|
||||
public async addCard(token: string) {
|
||||
|
@ -17,9 +17,9 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
|
||||
// StripeInput encapsulates Stripe add card addition logic
|
||||
// StripeCardInput encapsulates Stripe add card addition logic
|
||||
@Component
|
||||
export default class StripeInput extends Vue {
|
||||
export default class StripeCardInput extends Vue {
|
||||
@Prop({default: () => console.error('onStripeResponse is not reinitialized')})
|
||||
private readonly onStripeResponseCallback: (result: any) => void;
|
||||
|
@ -17,8 +17,8 @@
|
||||
class="options-container__item"
|
||||
v-for="option in paymentOptions"
|
||||
:key="option.label"
|
||||
@click.prevent="select(option)">
|
||||
|
||||
@click.prevent.stop="select(option)"
|
||||
>
|
||||
<div class="options-container__item__svg" v-if="option.value === current.value">
|
||||
<svg width="15" height="13" viewBox="0 0 15 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.0928 3.02746C14.6603 2.4239 14.631 1.4746 14.0275 0.907152C13.4239 0.339699 12.4746 0.368972 11.9072 0.972536L14.0928 3.02746ZM4.53846 11L3.44613 12.028C3.72968 12.3293 4.12509 12.5001 4.53884 12.5C4.95258 12.4999 5.34791 12.3289 5.63131 12.0275L4.53846 11ZM3.09234 7.27469C2.52458 6.67141 1.57527 6.64261 0.971991 7.21036C0.36871 7.77812 0.339911 8.72743 0.907664 9.33071L3.09234 7.27469ZM11.9072 0.972536L3.44561 9.97254L5.63131 12.0275L14.0928 3.02746L11.9072 0.972536ZM5.6308 9.97199L3.09234 7.27469L0.907664 9.33071L3.44613 12.028L5.6308 9.97199Z" fill="#2683FF"/>
|
||||
@ -30,7 +30,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<label class="label" v-if="isCustomAmount">
|
||||
<input class="custom-input" type="number" placeholder="Enter Amount" v-model="customAmount">
|
||||
<input
|
||||
v-number
|
||||
class="custom-input"
|
||||
placeholder="Enter Amount"
|
||||
v-model="customAmount"
|
||||
@input="onCustomAmountChange"
|
||||
>
|
||||
<div class="input-svg" @click="toggleCustomAmount">
|
||||
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#2683FF"/>
|
||||
@ -47,33 +53,64 @@ import { PaymentAmountOption } from '@/types/payments';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
|
||||
@Component
|
||||
export default class StorjInput extends Vue {
|
||||
export default class TokenDepositSelection extends Vue {
|
||||
/**
|
||||
* Set of default payment options
|
||||
*/
|
||||
public paymentOptions: PaymentAmountOption[] = [
|
||||
new PaymentAmountOption(20, `US $20 (+5 Bonus)`),
|
||||
new PaymentAmountOption(5, `US $5`),
|
||||
new PaymentAmountOption(10, `US $10 (+2 Bonus)`),
|
||||
new PaymentAmountOption(100, `US $100 (+20 Bonus)`),
|
||||
new PaymentAmountOption(1000, `US $1000 (+200 Bonus)`),
|
||||
new PaymentAmountOption(20, `USD $20`),
|
||||
new PaymentAmountOption(5, `USD $5`),
|
||||
new PaymentAmountOption(10, `USD $10`),
|
||||
new PaymentAmountOption(100, `USD $100`),
|
||||
new PaymentAmountOption(1000, `USD $1000`),
|
||||
];
|
||||
|
||||
public current: PaymentAmountOption = new PaymentAmountOption(20, `US $20 (+$5 Bonus)`);
|
||||
public customAmount: number = 0;
|
||||
public current: PaymentAmountOption = this.paymentOptions[0];
|
||||
public customAmount: string = '';
|
||||
public isCustomAmount = false;
|
||||
|
||||
/**
|
||||
* isSelectionShown flag that indicate is token amount selection shown
|
||||
*/
|
||||
public get isSelectionShown(): boolean {
|
||||
return this.$store.state.appStateModule.appState.isPaymentSelectionShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggleSelection toggles token amount selection
|
||||
*/
|
||||
public toggleSelection(): void {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_PAYMENT_SELECTION);
|
||||
}
|
||||
|
||||
public toggleCustomAmount(): void {
|
||||
this.isCustomAmount = !this.isCustomAmount;
|
||||
/**
|
||||
* onCustomAmountChange input event handle that emits value to parent component
|
||||
*/
|
||||
public onCustomAmountChange(): void {
|
||||
this.$emit('onChangeTokenValue', parseInt(this.customAmount, 10));
|
||||
}
|
||||
|
||||
public select(value: PaymentAmountOption): void {
|
||||
this.current = value;
|
||||
/**
|
||||
* toggleCustomAmount toggles custom amount input and changes token value in parent
|
||||
*/
|
||||
public toggleCustomAmount(): void {
|
||||
this.isCustomAmount = !this.isCustomAmount;
|
||||
|
||||
if (this.isCustomAmount) {
|
||||
this.$emit('onChangeTokenValue', 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('onChangeTokenValue', this.paymentOptions[0].value);
|
||||
}
|
||||
|
||||
/**
|
||||
* select standard value from list and emits it value to parent component
|
||||
*/
|
||||
public select(option: PaymentAmountOption): void {
|
||||
this.current = option;
|
||||
this.$emit('onChangeTokenValue', option.value);
|
||||
this.toggleSelection();
|
||||
}
|
||||
}
|
||||
@ -91,6 +128,7 @@ export default class StorjInput extends Vue {
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
color: #354049;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.custom-input::-webkit-inner-spin-button,
|
@ -39,6 +39,34 @@ Vue.directive('click-outside', {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* number directive allow user to type only numbers in input
|
||||
*/
|
||||
Vue.directive('number', {
|
||||
bind (el: HTMLElement) {
|
||||
el.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
const keyCode = parseInt(e.key);
|
||||
|
||||
if (!isNaN(keyCode) || e.key === 'Delete' || e.key === 'Backspace') {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* leadingZero adds zero to the start of single digit number
|
||||
*/
|
||||
Vue.filter('leadingZero', function (value: number): string {
|
||||
if (value <= 9) {
|
||||
return `0${value}`;
|
||||
}
|
||||
|
||||
return `${value}`;
|
||||
});
|
||||
|
||||
/**
|
||||
* centsToDollars is a Vue filter that converts amount of cents in dollars string.
|
||||
*/
|
||||
|
@ -6,7 +6,7 @@ import Router, { RouteRecord } from 'vue-router';
|
||||
|
||||
import AccountArea from '@/components/account/AccountArea.vue';
|
||||
import AccountBilling from '@/components/account/billing/BillingArea.vue';
|
||||
import BillingHistory from '@/components/account/billing/depositAndBilling/BillingHistory.vue';
|
||||
import BillingHistory from '@/components/account/billing/billingHistory/BillingHistory.vue';
|
||||
import ProfileArea from '@/components/account/ProfileArea.vue';
|
||||
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
|
||||
import BucketArea from '@/components/buckets/BucketArea.vue';
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { StoreModule } from '@/store';
|
||||
import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge } from '@/types/payments';
|
||||
import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge, TokenDeposit } from '@/types/payments';
|
||||
|
||||
const PAYMENTS_MUTATIONS = {
|
||||
SET_BALANCE: 'SET_BALANCE',
|
||||
@ -25,6 +25,7 @@ export const PAYMENTS_ACTIONS = {
|
||||
MAKE_CARD_DEFAULT: 'makeCardDefault',
|
||||
REMOVE_CARD: 'removeCard',
|
||||
GET_BILLING_HISTORY: 'getBillingHistory',
|
||||
MAKE_TOKEN_DEPOSIT: 'makeTokenDeposit',
|
||||
GET_PROJECT_CHARGES: 'getProjectCharges',
|
||||
};
|
||||
|
||||
@ -49,6 +50,7 @@ const {
|
||||
MAKE_CARD_DEFAULT,
|
||||
REMOVE_CARD,
|
||||
GET_BILLING_HISTORY,
|
||||
MAKE_TOKEN_DEPOSIT,
|
||||
GET_PROJECT_CHARGES,
|
||||
} = PAYMENTS_ACTIONS;
|
||||
|
||||
@ -157,6 +159,9 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState>
|
||||
|
||||
commit(SET_BILLING_HISTORY, billingHistory);
|
||||
},
|
||||
[MAKE_TOKEN_DEPOSIT]: async function({commit}: any, amount: number): Promise<TokenDeposit> {
|
||||
return await api.makeTokenDeposit(amount);
|
||||
},
|
||||
[GET_PROJECT_CHARGES]: async function({commit}: any): Promise<void> {
|
||||
const charges: ProjectCharge[] = await api.projectsCharges();
|
||||
|
||||
|
@ -61,6 +61,14 @@ export interface PaymentsApi {
|
||||
* @throws Error
|
||||
*/
|
||||
billingHistory(): Promise<BillingHistoryItem[]>;
|
||||
|
||||
/**
|
||||
* Creates token transaction in CoinPayments
|
||||
*
|
||||
* @param amount
|
||||
* @throws Error
|
||||
*/
|
||||
makeTokenDeposit(amount: number): Promise<TokenDeposit>;
|
||||
}
|
||||
|
||||
export class CreditCard {
|
||||
@ -89,29 +97,38 @@ export class BillingHistoryItem {
|
||||
public readonly id: string = '',
|
||||
public readonly description: string = '',
|
||||
public readonly amount: number = 0,
|
||||
public readonly received: number = 0,
|
||||
public readonly status: string = '',
|
||||
public readonly link: string = '',
|
||||
public readonly start: Date = new Date(),
|
||||
public readonly end: Date = new Date(),
|
||||
public readonly type: BillingHistoryItemType = 0,
|
||||
public readonly type: BillingHistoryItemType = BillingHistoryItemType.Invoice,
|
||||
) {}
|
||||
|
||||
public date(): string {
|
||||
if (this.type) {
|
||||
return this.start.toLocaleDateString();
|
||||
public get quantity(): Amount {
|
||||
if (this.type === BillingHistoryItemType.Invoice) {
|
||||
return new Amount('$', this.amountDollars(this.amount));
|
||||
}
|
||||
|
||||
return `${this.start.toLocaleDateString()} - ${this.end.toLocaleDateString()}`;
|
||||
return new Amount('$', this.amountDollars(this.amount), this.amountDollars(this.received));
|
||||
}
|
||||
|
||||
public amountDollars(): string {
|
||||
return `$${this.amount / 100}`;
|
||||
public get formattedStatus(): string {
|
||||
return this.status.charAt(0).toUpperCase() + this.status.substring(1);
|
||||
}
|
||||
|
||||
private amountDollars(amount): number {
|
||||
return amount / 100;
|
||||
}
|
||||
|
||||
public downloadLinkHtml(): string {
|
||||
const downloadLabel = this.type === 1 ? 'EtherScan' : 'PDF';
|
||||
if (!this.link) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `<a class="download-link" href="${this.link}">${downloadLabel}</a>`;
|
||||
const downloadLabel = this.type === BillingHistoryItemType.Transaction ? 'Checkout' : 'PDF';
|
||||
|
||||
return `<a class="download-link" target="_blank" href="${this.link}">${downloadLabel}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,6 +140,20 @@ export enum BillingHistoryItemType {
|
||||
Transaction = 1,
|
||||
}
|
||||
|
||||
// TokenDeposit holds public information about token deposit
|
||||
export class TokenDeposit {
|
||||
constructor(public amount: number, public address: string) {}
|
||||
}
|
||||
|
||||
// Amount holds information for displaying billing item payment
|
||||
class Amount {
|
||||
public constructor(
|
||||
public currency: string = '',
|
||||
public total: number = 0,
|
||||
public received: number = 0,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* ProjectCharge shows how much money current project will charge in the end of the month.
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
// TODO: move functions to Validator class
|
||||
export function validateEmail(email: string): boolean {
|
||||
const rgx = /.*@.*\..*$/;
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {
|
||||
validateEmail,
|
||||
validatePassword,
|
||||
} from '@/utils/validation';
|
||||
} from '@/utils/validation';
|
||||
|
||||
describe('validation', () => {
|
||||
it('validatePassword regex works correctly', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user