Stripe Integration

This commit is contained in:
2024-08-20 23:27:07 +02:00
parent 056db7b199
commit 48bff89526
28 changed files with 728 additions and 21 deletions

View File

@@ -6,7 +6,7 @@
} @else {
<a routerLink="/pricing" class="text-gray-800">Pricing</a>
<a (click)="login()" class="text-blue-600 border border-blue-600 px-3 py-2 rounded">Log In</a>
<a (click)="register()" class="text-white bg-blue-600 px-4 py-2 rounded">Register</a>
<a routerLink="/pricing" class="text-white bg-blue-600 px-4 py-2 rounded">Register</a>
}
</div>
<button (click)="toggleMenu()" class="md:hidden text-gray-600">

View File

@@ -20,10 +20,6 @@
<i class="fas fa-check text-green-500 mr-2"></i>
Get early access to new listings
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
Professional/Broker trial period (3 month)
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
Extended search functionality
@@ -31,7 +27,7 @@
</ul>
</div>
<div class="px-6 py-4 mt-auto">
<button class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Sign Up Now</button>
<button (click)="register()" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Sign Up Now</button>
</div>
</div>
@@ -51,7 +47,7 @@
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
Extended public listings (3+ months)
3-Month Free Trial
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
@@ -76,7 +72,8 @@
</ul>
</div>
<div class="px-6 py-4 mt-auto">
<button class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Get Started</button>
<!-- <button routerLink="/payment" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Get Started</button> -->
<button (click)="register('price_1PpSkpDjmFBOcNBs9UDPgBos')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Get Started</button>
</div>
</div>
@@ -100,7 +97,7 @@
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
Extended public listings (3+ months)
3-Month Free Trial
</li>
<li class="mb-4 flex items-center">
<i class="fas fa-check text-green-500 mr-2"></i>
@@ -125,7 +122,8 @@
</ul>
</div>
<div class="px-6 py-4 mt-auto">
<button class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Start Listing Now</button>
<!-- <button routerLink="/payment" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Start Listing Now</button> -->
<button (click)="register('price_1PpSmRDjmFBOcNBsaaSp2nk9')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Start Listing Now</button>
</div>
</div>
</div>

View File

