diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ad1b8261c1..42b252d0bd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,6 +39,7 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co import { DeleteAccountComponent } from './settings/delete-account.component'; import { DomainRulesComponent } from './settings/domain-rules.component'; import { OptionsComponent } from './settings/options.component'; +import { PremiumComponent } from './settings/premium.component'; import { ProfileComponent } from './settings/profile.component'; import { PurgeVaultComponent } from './settings/purge-vault.component'; import { SettingsComponent } from './settings/settings.component'; @@ -143,6 +144,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; OrganizationLayoutComponent, PasswordGeneratorComponent, PasswordGeneratorHistoryComponent, + PremiumComponent, ProfileComponent, PurgeVaultComponent, RegisterComponent, diff --git a/src/app/settings/premium.component.html b/src/app/settings/premium.component.html new file mode 100644 index 0000000000..2492042ce2 --- /dev/null +++ b/src/app/settings/premium.component.html @@ -0,0 +1,333 @@ +
+

{{'addons' | i18n}}

+
+ + + {{'additionalStorageDesc' | i18n : (storageGbPrice | currency:'USD')}} +
+

{{'summary' | i18n}}

+ {{'premiumMembership' | i18n}}: {{premiumPrice | currency:'USD':'$'}} +
{{'additionalStorageGb' | i18n}}: {{additionalStorage || 0}} GB × {{storageGbPrice | currency:'USD'}} = {{additionalStorageTotal + | currency:'USD':'$'}} +
+ {{'total' | i18n}}: USD {{total | currency:'USD'}} /{{'year' | i18n}} +
+ {{'cardChargedAnnually' | i18n}} +

{{'paymentInformation' | i18n}}

