mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
Implemented tax collection for subscriptions (#723)
* Implemented tax collection for subscriptions * Cleanup for Sales Tax * Code review fixes for Tax Rate implementation * Code review fixes for Tax Rate implementation
This commit is contained in:
@@ -206,16 +206,22 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
<div class="text-lg">
|
<h2 class="spaced-header mb-4">{{ (createOrganization ? 'paymentInformation' : 'billingInformation') | i18n}}</h2>
|
||||||
<strong>{{'total' | i18n}}:</strong> {{subtotal | currency:'USD $'}} /{{selectedPlanInterval | i18n}}
|
<app-payment *ngIf="createOrganization" [hideCredit]="true"></app-payment>
|
||||||
|
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
|
||||||
|
<div id="price" class="my-4">
|
||||||
|
<div class="text-muted text-sm">
|
||||||
|
{{ 'planPrice' | i18n }}: {{ subtotal | currency: 'USD $' }}
|
||||||
|
<br />
|
||||||
|
<ng-container>
|
||||||
|
{{ 'estimatedTax' | i18n }}: {{ taxCharges | currency: 'USD $' }}
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<hr class="my-1 col-3 ml-0">
|
||||||
|
<p class="text-lg"><strong>{{'total' | i18n}}:</strong>
|
||||||
|
{{total | currency:'USD $'}}/{{selectedPlanInterval | i18n}}</p>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="createOrganization">
|
<small class="text-muted font-italic">{{'paymentChargedWithTrial' | i18n : (selectedPlanInterval | i18n) }}</small>
|
||||||
<small
|
|
||||||
class="text-muted font-italic">{{'paymentChargedWithTrial' | i18n : (selectedPlanInterval | i18n) }}</small>
|
|
||||||
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
|
|
||||||
<app-payment [hideCredit]="true"></app-payment>
|
|
||||||
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!createOrganization">
|
<ng-container *ngIf="!createOrganization">
|
||||||
<app-payment [showMethods]="false"></app-payment>
|
<app-payment [showMethods]="false"></app-payment>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -159,6 +159,16 @@ export class OrganizationPlansComponent implements OnInit {
|
|||||||
return subTotal;
|
return subTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get taxCharges() {
|
||||||
|
return this.taxComponent != null && this.taxComponent.taxRate != null ?
|
||||||
|
(this.taxComponent.taxRate / 100) * this.subtotal :
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get total() {
|
||||||
|
return (this.subtotal + this.taxCharges) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
changedProduct() {
|
changedProduct() {
|
||||||
this.plan = this.selectablePlans[0].type;
|
this.plan = this.selectablePlans[0].type;
|
||||||
if (!this.selectedPlan.hasPremiumAccessOption) {
|
if (!this.selectedPlan.hasPremiumAccessOption) {
|
||||||
@@ -278,6 +288,9 @@ export class OrganizationPlansComponent implements OnInit {
|
|||||||
request.premiumAccessAddon = this.selectedPlan.hasPremiumAccessOption &&
|
request.premiumAccessAddon = this.selectedPlan.hasPremiumAccessOption &&
|
||||||
this.premiumAccessAddon;
|
this.premiumAccessAddon;
|
||||||
request.planType = this.selectedPlan.type;
|
request.planType = this.selectedPlan.type;
|
||||||
|
request.billingAddressCountry = this.taxComponent.taxInfo.country;
|
||||||
|
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
|
||||||
|
|
||||||
const result = await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
const result = await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
||||||
if (!result.success && result.paymentIntentClientSecret != null) {
|
if (!result.success && result.paymentIntentClientSecret != null) {
|
||||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
import { OrganizationTaxInfoUpdateRequest } from 'jslib/models/request/organizationTaxInfoUpdateRequest';
|
import { OrganizationTaxInfoUpdateRequest } from 'jslib/models/request/organizationTaxInfoUpdateRequest';
|
||||||
import { TaxInfoUpdateRequest } from 'jslib/models/request/taxInfoUpdateRequest';
|
import { TaxInfoUpdateRequest } from 'jslib/models/request/taxInfoUpdateRequest';
|
||||||
|
import { TaxRateResponse } from 'jslib/models/response/taxRateResponse';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tax-info',
|
selector: 'app-tax-info',
|
||||||
@@ -28,6 +29,8 @@ export class TaxInfoComponent {
|
|||||||
includeTaxId: false,
|
includeTaxId: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
taxRates: TaxRateResponse[];
|
||||||
|
|
||||||
private pristine: any = {
|
private pristine: any = {
|
||||||
taxId: null,
|
taxId: null,
|
||||||
line1: null,
|
line1: null,
|
||||||
@@ -77,9 +80,22 @@ export class TaxInfoComponent {
|
|||||||
this.onCountryChanged.emit();
|
this.onCountryChanged.emit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const taxRates = await this.apiService.getTaxRates();
|
||||||
|
this.taxRates = taxRates.data;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get taxRate() {
|
||||||
|
if (this.taxRates != null) {
|
||||||
|
const localTaxRate = this.taxRates.find(x =>
|
||||||
|
x.country === this.taxInfo.country &&
|
||||||
|
x.postalCode === this.taxInfo.postalCode
|
||||||
|
);
|
||||||
|
return localTaxRate?.rate ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getTaxInfoRequest(): TaxInfoUpdateRequest {
|
getTaxInfoRequest(): TaxInfoUpdateRequest {
|
||||||
if (this.organizationId) {
|
if (this.organizationId) {
|
||||||
const request = new OrganizationTaxInfoUpdateRequest();
|
const request = new OrganizationTaxInfoUpdateRequest();
|
||||||
|
|||||||
@@ -1662,6 +1662,9 @@
|
|||||||
"paymentInformation": {
|
"paymentInformation": {
|
||||||
"message": "Payment Information"
|
"message": "Payment Information"
|
||||||
},
|
},
|
||||||
|
"billingInformation": {
|
||||||
|
"message": "Billing Information"
|
||||||
|
},
|
||||||
"creditCard": {
|
"creditCard": {
|
||||||
"message": "Credit Card"
|
"message": "Credit Card"
|
||||||
},
|
},
|
||||||
@@ -3355,5 +3358,11 @@
|
|||||||
"noSendsInList": {
|
"noSendsInList": {
|
||||||
"message": "There are no Sends to list.",
|
"message": "There are no Sends to list.",
|
||||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||||
|
},
|
||||||
|
"planPrice": {
|
||||||
|
"message": "Plan price"
|
||||||
|
},
|
||||||
|
"estimatedTax": {
|
||||||
|
"message": "Estimated tax"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user