@@ -1,5 +1,10 @@
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
import { StripeService } from 'ngx-stripe';
import { switchMap } from 'rxjs';
import { environment } from '../../../environments/environment';
import { SharedModule } from '../../shared/shared/shared.module';
@Component({
@@ -10,8 +15,40 @@ import { SharedModule } from '../../shared/shared/shared.module';
styleUrl: './pricing.component.scss',
})
export class PricingComponent {
constructor(public keycloakService: KeycloakService) {}
register() {
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
private apiBaseUrl = environment.apiBaseUrl;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
constructor(public keycloakService: KeycloakService, private http: HttpClient, private stripeService: StripeService, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
if (this.id) {
this.checkout(atob(this.id));
}
}
register(priceId?: string) {
if (priceId) {
this.keycloakService.register({ redirectUri: `${window.location.origin}/pricing/${btoa(priceId)}` });
} else {
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
}
}
checkout(priceId) {
// Check the server.js tab to see an example implementation
this.http
.post(`${this.apiBaseUrl}/bizmatch/payment/create-checkout-session`, { priceId })
.pipe(
switchMap((session: any) => {
return this.stripeService.redirectToCheckout({ sessionId: session.id });
}),
)
.subscribe(result => {
// If `redirectToCheckout` fails due to a browser or network
// error, you should display the localized error message to your
// customer using `error.message`.
if (result.error) {
alert(result.error.message);
}
});
}
}

View File

@@ -133,7 +133,7 @@ export class AccountComponent {
const confirmed = await this.confirmationService.showConfirmation({ message: 'Are you sure you want to switch to Buyer ? All your listings as well as all your professionals informations will be deleted' });
if (confirmed) {
const id = this.user.id;
this.user = createDefaultUser(this.user.email, this.user.firstname, this.user.lastname);
this.user = createDefaultUser(this.user.email, this.user.firstname, this.user.lastname, 'free');
this.user.customerType = 'buyer';
this.user.id = id;
this.imageService.deleteLogoImagesByMail(this.user.email);

View File

@@ -0,0 +1,31 @@
<div class="min-h-screen flex items-center justify-center">
<div class="bg-white p-6 rounded-lg shadow-lg max-w-md">
<div class="text-center">
@if(user && (user.subscriptionPlan==='professional' || user.subscriptionPlan==='broker')){
<svg class="mx-auto h-16 w-16 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<h2 class="text-2xl font-bold text-gray-800 mt-4">Subscription Successful!</h2>
<p class="text-gray-600 mt-2">Thank you for subscribing to our service.</p>
<p class="text-gray-600 mt-2">
You have successfully subscribed to the @if(user.subscriptionPlan==='professional'){
<span class="font-semibold text-gray-800">Professional (CPA, Attorney, Title Company) Plan</span>
} @if(user.subscriptionPlan==='broker'){
<span class="font-semibold text-gray-800">Business Broker Plan</span>. }
</p>
<p class="text-gray-600 mt-2">We are excited to have you on board!</p>
<div class="mt-6">
<a routerLink="/account" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg">Go to your Account</a>
</div>
<div class="mt-4 text-sm text-gray-500">
<p>If you have any questions, feel free to <a routerLink="/emailUs" class="text-blue-500 hover:underline">contact our support team</a>.</p>
</div>
} @else {
<p class="text-gray-600 mt-2">We are processing your subscription - Please be patient</p>
<div class="loader mt-8 mx-auto"></div>
}
</div>
</div>
</div>

View File

@@ -0,0 +1,33 @@
/* HTML: <div class="loader"></div> */
.loader {
width: 15px;
aspect-ratio: 1;
position: relative;
}
.loader::before,
.loader::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: #000;
}
.loader::before {
box-shadow: -25px 0;
animation: l8-1 1s infinite linear;
}
.loader::after {
transform: rotate(0deg) translateX(25px);
animation: l8-2 1s infinite linear;
}
@keyframes l8-1 {
100% {
transform: translateX(25px);
}
}
@keyframes l8-2 {
100% {
transform: rotate(-180deg) translateX(25px);
}
}

View File

@@ -0,0 +1,62 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
import { User } from '../../../../../bizmatch-server/src/models/db.model';
import { LogService } from '../../services/log.service';
import { UserService } from '../../services/user.service';
import { map2User } from '../../utils/utils';
@Component({
selector: 'app-success',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './success.component.html',
styleUrl: './success.component.scss',
})
export class SuccessComponent {
user: User;
constructor(private keycloakService: KeycloakService, private userService: UserService, private logService: LogService, private router: Router) {}
async ngOnInit() {
let email = null;
try {
const token = await this.keycloakService.getToken();
const keycloakUser = map2User(token);
email = keycloakUser.email;
this.user = await this.userService.getByMail(email);
this.checkSubscriptionPlan(email);
} catch (e) {
this.checkSubscriptionPlan(email, e.message);
}
}
async checkSubscriptionPlan(email: string, error?: string) {
if (!email) {
this.logService.log({ severity: 'error', text: `Unauthorized Access to Success Page ${error}` });
this.router.navigate(['home']);
return;
}
let attempts = 0;
const maxAttempts = 5;
const interval = 5000; // 5 Sekunden
const intervalId = setInterval(async () => {
if (attempts >= maxAttempts) {
clearInterval(intervalId);
console.error('Max attempts reached');
return;
}
attempts++;
this.user = await this.userService.getByMail(email);
if (this.user && this.user.subscriptionPlan) {
clearInterval(intervalId);
console.log('Subscription plan is set:', this.user.subscriptionPlan);
} else {
console.log(`Attempt ${attempts}: Subscription plan is not set yet.`);
}
}, interval);
}
}