//PaymentsClassDemo L12
//Cancel Component
//HTML
<h1>Cancelled</h1>
<p>Not sure you want to buy?;<br /> we'll preserve your cart until you're ready!</p>
//TS
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-cancel',
templateUrl: './cancel.component.html',
styleUrls: ['./cancel.component.css']
})
export class CancelComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
//Cart Component
//HTML
<h2 class="my-5">Items in cart</h2>
<div class="container products-container">
<table class="table">
<thead class="table-dark">
<tr>
<th scope="col">Subscription</th>
<th scope="col">Price</th>
<th scope="col">Quantity</th>
<th scope="col">Total Cost</th>
</tr>
</thead>
<tbody class="table-body">
<tr *ngFor="let cartSub of subscriptionsInCart">
<td>{{cartSub.subscription.name}}</td>
<td>{{cartSub.subscription.price}}</td>
<td>
<span class="increase" style="color:#89cff0">
<i class="fa-solid fa-circle-left fa-lg" (click)="reduceProdCount(cartSub.subscription)"></i>
</span>
{{cartSub.quantity}}
<span class="decrease" style="color:#89cff0">
<i class="fa-solid fa-circle-right fa-lg" (click)="increaseProdCount(cartSub.subscription)"></i>
</span></td>
<td>{{cartSub.totalCost}}</td>
</tr>
</tbody>
<tfoot class="table-footer">
<tr>
<td></td>
<td></td>
<td><b>Total:</b></td>
<td>{{totalCostOfSubcriptionsInCart}}</td>
</tr>
</tfoot>
</table>
<br>
<app-payfastcheckout></app-payfastcheckout>
</div>
//TS
import { Component, OnInit } from '@angular/core';
import { SubscriptionCartOrganiserService } from '../services/SubscriptionCartOrganiser.service';
import { CartSubScription } from '../models/CartSubscriptionVM.model';
import { Subscription } from '../models/Subscription.model';
@Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit {
subscriptionsInCart : CartSubScription [] = [];
totalCostOfSubcriptionsInCart :number = 0;
constructor(private cartManager : SubscriptionCartOrganiserService) {
this.loadSubscriptions();
cartManager.cartProductsNumberDS.subscribe(num => {
this.loadSubscriptions();
});
}
ngOnInit(): void {
}
loadSubscriptions() {
this.subscriptionsInCart = this.cartManager.getSubscriptionsInCart();
this.totalCostOfSubcriptionsInCart = this.cartManager.getTotalCostOfSubcriptionsInCart();
}
increaseProdCount (sub : Subscription) {
for (var idx = 0; idx < this.subscriptionsInCart.length; idx++) {
if (this.subscriptionsInCart[idx].subscription.id == sub.id) {
this.cartManager.addProdFromCart(this.subscriptionsInCart[idx].subscription);
}
}
}
reduceProdCount (sub : Subscription) {
for (var idx = 0; idx < this.subscriptionsInCart.length; idx++) {
if (this.subscriptionsInCart[idx].subscription.id == sub.id) {
this.cartManager.removeProdFromCart(this.subscriptionsInCart[idx].subscription);
}
}
}
}
//Models
//CartSubscriptionVM.model.ts
import { Subscription } from "./Subscription.model";
export class CartSubScription {
subscription : Subscription;
quantity : number = 1;
totalCost : number = 0;
constructor(subscr : Subscription, quant: number) {
this.subscription = subscr;
this.quantity = quant;
this.totalCost = quant * subscr.price;
}
increment() {
this.quantity +=1
}
}
//Subscription.model.ts
export class Subscription {
id : number = 0;
name : string = "";
description : string = "";
price : number = 0;
}
//navigation-bar component
//HTML
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" routerLink="">Home <span class="sr-only">(current)</span></a>
</li>
</ul>
<a class="navbar-brand" routerLink="cart">
<span class="itemCount">{{numCartItems}}</span>
<i class="fa-solid fa-cart-shopping"></i>
</a>
</div>
</nav>
//TS
import { Component, OnInit } from '@angular/core';
import { SubscriptionCartOrganiserService } from '../services/SubscriptionCartOrganiser.service';
@Component({
selector: 'app-navigation-bar',
templateUrl: './navigation-bar.component.html',
styleUrls: ['./navigation-bar.component.css']
})
export class NavigationBarComponent implements OnInit {
numCartItems : number = 0;
constructor(private cartManager : SubscriptionCartOrganiserService) {
this.numCartItems = cartManager.getNumberOfItemsInCart();
cartManager.cartProductsNumberDS.subscribe(num => {
this.numCartItems = num;
});
}
ngOnInit(): void {
}
}
//payfastcheckout component
//HTML
<button type="button" class="btn btn-primary m-3" (click)="doOnSitePayment()">Checkout</button>
//TS
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { SubscriptionCartOrganiserService } from '../services/SubscriptionCartOrganiser.service';
import { Md5 } from 'ts-md5';
import { FormBuilder } from '@angular/forms'
import { environment } from 'src/environments/environment';
declare function payfast_do_onsite_payment(param1 : any, callback: any): any;
@Component({
selector: 'app-payfastcheckout',
templateUrl: './payfastcheckout.component.html',
styleUrls: ['./payfastcheckout.component.css']
})
export class PayfastcheckoutComponent implements OnInit {
constructor(private httpComms : HttpClient, private pageRouter : Router, private cartManager : SubscriptionCartOrganiserService, private formBuilder: FormBuilder) {
}
ngOnInit(): void {
}
getSignature(data : Map<string, string>) : string {
let tmp = new URLSearchParams();
data.forEach((v, k)=> {
tmp.append(k, v)
});
let queryString = tmp.toString();
let sig = Md5.hashStr(queryString);
return sig;
}
async doOnSitePayment() {
let onSiteUserData = new Map<string, string>();
onSiteUserData.set("merchant_id", "10033427")
onSiteUserData.set("merchant_key", "mu83ipbgas9p7")
onSiteUserData.set('return_url', window.location.origin + '/success')
onSiteUserData.set('cancel_url', window.location.origin + '/cancel')
onSiteUserData.set("email_address", 'test@user.com');
onSiteUserData.set("amount", this.cartManager.getTotalCostOfSubcriptionsInCart().toString());
onSiteUserData.set("item_name", this.cartManager.getCartOrderName());
onSiteUserData.set('passphrase', 'HelloWorldHello');
let signature = this.getSignature(onSiteUserData);
onSiteUserData.set('signature', signature);
let formData = new FormData();
onSiteUserData.forEach((val, key) => {
formData.append(key, val);
});
let response = await fetch(environment.payfastOnsiteEndpoint, {
method: 'POST',
body: formData,
redirect: 'follow'
});
let respJson = await response.json();
let uuid = respJson['uuid'];
payfast_do_onsite_payment({'uuid': uuid}, (res: any) => {
if (res == true) {
this.pageRouter.navigate(['/success'])
}
else {
this.pageRouter.navigate(['/cancel'])
}
});
}
doFormPayment() {
let onSiteUserData = new Map<string, string>();
onSiteUserData.set("merchant_id", "10033427")
onSiteUserData.set("merchant_key", "mu83ipbgas9p7")
onSiteUserData.set('return_url', window.location.origin + '/success')
onSiteUserData.set('cancel_url', window.location.origin + '/cancel')
onSiteUserData.set("email_address", 'test@user.com');
onSiteUserData.set("amount", this.cartManager.getTotalCostOfSubcriptionsInCart().toString());
onSiteUserData.set("item_name", this.cartManager.getCartOrderName());
onSiteUserData.set('passphrase', 'HelloWorldHello');
let signature = this.getSignature(onSiteUserData);
onSiteUserData.set('signature', signature);
let autoPaymentForm = this.formBuilder.group(onSiteUserData);
this.httpComms.post('https://sandbox.payfast.co.za/eng/process', onSiteUserData).subscribe(resp => {
console.log(resp);
});
}
}
//productcatalog component
//HTML
<div class="row mt-5">
<div class="card m-3" style="width: 20rem;" *ngFor="let prod of products">
<div class="card-body">
<h5 class="card-title">{{prod.name}}</h5>
<p class="card-text">
{{prod.description}}
</p>
<button type="button" class="btn btn-primary" (click)="addSubscriptionToCart(prod)">
Add to cart
</button>
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div class="toast" style="position: absolute; top: 0; right: 0;">
<div class="toast-header">
<img src="..." class="rounded mr-2" alt="...">
<strong class="mr-auto">Cart items</strong>
<small>Now</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">
Added item to cart.
</div>
</div>
</div>
//TS
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Subscription } from '../models/Subscription.model';
import { FakeSubscriptionDataService } from '../services/FakeSubscriptionData.service';
import { SubscriptionCartOrganiserService } from '../services/SubscriptionCartOrganiser.service';
@Component({
selector: 'app-productcatalog',
templateUrl: './productcatalog.component.html',
styleUrls: ['./productcatalog.component.css']
})
export class ProductcatalogComponent implements OnInit {
products : Subscription [] = [];
constructor(private fakeDataProvider : FakeSubscriptionDataService, private cartSubscriptionService : SubscriptionCartOrganiserService) {
this.products = fakeDataProvider.getOfferedSubscriptions();
}
ngOnInit(): void {
}
addSubscriptionToCart(product : Subscription) {
this.cartSubscriptionService.addProdFromCart(product);
}
}
//Services
//FakeSubscriptionData.service.ts
import { Injectable } from "@angular/core";
import { Subscription } from "../models/Subscription.model";
@Injectable({
providedIn: 'root'
})
export class FakeSubscriptionDataService {
subscriptions : Subscription[];
constructor () {
this.subscriptions = [
{
id: 1,
name: "Netflix",
description: "At Netflix, we want to entertain the world. Whatever your taste, and no matter where you live, we give you access to best-in-class TV series, documentaries, feature films and mobile games.",
price : 100
},
{
id: 2,
name: "Showmax",
description: "Showmax is an internet TV service. What sets Showmax apart is a unique combination of hit African content, first and exclusive international series, movies, the best kids’ shows, and live sport.",
price : 500
},
{
id: 3,
name: "Tencent Video",
description: "Tencent Video is China's second-largest video-streaming platform. It includes a variety of categories of online videos. The most popular categories on the platform include Chinese TV shows and China-made animation shows.",
price : 800
},
{
id: 4,
name: "BBC iPlayer",
description: "BBC iPlayer is a video on demand service from the BBC. The service is available on a wide range of devices, including mobile phones and tablets, personal computers and smart televisions.",
price : 900
},
];
}
getOfferedSubscriptions () {
return this.subscriptions;
}
}
//SubscriptionCartOrganiser.service.ts
import { Injectable } from "@angular/core";
import { Subject } from "rxjs";
import { CartSubScription } from "../models/CartSubscriptionVM.model";
import { Subscription } from "../models/Subscription.model";
@Injectable({
providedIn: 'root'
})
export class SubscriptionCartOrganiserService {
static tmpSubscriptionsCartName : string = "ls-cart-subscriptions";
cartProductsNumberDS = new Subject<number>();
cartItemsOrderName : string = "Subs Order @ ";
notifyOnNewItemInCart() {
this.cartProductsNumberDS.next(this.getNumberOfItemsInCart());
}
getLocalStorageSubscriptions(): Subscription[] {
let storedSubString = localStorage.getItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName)
let cartSubscriptions = [];
if (storedSubString) {
cartSubscriptions = JSON.parse(storedSubString)
}
return cartSubscriptions;
}
getNumberOfItemsInCart() : number {
return this.getLocalStorageSubscriptions().length
}
getSubscriptionsInCart() : CartSubScription[] {
let localStorageSubs = this.getLocalStorageSubscriptions();
let cartSubscriptions : CartSubScription[] = [];
let subCounts = new Map<Number, Number>(); //temporary storage
localStorageSubs.forEach(sub => {
if (!subCounts.has(sub.id)) {
let count = localStorageSubs.filter(currSub => currSub.id == sub.id).length;
subCounts.set(sub.id, count)
let cartSub = new CartSubScription(sub, count);
cartSubscriptions.push(cartSub);
}
});
return cartSubscriptions;
}
getTotalCostOfSubcriptionsInCart() : number {
let totalCost = 0;
let cartSubs = this.getSubscriptionsInCart();
cartSubs.forEach(cartSub => {
totalCost += (cartSub.subscription.price * cartSub.quantity);
});
return totalCost;
}
getCartOrderName() {
return this.cartItemsOrderName + Date.now();
}
addSubscriptionToCart(product : Subscription) {
let storedSubString = localStorage.getItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName)
let cartSubscriptions = [];
if (storedSubString) {
cartSubscriptions = JSON.parse(storedSubString)
}
cartSubscriptions.push(product);
localStorage.setItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName, JSON.stringify(cartSubscriptions))
this.notifyOnNewItemInCart();
}
removeProdFromCart(subscr : Subscription) {
let storedSubString = localStorage.getItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName)
let cartSubscriptions = [];
if (storedSubString) {
cartSubscriptions = JSON.parse(storedSubString)
}
for (var idx = 0; idx < cartSubscriptions.length; idx++) {
if (cartSubscriptions[idx].id == subscr.id) {
cartSubscriptions.splice(idx, 1);
break;
}
}
localStorage.setItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName, JSON.stringify(cartSubscriptions))
this.notifyOnNewItemInCart();
}
addProdFromCart(subscr : Subscription) {
this.addSubscriptionToCart(subscr);
this.notifyOnNewItemInCart();
}
clearCart () {
localStorage.removeItem(SubscriptionCartOrganiserService.tmpSubscriptionsCartName);
this.notifyOnNewItemInCart();
}
}
//Success component
//HTML
<h1>Success</h1>
<p>We received your purchase;<br /> Your items will be sent shortly!</p>
//TS
import { Component, OnInit } from '@angular/core';
import { SubscriptionCartOrganiserService } from '../services/SubscriptionCartOrganiser.service';
@Component({
selector: 'app-success',
templateUrl: './success.component.html',
styleUrls: ['./success.component.css']
})
export class SuccessComponent implements OnInit {
constructor(private cartManager : SubscriptionCartOrganiserService) {
}
ngOnInit(): void {
this.cartManager.clearCart();
}
}
//app component
//app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CancelComponent } from './cancel/cancel.component';
import { CartComponent } from './cart/cart.component';
import { ProductcatalogComponent } from './productcatalog/productcatalog.component';
import { SuccessComponent } from './success/success.component';
const routes: Routes = [
{path : '', component: ProductcatalogComponent},
{path : 'cart', component: CartComponent},
{path : 'success', component: SuccessComponent},
{path: 'cancel', component: CancelComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
//app.component.html
<app-navigation-bar></app-navigation-bar>
<div class="container mt-1">
<router-outlet></router-outlet>
</div>
//app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'PaymentsClassDemo';
}
//app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SquarecheckoutComponent } from './squarecheckout/squarecheckout.component';
import { PayfastcheckoutComponent } from './payfastcheckout/payfastcheckout.component';
import { ProductcatalogComponent } from './productcatalog/productcatalog.component';
import { CartComponent } from './cart/cart.component';
import { NavigationBarComponent } from './navigation-bar/navigation-bar.component';
import { HttpClientModule } from '@angular/common/http';
import { SuccessComponent } from './success/success.component';
import { CancelComponent } from './cancel/cancel.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
SquarecheckoutComponent,
PayfastcheckoutComponent,
ProductcatalogComponent,
CartComponent,
NavigationBarComponent,
SuccessComponent,
CancelComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule, ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
//environment
//environment.prod.ts
export const environment = {
production: true,
payfastEndpoint: 'https://www.payfast.co.za/en/process',
payfastOnsiteEndpoint: 'https://www.payfast.co.za/onsite/process',
};
//environment.ts
export const environment = {
production: false,
payfastEndpoint: 'https://sandbox.payfast.co.za/eng/process',
payfastOnsiteEndpoint: 'https://sandbox.payfast.co.za/onsite/process'
};