[accelerator] prepaid acceleration
This commit is contained in:
		
							parent
							
								
									78f03bd6d5
								
							
						
					
					
						commit
						8fee195577
					
				| @ -219,7 +219,7 @@ | |||||||
|               </ng-container> |               </ng-container> | ||||||
| 
 | 
 | ||||||
|               <!-- LOGIN CTA --> |               <!-- LOGIN CTA --> | ||||||
|               <ng-container *ngIf="stateService.isMempoolSpaceBuild && !isLoggedIn()"> |               <ng-container *ngIf="stateService.isMempoolSpaceBuild && !isLoggedIn() && paymentType === 'bitcoin'"> | ||||||
|                 <tr class="group-first group-last" style="border-top: 1px dashed grey"> |                 <tr class="group-first group-last" style="border-top: 1px dashed grey"> | ||||||
|                   <td class="item"></td> |                   <td class="item"></td> | ||||||
|                   <td class="amt"></td> |                   <td class="amt"></td> | ||||||
| @ -242,7 +242,7 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|    |    | ||||||
|       <div class="row mb-3" *ngIf="isLoggedIn()"> |       <div class="row mb-3" *ngIf="isLoggedIn() && paymentType === 'bitcoin'"> | ||||||
|         <div class="col"> |         <div class="col"> | ||||||
|           <div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess"> |           <div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess"> | ||||||
|             <button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button> |             <button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button> | ||||||
| @ -250,6 +250,14 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|  |       <div class="row d-flex justify-content-end mr-1" style="height: 48px" *ngIf="!hideCashApp"> | ||||||
|  |         <div id="cash-app-pay" style="max-width: 320px" [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'"></div> | ||||||
|  |         <div *ngIf="showSpinner" class="d-flex align-items-center"> | ||||||
|  |           <span class="mr-2">Loading</span> | ||||||
|  |           <div class="spinner-border text-light" style="width: 25px; height: 25px"></div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |    | ||||||
|     </div> |     </div> | ||||||
|   </ng-container> |   </ng-container> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; | import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; | ||||||
| import { ApiService } from '../../services/api.service'; |  | ||||||
| import { Subscription, catchError, of, tap } from 'rxjs'; | import { Subscription, catchError, of, tap } from 'rxjs'; | ||||||
| import { StorageService } from '../../services/storage.service'; | import { StorageService } from '../../services/storage.service'; | ||||||
| import { Transaction } from '../../interfaces/electrs.interface'; | import { Transaction } from '../../interfaces/electrs.interface'; | ||||||
| @ -40,7 +39,7 @@ export const MAX_BID_RATIO = 4; | |||||||
|   templateUrl: 'accelerate-preview.component.html', |   templateUrl: 'accelerate-preview.component.html', | ||||||
|   styleUrls: ['accelerate-preview.component.scss'] |   styleUrls: ['accelerate-preview.component.scss'] | ||||||
| }) | }) | ||||||
| export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges { | export class AcceleratePreviewComponent implements OnDestroy, OnChanges { | ||||||
|   @Input() tx: Transaction | undefined; |   @Input() tx: Transaction | undefined; | ||||||
|   @Input() scrollEvent: boolean; |   @Input() scrollEvent: boolean; | ||||||
| 
 | 
 | ||||||
| @ -63,18 +62,37 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
| 
 | 
 | ||||||
|   maxRateOptions: RateOption[] = []; |   maxRateOptions: RateOption[] = []; | ||||||
| 
 | 
 | ||||||
|  |   // Cashapp payment
 | ||||||
|  |   paymentType: 'bitcoin' | 'cashapp' = 'bitcoin'; | ||||||
|  |   cashAppSubscription: Subscription; | ||||||
|  |   conversionsSubscription: Subscription; | ||||||
|  |   payments: any; | ||||||
|  |   showSpinner = false; | ||||||
|  |   square: any; | ||||||
|  |   cashAppPay: any; | ||||||
|  |   hideCashApp = false; | ||||||
|  | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     public stateService: StateService, |     public stateService: StateService, | ||||||
|     private servicesApiService: ServicesApiServices, |     private servicesApiService: ServicesApiServices, | ||||||
|     private storageService: StorageService, |     private storageService: StorageService, | ||||||
|     private audioService: AudioService, |     private audioService: AudioService, | ||||||
|     private cd: ChangeDetectorRef |     private cd: ChangeDetectorRef | ||||||
|   ) { } |   ) { | ||||||
|  |     if (window.document.referrer === 'cash.app') { | ||||||
|  |       this.paymentType = 'cashapp'; | ||||||
|  |     } else { | ||||||
|  |       this.paymentType = 'bitcoin'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   ngOnDestroy(): void { |   ngOnDestroy(): void { | ||||||
|     if (this.estimateSubscription) { |     if (this.estimateSubscription) { | ||||||
|       this.estimateSubscription.unsubscribe(); |       this.estimateSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
|  |     if (this.cashAppPay) { | ||||||
|  |       this.cashAppPay.destroy(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges(changes: SimpleChanges): void { |   ngOnChanges(changes: SimpleChanges): void { | ||||||
| @ -83,9 +101,16 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngAfterViewInit() { | ||||||
|  |     this.showSpinner = true; | ||||||
|  | 
 | ||||||
|     this.user = this.storageService.getAuth()?.user ?? null; |     this.user = this.storageService.getAuth()?.user ?? null; | ||||||
| 
 | 
 | ||||||
|  |     this.servicesApiService.setupSquare$().subscribe(ids => { | ||||||
|  |       this.square = { | ||||||
|  |         appId: ids.squareAppId, | ||||||
|  |         locationId: ids.squareLocationId | ||||||
|  |       }; | ||||||
|       this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( |       this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( | ||||||
|         tap((response) => { |         tap((response) => { | ||||||
|           if (response.status === 204) { |           if (response.status === 204) { | ||||||
| @ -101,6 +126,11 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|               this.estimateSubscription.unsubscribe(); |               this.estimateSubscription.unsubscribe(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             if (this.paymentType === 'cashapp') { | ||||||
|  |               this.estimate.userBalance = 999999999; | ||||||
|  |               this.estimate.enoughBalance = true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { |             if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) { | ||||||
|               if (this.isLoggedIn()) { |               if (this.isLoggedIn()) { | ||||||
|                 this.error = `not_enough_balance`; |                 this.error = `not_enough_balance`; | ||||||
| @ -135,6 +165,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
| 
 | 
 | ||||||
|             if (!this.error) { |             if (!this.error) { | ||||||
|               this.scrollToPreview('acceleratePreviewAnchor', 'start'); |               this.scrollToPreview('acceleratePreviewAnchor', 'start'); | ||||||
|  |               if (this.paymentType === 'cashapp') { | ||||||
|  |                 this.setupSquare(); | ||||||
|  |               }  | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         }), |         }), | ||||||
| @ -146,6 +179,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|           return of(null); |           return of(null); | ||||||
|         }) |         }) | ||||||
|       ).subscribe(); |       ).subscribe(); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
| @ -216,4 +250,99 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|   onResize(): void { |   onResize(): void { | ||||||
|     this.isMobile = window.innerWidth <= 767.98; |     this.isMobile = window.innerWidth <= 767.98; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * CashApp payment | ||||||
|  |    */ | ||||||
|  |   setupSquare() { | ||||||
|  |     const init = () => { | ||||||
|  |       this.initSquare(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     //@ts-ignore
 | ||||||
|  |     if (!window.Square) { | ||||||
|  |       console.warn('Square.js failed to load properly. Retrying in 1 second.'); | ||||||
|  |       setTimeout(init, 1000); | ||||||
|  |     } else { | ||||||
|  |       init(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async initSquare(): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       //@ts-ignore
 | ||||||
|  |       this.payments = window.Square.payments(this.square.appId, this.square.locationId) | ||||||
|  |       await this.requestCashAppPayment(); | ||||||
|  |     } catch (e) { | ||||||
|  |       console.error(e); | ||||||
|  |       this.error = 'Error loading Square Payments'; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async requestCashAppPayment() { | ||||||
|  |     if (this.cashAppSubscription) { | ||||||
|  |       this.cashAppSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|  |     if (this.conversionsSubscription) { | ||||||
|  |       this.conversionsSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|  |     this.hideCashApp = false; | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     this.conversionsSubscription = this.stateService.conversions$.subscribe( | ||||||
|  |       async (conversions) => { | ||||||
|  |         const maxCostUsd = this.maxCost / 100_000_000 * conversions.USD; | ||||||
|  |         const paymentRequest = this.payments.paymentRequest({ | ||||||
|  |           countryCode: 'US', | ||||||
|  |           currencyCode: 'USD', | ||||||
|  |           total: { | ||||||
|  |             amount: maxCostUsd.toString(), | ||||||
|  |             label: 'Total', | ||||||
|  |             pending: true, | ||||||
|  |             productUrl: `https://mempool.space/tx/${this.tx.txid}`, | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |         this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { | ||||||
|  |           redirectURL: 'https://my.website/checkout', | ||||||
|  |           referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, | ||||||
|  |         }); | ||||||
|  |         await this.cashAppPay.attach('#cash-app-pay'); | ||||||
|  |         this.showSpinner = false; | ||||||
|  |          | ||||||
|  |         const that = this; | ||||||
|  |         this.cashAppPay.addEventListener('ontokenization', function (event) { | ||||||
|  |           const { tokenResult, error } = event.detail; | ||||||
|  |           if (error) { | ||||||
|  |             this.error = error; | ||||||
|  |           } else if (tokenResult.status === 'OK') { | ||||||
|  |             that.hideCashApp = true; | ||||||
|  | 
 | ||||||
|  |             that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$( | ||||||
|  |               that.tx.txid, | ||||||
|  |               that.userBid, | ||||||
|  |               tokenResult.token, | ||||||
|  |               tokenResult.details.cashAppPay.cashtag, | ||||||
|  |               tokenResult.details.cashAppPay.referenceId | ||||||
|  |             ).subscribe({ | ||||||
|  |               next: () => { | ||||||
|  |                 that.audioService.playSound('ascend-chime-cartoon'); | ||||||
|  |                 that.showSuccess = true; | ||||||
|  |                 that.scrollToPreviewWithTimeout('successAlert', 'center'); | ||||||
|  |                 that.estimateSubscription.unsubscribe(); | ||||||
|  |               }, | ||||||
|  |               error: (response) => { | ||||||
|  |                 if (response.status === 403 && response.error === 'not_available') { | ||||||
|  |                   that.error = 'waitlisted'; | ||||||
|  |                 } else { | ||||||
|  |                   that.error = response.error; | ||||||
|  |                 } | ||||||
|  |                 that.scrollToPreviewWithTimeout('mempoolError', 'center'); | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ import { EnterpriseService } from '../../services/enterprise.service'; | |||||||
| import { NavigationService } from '../../services/navigation.service'; | import { NavigationService } from '../../services/navigation.service'; | ||||||
| import { MenuComponent } from '../menu/menu.component'; | import { MenuComponent } from '../menu/menu.component'; | ||||||
| import { StorageService } from '../../services/storage.service'; | import { StorageService } from '../../services/storage.service'; | ||||||
| import { ApiService } from '../../services/api.service'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-master-page', |   selector: 'app-master-page', | ||||||
| @ -45,7 +44,6 @@ export class MasterPageComponent implements OnInit, OnDestroy { | |||||||
|     private enterpriseService: EnterpriseService, |     private enterpriseService: EnterpriseService, | ||||||
|     private navigationService: NavigationService, |     private navigationService: NavigationService, | ||||||
|     private storageService: StorageService, |     private storageService: StorageService, | ||||||
|     private apiService: ApiService, |  | ||||||
|     private router: Router, |     private router: Router, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -141,6 +141,10 @@ export class ServicesApiServices { | |||||||
|     return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid }); |     return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string) { | ||||||
|  |     return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getAccelerations$(): Observable<Acceleration[]> { |   getAccelerations$(): Observable<Acceleration[]> { | ||||||
|     return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`); |     return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`); | ||||||
|   } |   } | ||||||
| @ -160,4 +164,8 @@ export class ServicesApiServices { | |||||||
|   getAccelerationStats$(): Observable<AccelerationStats> { |   getAccelerationStats$(): Observable<AccelerationStats> { | ||||||
|     return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`); |     return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   setupSquare$(): Observable<{squareAppId: string, squareLocationId: string}> { | ||||||
|  |     return this.httpClient.get<{squareAppId: string, squareLocationId: string}>(`${SERVICES_API_PREFIX}/square/setup`); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user