Skip to content
This repository has been archived by the owner on Jul 1, 2021. It is now read-only.

Commit

Permalink
Merge pull request #36 from laurentksh/Premium
Browse files Browse the repository at this point in the history
Premium
  • Loading branch information
laurentksh authored Jun 19, 2021
2 parents 47d5abd + f834591 commit 5c82a2a
Show file tree
Hide file tree
Showing 34 changed files with 685 additions and 94 deletions.
34 changes: 34 additions & 0 deletions src/back-end/CryptEx/CryptExApi/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,39 @@ public async Task<IActionResult> ChangePassword(ChangePasswordDTO changePassword
return exceptionHandler.Handle(ex, Request);
}
}

[HttpPost("premium")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> GetPremium(BuyPremiumDto dto)
{
try {
var user = await HttpContext.GetUser();
await userService.GetPremium(user, dto);

return Ok();
} catch (Exception ex) {
logger.LogWarning(ex, "Could not get premium.");
return exceptionHandler.Handle(ex, Request);
}
}

[HttpDelete("premium")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> RemovePremium()
{
try {
var user = await HttpContext.GetUser();
await userService.RemovePremium(user);

return Ok();
} catch (Exception ex) {
logger.LogWarning(ex, "Could not remove premium.");
return exceptionHandler.Handle(ex, Request);
}
}
}
}
13 changes: 13 additions & 0 deletions src/back-end/CryptEx/CryptExApi/Extensions/ClaimsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace CryptExApi.Extensions
{
public static class ClaimsExtensions
{
public static bool IsPremium(this IList<Claim> claims) => claims.Any(x => x.Type == "premium" && x.Value == bool.TrueString);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/back-end/CryptEx/CryptExApi/Models/DTO/BuyPremiumDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace CryptExApi.Models.DTO
{
public class BuyPremiumDto
{
[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }

[CreditCard]
public string CreditCardNumber { get; set; }

[Required]
public string Expiracy { get; set; }

[Required]
public string Cvc { get; set; }
}
}
36 changes: 29 additions & 7 deletions src/back-end/CryptEx/CryptExApi/Services/DepositService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using CryptExApi.Data;
using CryptExApi.Extensions;
using CryptExApi.Models.Database;
using CryptExApi.Models.SignalR;
using CryptExApi.Models.ViewModel;
Expand Down Expand Up @@ -36,14 +37,16 @@ public class DepositService : IDepositService
private readonly IDepositRepository repository;
private readonly IStripeRepository stripeRepository;
private readonly IWalletRepository walletRepository;
private readonly UserManager<AppUser> userManager;

public DepositService(IConfiguration configuration, IHubContext<DepositHub> hubContext, IDepositRepository repository, IStripeRepository stripeRepository, IWalletRepository walletRepository)
public DepositService(IConfiguration configuration, IHubContext<DepositHub> hubContext, IDepositRepository repository, IStripeRepository stripeRepository, IWalletRepository walletRepository, UserManager<AppUser> userManager)
{
this.configuration = configuration;
this.hubContext = hubContext;
this.repository = repository;
this.stripeRepository = stripeRepository;
this.walletRepository = walletRepository;
this.userManager = userManager;
}

public async Task<FiatDepositViewModel> CreatePaymentSession(decimal amount, AppUser user)
Expand Down Expand Up @@ -84,25 +87,44 @@ public async Task<FiatDepositViewModel> CreatePaymentSession(decimal amount, App
break;
}

var options = new SessionCreateOptions
{
PaymentMethodTypes = paymentMethods,
LineItems = new List<SessionLineItemOptions>
var lineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
PriceData = new SessionLineItemPriceDataOptions
{
Currency = currency,
UnitAmountDecimal = amount * 100,
UnitAmountDecimal = amount * 100m,
ProductData = new SessionLineItemPriceDataProductDataOptions
{
Name = $"Fiat {user.PreferedCurrency} Deposit",
}
},
Quantity = 1
}
},
};

if (!(await userManager.GetClaimsAsync(user)).IsPremium()) {
lineItems.Add(new SessionLineItemOptions
{
PriceData = new SessionLineItemPriceDataOptions
{
Currency = currency,
UnitAmountDecimal = ((amount * 100m) * 0.02m),
ProductData = new SessionLineItemPriceDataProductDataOptions
{
Name = "Fiat Deposit Fee",
Description = "A 2% transaction fee will be applied to your deposit. Get premium now to have 0 fees on your deposits !"
}
},
Quantity = 1
});
}

var options = new SessionCreateOptions
{
PaymentMethodTypes = paymentMethods,
LineItems = lineItems,
Mode = "payment",
SuccessUrl = configuration["BaseUrl"] + "/deposit-withdraw?fiatDepositStatus=success",
CancelUrl = configuration["BaseUrl"] + "/deposit-withdraw?fiatDepositStatus=cancelled",
Expand Down
16 changes: 16 additions & 0 deletions src/back-end/CryptEx/CryptExApi/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using CryptExApi.Exceptions;
using CryptExApi.Models.Database;
Expand Down Expand Up @@ -36,6 +37,10 @@ public interface IUserService
Task SetIban(AppUser user, IbanDto dto);

Task SetAccountStatus(Guid userId, AccountStatus status);

Task GetPremium(AppUser user, BuyPremiumDto dto);

Task RemovePremium(AppUser user);
}

