2024-04-13 23:07:19 +09:00
import { Component , OnInit , OnDestroy , Output , EventEmitter , Input , ChangeDetectorRef } from '@angular/core' ;
2024-04-13 20:53:19 +09:00
import { Subscription , tap , of , catchError } from 'rxjs' ;
import { WebsocketService } from '../../services/websocket.service' ;
import { ServicesApiServices } from '../../services/services-api.service' ;
import { nextRoundNumber } from '../../shared/common.utils' ;
import { StateService } from '../../services/state.service' ;
2024-04-13 23:07:19 +09:00
import { AudioService } from '../../services/audio.service' ;
2024-04-13 16:11:49 +09:00
@Component ( {
selector : 'app-accelerate-checkout' ,
templateUrl : './accelerate-checkout.component.html' ,
styleUrls : [ './accelerate-checkout.component.scss' ]
} )
export class AccelerateCheckout implements OnInit , OnDestroy {
2024-04-13 20:53:19 +09:00
@Input ( ) eta : number = Date . now ( ) + 123456789 ;
@Input ( ) txid : string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad' ;
2024-04-13 23:07:19 +09:00
@Output ( ) close = new EventEmitter < null > ( ) ;
2024-04-13 20:53:19 +09:00
calculating = true ;
choosenOption : 'wait' | 'accelerate' = 'wait' ;
error = '' ;
// accelerator stuff
square : { appId : string , locationId : string } ;
accelerationUUID : string ;
estimateSubscription : Subscription ;
2024-04-13 23:07:19 +09:00
maxBidBoost : number ; // sats
2024-04-13 20:53:19 +09:00
cost : number ; // sats
// square
2024-04-13 23:07:19 +09:00
loadingCashapp = false ;
2024-04-13 20:53:19 +09:00
cashappSubmit : any ;
payments : any ;
cashAppPay : any ;
cashAppSubscription : Subscription ;
conversionsSubscription : Subscription ;
2024-04-13 23:12:05 +09:00
step : 'cta' | 'checkout' | 'processing' | 'completed' = 'cta' ;
2024-04-13 20:53:19 +09:00
constructor (
private websocketService : WebsocketService ,
private servicesApiService : ServicesApiServices ,
2024-04-13 23:07:19 +09:00
private stateService : StateService ,
private audioService : AudioService ,
private cd : ChangeDetectorRef
) {
this . accelerationUUID = window . crypto . randomUUID ( ) ;
}
2024-04-13 16:11:49 +09:00
ngOnInit() {
2024-04-13 20:53:19 +09:00
const urlParams = new URLSearchParams ( window . location . search ) ;
if ( urlParams . get ( 'cash_request_id' ) ) { // Redirected from cashapp
2024-04-13 23:07:19 +09:00
this . insertSquare ( ) ;
this . setupSquare ( ) ;
this . step = 'processing' ;
2024-04-13 20:53:19 +09:00
}
2024-04-13 23:07:19 +09:00
this . servicesApiService . setupSquare $ ( ) . subscribe ( ids = > {
this . square = {
appId : ids.squareAppId ,
locationId : ids.squareLocationId
} ;
if ( this . step === 'cta' ) {
this . estimate ( ) ;
}
} ) ;
2024-04-13 16:11:49 +09:00
}
ngOnDestroy() {
2024-04-13 20:53:19 +09:00
if ( this . estimateSubscription ) {
this . estimateSubscription . unsubscribe ( ) ;
}
}
/ * *
* Accelerator
* /
estimate() {
if ( this . estimateSubscription ) {
this . estimateSubscription . unsubscribe ( ) ;
}
this . calculating = true ;
this . estimateSubscription = this . servicesApiService . estimate $ ( this . txid ) . pipe (
tap ( ( response ) = > {
this . calculating = false ;
if ( response . status === 204 ) {
this . error = ` cannot_accelerate_tx ` ;
} else {
const estimation = response . body ;
if ( ! estimation ) {
this . error = ` cannot_accelerate_tx ` ;
return ;
}
// Make min extra fee at least 50% of the current tx fee
2024-04-13 23:07:19 +09:00
const minExtraBoost = nextRoundNumber ( Math . max ( estimation . cost * 2 , estimation . txSummary . effectiveFee ) ) ;
2024-04-13 20:53:19 +09:00
const DEFAULT_BID_RATIO = 2 ;
2024-04-13 23:07:19 +09:00
this . maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO ;
2024-04-14 00:19:14 +09:00
this . cost = this . maxBidBoost + estimation . mempoolBaseFee + estimation . vsizeFee ;
2024-04-13 20:53:19 +09:00
}
} ) ,
catchError ( ( response ) = > {
this . error = ` cannot_accelerate_tx ` ;
return of ( null ) ;
} )
) . subscribe ( ) ;
}
/ * *
* Square
* /
insertSquare ( ) : void {
//@ts-ignore
if ( window . Square ) {
return ;
}
let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js' ;
if ( document . location . hostname === 'mempool-staging.fmt.mempool.space' ||
document . location . hostname === 'mempool-staging.va1.mempool.space' ||
document . location . hostname === 'mempool-staging.fra.mempool.space' ||
document . location . hostname === 'mempool-staging.tk7.mempool.space' ||
document . location . hostname === 'mempool.space' ) {
statsUrl = 'https://web.squarecdn.com/v1/square.js' ;
}
( function ( ) {
const d = document , g = d . createElement ( 'script' ) , s = d . getElementsByTagName ( 'script' ) [ 0 ] ;
// @ts-ignore
g . type = 'text/javascript' ; g . src = statsUrl ; s . parentNode . insertBefore ( g , s ) ;
} ) ( ) ;
}
setupSquare() {
const init = ( ) = > {
this . initSquare ( ) ;
} ;
//@ts-ignore
if ( ! window . Square ) {
console . debug ( '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 . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
if ( this . cashAppPay ) {
2024-04-13 23:07:19 +09:00
await this . cashAppPay . destroy ( ) ;
2024-04-13 20:53:19 +09:00
}
const redirectHostname = document . location . hostname === 'localhost' ? ` http://localhost:4200 ` : ` https:// ${ document . location . hostname } ` ;
2024-04-13 23:07:19 +09:00
const costUSD = this . step === 'processing' ? 69.69 : ( this . cost / 100 _000_000 * conversions . USD ) ; // When we're redirected to this component, the payment data is already linked to the payment token, so does not matter what amonut we put in there, therefore it's 69.69
2024-04-13 20:53:19 +09:00
const paymentRequest = this . payments . paymentRequest ( {
countryCode : 'US' ,
currencyCode : 'USD' ,
total : {
amount : costUSD.toString ( ) ,
label : 'Total' ,
pending : true ,
productUrl : ` ${ redirectHostname } /tracker/ ${ this . txid } ` ,
} ,
button : { shape : 'semiround' , size : 'small' , theme : 'light' }
} ) ;
this . cashAppPay = await this . payments . cashAppPay ( paymentRequest , {
2024-04-13 23:07:19 +09:00
redirectURL : ` ${ redirectHostname } /tracker/ ${ this . txid } ` ,
2024-04-13 20:53:19 +09:00
referenceId : ` accelerator- ${ this . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } ` ,
button : { shape : 'semiround' , size : 'small' , theme : 'light' }
} ) ;
2024-04-13 23:07:19 +09:00
if ( this . step === 'checkout' ) {
await this . cashAppPay . attach ( ` #cash-app-pay ` , { theme : 'light' , size : 'small' , shape : 'semiround' } )
}
this . loadingCashapp = false ;
2024-04-13 20:53:19 +09:00
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 . servicesApiService . accelerateWithCashApp $ (
that . txid ,
tokenResult . token ,
tokenResult . details . cashAppPay . cashtag ,
tokenResult . details . cashAppPay . referenceId ,
that . accelerationUUID
) . subscribe ( {
next : ( ) = > {
2024-04-13 23:07:19 +09:00
that . audioService . playSound ( 'ascend-chime-cartoon' ) ;
that . step = 'completed' ;
setTimeout ( ( ) = > {
that . closeModal ( ) ;
} , 10000 ) ;
2024-04-13 20:53:19 +09:00
} ,
error : ( response ) = > {
if ( response . status === 403 && response . error === 'not_available' ) {
that . error = 'waitlisted' ;
} else {
that . error = response . error ;
}
}
} ) ;
}
} ) ;
}
) ;
}
/ * *
* UI events
* /
enableCheckoutPage() {
2024-04-13 23:07:19 +09:00
this . step = 'checkout' ;
this . loadingCashapp = true ;
2024-04-13 20:53:19 +09:00
this . insertSquare ( ) ;
this . setupSquare ( ) ;
}
selectedOptionChanged ( event ) {
this . choosenOption = event . target . id ;
2024-04-13 23:07:19 +09:00
if ( this . choosenOption === 'wait' ) {
this . restart ( ) ;
this . closeModal ( ) ;
}
2024-04-13 20:53:19 +09:00
}
restart() {
2024-04-13 23:07:19 +09:00
this . step = 'cta' ;
2024-04-13 20:53:19 +09:00
this . choosenOption = 'wait' ;
2024-04-13 16:11:49 +09:00
}
2024-04-13 09:05:05 +00:00
closeModal ( ) : void {
2024-04-13 23:07:19 +09:00
if ( this . cashAppPay ) {
this . cashAppPay . destroy ( ) ;
}
2024-04-13 09:05:05 +00:00
this . close . emit ( ) ;
}
2024-04-13 16:11:49 +09:00
}