Payment Prompt

PHOTO EMBED

Sun Jun 30 2024 12:09:26 GMT+0000 (Coordinated Universal Time)

Saved by @iamkatmakhafola

//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">&times;</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'
};
content_copyCOPY

Prompt: Consider this application 'PaymentClassDemo' which allows users to buy streaming services through a payfast payment gateway. The application makes use of frontend, meaning it does not have a backend. I want to implement payments on my application but the difference between my application and 'PaymentsClassDemo' is that mine has a backend. With that in mind i'd like process payments using a backend api controller that has endpoints that will be used in the frontend through a service that will allow a component or components to communicate with the backend to successfully make payments.