public class UserService : IUserService
Expand Down Expand Up @@ -158,5 +163,16 @@ public async Task SetAccountStatus(Guid userId, AccountStatus status)
{
await userRepository.SetAccountStatus(userId, status);
}

//Did not have the time to make a proper premium subscription (I originally wanted to use Stripe)
public async Task GetPremium(AppUser user, BuyPremiumDto dto)
{
await userManager.AddClaimAsync(user, new Claim("premium", bool.TrueString));
}

public async Task RemovePremium(AppUser user)
{
await userManager.RemoveClaimAsync(user, new Claim("premium", bool.TrueString));
}
}
}
2 changes: 2 additions & 0 deletions src/front-end/CryptExFrontEnd/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AdminModule } from './admin/admin.module';
import { WalletModule } from './wallet/wallet.module';
import { PremiumModule } from './premium/premium.module';

// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
Expand All @@ -38,6 +39,7 @@ export function HttpLoaderFactory(http: HttpClient) {
DepositWithdrawModule,
AdminModule,
WalletModule,
PremiumModule,
AppRoutingModule, //This must be the last module loaded, otherwise other routes will be ignored.
TranslateModule.forRoot({
loader: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div class="mr-20 mt-20 w-full 2xl:w-1/3">
<div class="shadow-md bg-white rounded-md p-5">
<h3 class="font-bold text-2xl">{{ 'DepositWithdraw.Fiat.Deposit' | translate }}</h3>
<p class="font-semibold mt-2" [ngClass]="[isPremium() ? 'text-green-500' : 'text-yellow-500']">{{ (isPremium() ? 'DepositWithdraw.Fiat.NoFee' : 'DepositWithdraw.Fiat.Fee') | translate }}</p>
<div class="mt-5">
<input type="number" (change)="amountChanged($event.target.value)" class="w-full px-2 py-2 rounded-lg border-2 border-gray-200 outline-none focus:border-indigo-500" placeholder="{{ 'DepositWithdraw.Both.EnterAny' | translate }} {{ userService.SelectedCurrency }} {{ 'DepositWithdraw.Both.Amount' | translate }}">
<button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'src/app/auth/services/auth.service';
import { SnackBar, SnackBarCreate, AlertType } from 'src/app/components/snackbar/snack-bar';
import { SnackbarService } from 'src/app/services/snackbar.service';
import { StripeService } from 'src/app/stripe/services/stripe.service';
Expand All @@ -20,6 +21,7 @@ export class DepositWithdrawFiatComponent implements OnInit {
private depWitService: DepositWithdrawService,
private stripe: StripeService,
public userService: UserService,
private authService: AuthService,
private snackBarService: SnackbarService) { }

ngOnInit(): void {
Expand All @@ -32,6 +34,10 @@ export class DepositWithdrawFiatComponent implements OnInit {
})
}

isPremium(): boolean {
return this.authService.HasClaim("premium");
}

onDeposit(): void {
this.depWitService.DepositFiat(this.amount).then(x => {
if (x.success) {
Expand Down
16 changes: 16 additions & 0 deletions src/front-end/CryptExFrontEnd/src/app/guards/premium.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { PremiumGuard } from './premium.guard';

describe('PremiumGuard', () => {
let guard: PremiumGuard;

beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(PremiumGuard);
});

it('should be created', () => {
expect(guard).toBeTruthy();
});
});
22 changes: 22 additions & 0 deletions src/front-end/CryptExFrontEnd/src/app/guards/premium.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../auth/services/auth.service';

@Injectable({
providedIn: 'root'
})
export class PremiumGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.authService.HasClaim("premium")) {
return true
} else {
this.router.navigate(["forbidden"])
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

<a routerLink="deposit-withdraw" routerLinkActive="bg-gray-900" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">{{ 'Header.NavBar.DepositWithdraw' | translate}}</a>

<a routerLink="premium" routerLinkActive="bg-gray-900" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">{{ 'Header.NavBar.Premium' | translate}}</a>

<a routerLink="contact" routerLinkActive="bg-gray-900" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">{{ 'Header.NavBar.Contact' | translate }}</a>

<a routerLink="admin" routerLinkActive="bg-yellow-900" *ngIf="IsAdmin()" class="text-white bg-yellow-400 hover:bg-yellow-500 px-3 py-2 rounded-md text-sm font-medium place-self-end ml-auto mr-0 right-0">{{ 'Header.NavBar.Admin' | translate }}</a>
Expand All @@ -39,7 +41,7 @@
<div>
<button (click)="isOpen=!isOpen" type="button" class="dp-btn bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">{{ 'Header.NavBar.OpenUserMenu' | translate }}</span>
<img class="h-8 w-8 rounded-full" src="/assets/images/default_pp_white.svg" alt="Profile Picture">
<img class="h-8 w-8 rounded-full" [ngClass]="[isPremium() ? 'bg-yellow-400 ring-2 ring-yellow-400' : '']" src="/assets/images/default_pp_white.svg" alt="Profile Picture">
</button>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export class HeaderComponent implements OnInit {
return this.authService.IsInRole("admin")
}

isPremium(): boolean {
return this.authService.HasClaim("premium");
}

@HostListener('document:click', ['$event'])
onDocumentClick(event: MouseEvent) {
var el = event.target as HTMLElement;
Expand Down
Loading

0 comments on commit 5c82a2a

Please sign in to comment.