+
+
+ + +
+
+ + +
+
+ +
+
+ + + {{'creditCardAllBrandsAccepted' | i18n}} +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ {{'paypalClickSubmit' | i18n}} +
+
+ +
diff --git a/src/app/settings/premium.component.ts b/src/app/settings/premium.component.ts new file mode 100644 index 0000000000..b41bafbe2a --- /dev/null +++ b/src/app/settings/premium.component.ts @@ -0,0 +1,150 @@ +import { + Component, + OnInit, +} from '@angular/core'; + +import { I18nService } from 'jslib/abstractions/i18n.service'; + +@Component({ + selector: 'app-premium', + templateUrl: 'premium.component.html', +}) +export class PremiumComponent implements OnInit { + premiumPrice = 10; + storageGbPrice = 4; + additionalStorage = 0; + method = 'card'; + card: any = { + number: null, + exp_month: null, + exp_year: null, + address_country: '', + address_zip: null, + }; + formPromise: Promise; + cardExpMonthOptions: any[]; + cardExpYearOptions: any[]; + + private stripeScript: HTMLScriptElement; + private btScript: HTMLScriptElement; + private btInstance: any = null; + + constructor(private i18nService: I18nService) { + this.stripeScript = window.document.createElement('script'); + this.stripeScript.src = 'https://js.stripe.com/v2/'; + this.stripeScript.async = true; + this.stripeScript.onload = () => { + (window as any).Stripe.setPublishableKey('pk_test_KPoCfZXu7mznb9uSCPZ2JpTD'); + }; + this.btScript = window.document.createElement('script'); + this.btScript.src = 'https://js.braintreegateway.com/web/dropin/1.4.0/js/dropin.min.js'; + this.btScript.async = true; + + this.cardExpMonthOptions = [ + { name: '-- ' + i18nService.t('select') + ' --', value: null }, + { name: '01 - ' + i18nService.t('january'), value: '01' }, + { name: '02 - ' + i18nService.t('february'), value: '02' }, + { name: '03 - ' + i18nService.t('march'), value: '03' }, + { name: '04 - ' + i18nService.t('april'), value: '04' }, + { name: '05 - ' + i18nService.t('may'), value: '05' }, + { name: '06 - ' + i18nService.t('june'), value: '06' }, + { name: '07 - ' + i18nService.t('july'), value: '07' }, + { name: '08 - ' + i18nService.t('august'), value: '08' }, + { name: '09 - ' + i18nService.t('september'), value: '09' }, + { name: '10 - ' + i18nService.t('october'), value: '10' }, + { name: '11 - ' + i18nService.t('november'), value: '11' }, + { name: '12 - ' + i18nService.t('december'), value: '12' }, + ]; + + this.cardExpYearOptions = [ + { name: '-- ' + i18nService.t('select') + ' --', value: null }, + ]; + const year = (new Date()).getFullYear(); + for (let i = year; i < (year + 10); i++) { + this.cardExpYearOptions.push({ name: i.toString(), value: i.toString().slice(-2) }); + } + } + + ngOnInit() { + window.document.body.appendChild(this.stripeScript); + window.document.body.appendChild(this.btScript); + } + + ngOnDestroy() { + window.document.body.removeChild(this.stripeScript); + window.document.body.removeChild(this.btScript); + Array.from(window.document.querySelectorAll('iframe')).forEach((el) => { + if (el.src != null && el.src.indexOf('stripe') > -1) { + window.document.body.removeChild(el); + } + }); + } + + async submit() { + try { + const token = await this.createPaymentToken(); + } catch (e) { + + } + } + + changeMethod() { + if (this.method !== 'paypal') { + this.btInstance = null; + return; + } + + window.setTimeout(() => { + (window as any).braintree.dropin.create({ + authorization: 'sandbox_r72q8jq6_9pnxkwm75f87sdc2', + container: '#bt-dropin-container', + paymentOptionPriority: ['paypal'], + paypal: { + flow: 'vault', + buttonStyle: { + label: 'pay', + size: 'medium', + shape: 'pill', + color: 'blue', + }, + }, + }, (createErr: any, instance: any) => { + if (createErr != null) { + console.error(createErr); + return; + } + this.btInstance = instance; + }); + }, 250); + } + + get additionalStorageTotal(): number { + return this.storageGbPrice * this.additionalStorage; + } + + get total(): number { + return this.additionalStorageTotal + this.premiumPrice; + } + + private createPaymentToken() { + return new Promise((resolve, reject) => { + if (this.method === 'paypal') { + this.btInstance.requestPaymentMethod().then((payload: any) => { + resolve(payload.nonce); + }).catch((err: any) => { + reject(err.message); + }); + } else { + (window as any).Stripe.card.createToken(this.card, (status: number, response: any) => { + if (status === 200 && response.id != null) { + resolve(response.id); + } else if (response.error != null) { + reject(response.error.message); + } else { + reject(); + } + }); + } + }); + } +} diff --git a/src/app/settings/user-billing.component.html b/src/app/settings/user-billing.component.html index c4f6b837cf..4e280ab941 100644 --- a/src/app/settings/user-billing.component.html +++ b/src/app/settings/user-billing.component.html @@ -1,3 +1,4 @@ + diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 8e2f9dabf7..52e547ec62 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -979,6 +979,9 @@ "premium": { "message": "Premium" }, + "premiumMembership": { + "message": "Premium Membership" + }, "manage": { "message": "Manage" }, @@ -1191,5 +1194,53 @@ }, "billingAndLicensing": { "message": "Billing & Licensing" + }, + "addons": { + "message": "Addons" + }, + "additionalStorageGb": { + "message": "Additional Storage (GB)" + }, + "additionalStorageGbDesc": { + "message": "# of additional GB" + }, + "additionalStorageDesc": { + "message": "Your plan comes with 1 GB of encrypted file storage. You can add additional storage for $PRICE$ per GB /year.", + "placeholders": { + "price": { + "content": "$1", + "example": "$4.00" + } + } + }, + "summary": { + "message": "Summary" + }, + "total": { + "message": "Total" + }, + "year": { + "message": "year" + }, + "month": { + "message": "month" + }, + "cardChargedAnnually": { + "message": "Your card will be charged immediately and on a recurring basis each year. You may cancel at any time." + }, + "cardChargedMonthly": { + "message": "Your card will be charged immediately and on a recurring basis each month. You may cancel at any time." + }, + "paymentInformation": { + "message": "Payment Information" + }, + "creditCard": { + "message": "Credit Card" + }, + "creditCardAllBrandsAccepted": { + "message": "All major credit card brands are accepted." + }, + "paypalClickSubmit": { + "message": "Click the PayPal button to log into your PayPal account, then click the Submit button below to continue." } } diff --git a/src/scss/styles.scss b/src/scss/styles.scss index ab28e121f9..ece25e1e38 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -401,6 +401,24 @@ app-breach-report { } } +.braintree-placeholder, .braintree-sheet__header { + display: none; +} + +.braintree-sheet__content--button { + text-align: left; + padding: 0; + min-height: 0; +} + +.braintree-sheet__container { + margin-bottom: 0; +} + +.braintree-sheet { + border: none; +} + .totp { .totp-code { @extend .text-monospace;