2024-07-26 09:27:18 +00:00
/* eslint-disable no-console */
2024-06-27 09:10:32 +00:00
import { Component , OnInit , OnDestroy , Output , EventEmitter , Input , ChangeDetectorRef , SimpleChanges , HostListener } from '@angular/core' ;
2024-06-30 08:39:32 +00:00
import { Subscription , tap , of , catchError , Observable , switchMap } from 'rxjs' ;
2024-10-22 21:05:01 +09:00
import { ServicesApiServices } from '@app/services/services-api.service' ;
2024-11-26 17:14:47 +01:00
import { md5 } from '@app/shared/common.utils' ;
2024-10-22 21:05:01 +09:00
import { StateService } from '@app/services/state.service' ;
import { AudioService } from '@app/services/audio.service' ;
import { ETA , EtaService } from '@app/services/eta.service' ;
2024-10-23 11:09:38 +09:00
import { Transaction } from '@interfaces/electrs.interface' ;
2024-10-22 21:05:01 +09:00
import { MiningStats } from '@app/services/mining.service' ;
import { IAuth , AuthServiceMempool } from '@app/services/auth.service' ;
import { EnterpriseService } from '@app/services/enterprise.service' ;
import { ApiService } from '@app/services/api.service' ;
2024-08-05 00:47:35 +02:00
import { isDevMode } from '@angular/core' ;
2024-06-27 09:10:32 +00:00
2025-01-19 17:56:19 +09:00
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile' ;
2024-06-27 12:56:49 +00:00
2024-06-27 09:10:32 +00:00
export type AccelerationEstimate = {
hasAccess : boolean ;
txSummary : TxSummary ;
nextBlockFee : number ;
targetFeeRate : number ;
userBalance : number ;
enoughBalance : boolean ;
cost : number ;
mempoolBaseFee : number ;
vsizeFee : number ;
2024-06-27 12:56:49 +00:00
pools : number [ ] ;
2025-01-19 17:56:19 +09:00
availablePaymentMethods : Record < PaymentMethod , { min : number , max : number , card ? : { card_id : string , last_4 : string , brand : string , name : string , billing : any }} > ;
2024-07-06 10:48:37 +00:00
unavailable? : boolean ;
2024-07-08 05:18:42 +00:00
options : { // recommended bid options
fee : number ; // recommended userBid in sats
} [ ] ;
2024-06-27 09:10:32 +00:00
}
export type TxSummary = {
txid : string ; // txid of the current transaction
effectiveVsize : number ; // Total vsize of the dependency tree
effectiveFee : number ; // Total fee of the dependency tree in sats
ancestorCount : number ; // Number of ancestors
}
export interface RateOption {
fee : number ;
rate : number ;
index : number ;
}
export const MIN_BID_RATIO = 1 ;
export const DEFAULT_BID_RATIO = 2 ;
export const MAX_BID_RATIO = 4 ;
2024-04-13 16:11:49 +09:00
2025-01-19 17:56:19 +09:00
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'cardonfile' | 'processing' | 'paid' | 'success' ;
2024-06-28 07:02:12 +00:00
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-06-27 02:02:35 +00:00
@Input ( ) tx : Transaction ;
2024-07-03 18:07:41 +09:00
@Input ( ) accelerating : boolean = false ;
2024-06-27 02:02:35 +00:00
@Input ( ) miningStats : MiningStats ;
@Input ( ) eta : ETA ;
2024-06-30 01:46:11 +00:00
@Input ( ) scrollEvent : boolean ;
2024-06-28 07:02:12 +00:00
@Input ( ) cashappEnabled : boolean = true ;
2024-07-21 23:08:08 +02:00
@Input ( ) applePayEnabled : boolean = false ;
2024-07-25 15:54:24 +02:00
@Input ( ) googlePayEnabled : boolean = true ;
2025-01-19 17:56:19 +09:00
@Input ( ) cardOnFileEnabled : boolean = true ;
2024-06-27 09:10:32 +00:00
@Input ( ) advancedEnabled : boolean = false ;
@Input ( ) forceMobile : boolean = false ;
2024-06-30 05:37:51 +00:00
@Input ( ) showDetails : boolean = false ;
2024-06-30 03:43:28 +00:00
@Input ( ) noCTA : boolean = false ;
2024-07-05 10:41:59 +00:00
@Output ( ) unavailable = new EventEmitter < boolean > ( ) ;
2024-07-03 18:07:41 +09:00
@Output ( ) completed = new EventEmitter < boolean > ( ) ;
2024-06-30 05:37:51 +00:00
@Output ( ) hasDetails = new EventEmitter < boolean > ( ) ;
2024-06-27 09:10:32 +00:00
@Output ( ) changeMode = new EventEmitter < boolean > ( ) ;
2024-04-13 20:53:19 +09:00
calculating = true ;
2024-09-12 16:02:11 +02:00
processing = false ;
2024-12-22 12:27:29 +00:00
isCheckoutLocked = 0 ; // reference counter, 0 = unlocked, >0 = locked
isTokenizing = 0 ; // reference counter, 0 = false, >0 = true
2024-07-01 06:19:11 +00:00
selectedOption : 'wait' | 'accel' ;
2024-07-05 10:41:59 +00:00
cantPayReason = '' ;
quoteError = '' ; // error fetching estimate or initial data
accelerateError = '' ; // error executing acceleration
btcpayInvoiceFailed = false ;
timePaid : number = 0 ; // time acceleration requested
2024-06-27 09:10:32 +00:00
math = Math ;
isMobile : boolean = window . innerWidth <= 767.98 ;
2024-10-23 22:51:04 +09:00
isProdDomain = false ;
2024-04-13 20:53:19 +09:00
2024-06-28 07:02:12 +00:00
private _step : CheckoutStep = 'summary' ;
2024-06-27 09:10:32 +00:00
simpleMode : boolean = true ;
2024-07-06 08:24:18 +00:00
timeoutTimer : any ;
2024-06-26 18:35:36 +09:00
2024-07-01 16:21:47 +09:00
authSubscription$ : Subscription ;
auth : IAuth | null = null ;
2024-06-27 09:10:32 +00:00
2024-04-13 20:53:19 +09:00
// accelerator stuff
2024-06-27 09:10:32 +00:00
accelerationSubscription : Subscription ;
difficultySubscription : Subscription ;
2024-04-13 20:53:19 +09:00
estimateSubscription : Subscription ;
2024-06-24 02:06:22 +00:00
estimate : AccelerationEstimate ;
2024-04-13 23:07:19 +09:00
maxBidBoost : number ; // sats
2024-04-13 20:53:19 +09:00
cost : number ; // sats
2024-06-24 02:06:22 +00:00
etaInfo$ : Observable < { hashratePercentage : number , ETA : number , acceleratedETA : number } > ;
2024-06-27 09:10:32 +00:00
showSuccess = false ;
hasAncestors : boolean = false ;
minExtraCost = 0 ;
minBidAllowed = 0 ;
maxBidAllowed = 0 ;
defaultBid = 0 ;
userBid = 0 ;
selectFeeRateIndex = 1 ;
maxRateOptions : RateOption [ ] = [ ] ;
2024-04-13 20:53:19 +09:00
// square
2024-04-13 23:07:19 +09:00
loadingCashapp = false ;
2024-07-21 22:17:47 +02:00
loadingApplePay = false ;
2024-07-25 15:54:24 +02:00
loadingGooglePay = false ;
2025-01-19 17:56:19 +09:00
loadingCardOnFile = false ;
2024-04-13 20:53:19 +09:00
payments : any ;
cashAppPay : any ;
2024-07-21 22:17:47 +02:00
applePay : any ;
2024-07-25 15:54:24 +02:00
googlePay : any ;
2024-04-13 20:53:19 +09:00
conversionsSubscription : Subscription ;
2024-07-26 09:27:18 +00:00
conversions : Record < string , number > ;
2024-06-26 18:35:36 +09:00
// btcpay
loadingBtcpayInvoice = false ;
invoice = undefined ;
2024-04-13 20:53:19 +09:00
constructor (
2024-06-27 09:10:32 +00:00
public stateService : StateService ,
2024-07-23 15:17:22 +00:00
private apiService : ApiService ,
2024-04-13 20:53:19 +09:00
private servicesApiService : ServicesApiServices ,
2024-06-24 02:06:22 +00:00
private etaService : EtaService ,
2024-04-13 23:07:19 +09:00
private audioService : AudioService ,
2024-07-01 16:21:47 +09:00
private cd : ChangeDetectorRef ,
2024-07-10 16:18:13 +00:00
private authService : AuthServiceMempool ,
private enterpriseService : EnterpriseService ,
2024-04-13 23:07:19 +09:00
) {
2024-10-23 22:51:04 +09:00
this . isProdDomain = this . stateService . env . PROD_DOMAINS . indexOf ( document . location . hostname ) > - 1 ;
2024-07-21 23:08:08 +02:00
// Check if Apple Pay available
2024-07-26 09:27:18 +00:00
// https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/checking_for_apple_pay_availability#overview
if ( window [ 'ApplePaySession' ] ) {
2024-07-21 23:08:08 +02:00
this . applePayEnabled = true ;
}
2024-04-13 23:07:19 +09:00
}
2024-04-13 16:11:49 +09:00
2024-07-26 09:27:18 +00:00
ngOnInit ( ) : void {
2024-07-01 16:21:47 +09:00
this . authSubscription $ = this . authService . getAuth $ ( ) . subscribe ( ( auth ) = > {
2024-07-02 13:08:20 +00:00
if ( this . auth ? . user ? . userId !== auth ? . user ? . userId ) {
this . auth = auth ;
this . estimate = null ;
2024-07-05 10:41:59 +00:00
this . quoteError = null ;
this . accelerateError = null ;
this . timePaid = 0 ;
this . btcpayInvoiceFailed = false ;
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'summary' , true ) ;
2024-07-02 13:08:20 +00:00
} else {
this . auth = auth ;
}
2024-07-01 16:21:47 +09:00
} ) ;
this . authService . refreshAuth $ ( ) . subscribe ( ) ;
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-12-22 15:17:39 +00:00
this . moveToStep ( 'processing' , true ) ;
2024-04-13 23:07:19 +09:00
this . insertSquare ( ) ;
this . setupSquare ( ) ;
2024-06-28 07:02:12 +00:00
} else {
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'summary' , true ) ;
2024-04-13 20:53:19 +09:00
}
2024-04-13 23:07:19 +09:00
2024-07-01 18:18:13 +09:00
this . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
this . conversions = conversions ;
}
) ;
2024-04-13 16:11:49 +09:00
}
2024-07-26 09:27:18 +00:00
ngOnDestroy ( ) : void {
2024-04-13 20:53:19 +09:00
if ( this . estimateSubscription ) {
this . estimateSubscription . unsubscribe ( ) ;
}
2024-07-01 16:21:47 +09:00
if ( this . authSubscription $ ) {
this . authSubscription $ . unsubscribe ( ) ;
}
2024-04-13 20:53:19 +09:00
}
2024-06-30 01:46:11 +00:00
ngOnChanges ( changes : SimpleChanges ) : void {
if ( changes . scrollEvent && this . scrollEvent ) {
this . scrollToElement ( 'acceleratePreviewAnchor' , 'start' ) ;
}
2024-08-27 11:29:29 +02:00
if ( changes . accelerating && this . accelerating ) {
if ( this . step === 'processing' || this . step === 'paid' ) {
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'success' , true ) ;
2024-08-27 11:29:29 +02:00
} else { // Edge case where the transaction gets accelerated by someone else or on another session
this . closeModal ( ) ;
2024-07-03 18:07:41 +09:00
}
}
2024-06-30 01:46:11 +00:00
}
2024-12-22 15:17:39 +00:00
moveToStep ( step : CheckoutStep , force : boolean = false ) : void {
if ( this . isCheckoutLocked > 0 && ! force ) {
return ;
}
2024-11-16 11:23:09 +01:00
this . processing = false ;
2024-06-28 07:02:12 +00:00
this . _step = step ;
2024-07-06 08:24:18 +00:00
if ( this . timeoutTimer ) {
clearTimeout ( this . timeoutTimer ) ;
}
2024-06-28 07:02:12 +00:00
if ( ! this . estimate && [ 'quote' , 'summary' , 'checkout' ] . includes ( this . step ) ) {
this . fetchEstimate ( ) ;
}
2024-07-10 16:18:13 +00:00
if ( this . _step === 'checkout' ) {
2024-08-02 23:27:40 +02:00
this . insertSquare ( ) ;
2024-07-10 16:18:13 +00:00
this . enterpriseService . goal ( 8 ) ;
}
2024-06-28 07:02:12 +00:00
if ( this . _step === 'checkout' && this . canPayWithBitcoin ) {
2024-07-05 10:41:59 +00:00
this . btcpayInvoiceFailed = false ;
2024-06-28 07:02:12 +00:00
this . loadingBtcpayInvoice = true ;
2024-06-30 10:08:49 +00:00
this . invoice = null ;
2024-06-28 07:02:12 +00:00
this . requestBTCPayInvoice ( ) ;
} else if ( this . _step === 'cashapp' && this . cashappEnabled ) {
this . loadingCashapp = true ;
this . setupSquare ( ) ;
2024-07-21 22:38:49 +02:00
this . scrollToElementWithTimeout ( 'confirm-title' , 'center' , 100 ) ;
2024-07-21 22:17:47 +02:00
} else if ( this . _step === 'applepay' && this . applePayEnabled ) {
this . loadingApplePay = true ;
this . setupSquare ( ) ;
2024-07-21 22:38:49 +02:00
this . scrollToElementWithTimeout ( 'confirm-title' , 'center' , 100 ) ;
2024-07-25 15:54:24 +02:00
} else if ( this . _step === 'googlepay' && this . googlePayEnabled ) {
this . loadingGooglePay = true ;
this . setupSquare ( ) ;
this . scrollToElementWithTimeout ( 'confirm-title' , 'center' , 100 ) ;
2025-01-19 17:56:19 +09:00
} else if ( this . _step === 'cardonfile' && this . cardOnFileEnabled ) {
this . loadingCardOnFile = true ;
this . setupSquare ( ) ;
this . scrollToElementWithTimeout ( 'confirm-title' , 'center' , 100 ) ;
2024-07-06 08:24:18 +00:00
} else if ( this . _step === 'paid' ) {
this . timePaid = Date . now ( ) ;
this . timeoutTimer = setTimeout ( ( ) = > {
if ( this . step === 'paid' ) {
this . accelerateError = 'internal_server_error' ;
}
2024-07-26 09:27:18 +00:00
} , 120000 ) ;
2024-06-28 07:02:12 +00:00
}
2024-06-30 05:37:51 +00:00
this . hasDetails . emit ( this . _step === 'quote' ) ;
2024-06-28 07:02:12 +00:00
}
2024-07-03 18:07:41 +09:00
closeModal ( ) : void {
this . completed . emit ( true ) ;
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'summary' , true ) ;
2024-07-03 18:07:41 +09:00
}
2024-06-30 01:46:11 +00:00
/ * *
2024-07-21 22:17:47 +02:00
* Scroll to element id with or without setTimeout
* /
2024-06-30 01:46:11 +00:00
scrollToElementWithTimeout ( id : string , position : ScrollLogicalPosition , timeout : number = 1000 ) : void {
setTimeout ( ( ) = > {
this . scrollToElement ( id , position ) ;
} , timeout ) ;
}
2024-07-26 09:27:18 +00:00
scrollToElement ( id : string , position : ScrollLogicalPosition ) : void {
2024-06-30 01:46:11 +00:00
const acceleratePreviewAnchor = document . getElementById ( id ) ;
if ( acceleratePreviewAnchor ) {
this . cd . markForCheck ( ) ;
acceleratePreviewAnchor . scrollIntoView ( {
behavior : 'smooth' ,
inline : position ,
block : position ,
} ) ;
}
}
2024-04-16 16:56:37 +09:00
/ * *
2024-04-13 20:53:19 +09:00
* Accelerator
* /
2024-07-26 09:27:18 +00:00
fetchEstimate ( ) : void {
2024-04-13 20:53:19 +09:00
if ( this . estimateSubscription ) {
this . estimateSubscription . unsubscribe ( ) ;
}
this . calculating = true ;
2024-07-05 10:41:59 +00:00
this . quoteError = null ;
this . accelerateError = null ;
2024-06-27 02:02:35 +00:00
this . estimateSubscription = this . servicesApiService . estimate $ ( this . tx . txid ) . pipe (
2024-04-13 20:53:19 +09:00
tap ( ( response ) = > {
if ( response . status === 204 ) {
2024-07-05 10:41:59 +00:00
this . quoteError = ` cannot_accelerate_tx ` ;
if ( this . step === 'summary' ) {
this . unavailable . emit ( true ) ;
}
2024-04-13 20:53:19 +09:00
} else {
2024-06-24 02:06:22 +00:00
this . estimate = response . body ;
if ( ! this . estimate ) {
2024-07-05 10:41:59 +00:00
this . quoteError = ` cannot_accelerate_tx ` ;
if ( this . step === 'summary' ) {
this . unavailable . emit ( true ) ;
}
2024-04-13 20:53:19 +09:00
return ;
}
2024-06-27 09:10:32 +00:00
if ( this . estimate . hasAccess === true && this . estimate . userBalance <= 0 ) {
if ( this . isLoggedIn ( ) ) {
2024-07-05 10:41:59 +00:00
this . quoteError = ` not_enough_balance ` ;
2024-06-27 09:10:32 +00:00
}
}
2024-07-06 10:48:37 +00:00
if ( this . estimate . unavailable ) {
this . quoteError = ` temporarily_unavailable ` ;
}
2024-06-27 09:10:32 +00:00
this . hasAncestors = this . estimate . txSummary . ancestorCount > 1 ;
this . etaInfo $ = this . etaService . getProjectedEtaObservable ( this . estimate , this . miningStats ) ;
2024-07-08 05:18:42 +00:00
this . maxRateOptions = this . estimate . options . map ( ( option , index ) = > ( {
fee : option.fee ,
rate : ( this . estimate . txSummary . effectiveFee + option . fee ) / this . estimate . txSummary . effectiveVsize ,
index
} ) ) ;
2024-06-27 09:10:32 +00:00
2024-07-08 05:18:42 +00:00
this . defaultBid = this . maxRateOptions [ 1 ] . fee ;
2024-06-27 09:10:32 +00:00
this . userBid = this . defaultBid ;
this . cost = this . userBid + this . estimate . mempoolBaseFee + this . estimate . vsizeFee ;
2024-07-05 10:41:59 +00:00
this . validateChoice ( ) ;
2024-07-11 12:27:31 +00:00
if ( ! this . couldPay ) {
this . quoteError = ` cannot_accelerate_tx ` ;
if ( this . step === 'summary' ) {
this . unavailable . emit ( true ) ;
}
return ;
}
2024-06-28 13:29:44 +00:00
if ( this . step === 'checkout' && this . canPayWithBitcoin && ! this . loadingBtcpayInvoice ) {
2024-06-28 07:02:12 +00:00
this . loadingBtcpayInvoice = true ;
this . requestBTCPayInvoice ( ) ;
}
2024-06-26 18:35:36 +09:00
this . calculating = false ;
this . cd . markForCheck ( ) ;
2024-04-13 20:53:19 +09:00
}
} ) ,
2024-07-26 09:27:18 +00:00
catchError ( ( ) = > {
2024-06-27 09:10:32 +00:00
this . estimate = undefined ;
2024-07-05 10:41:59 +00:00
this . quoteError = ` cannot_accelerate_tx ` ;
2024-06-27 09:10:32 +00:00
this . estimateSubscription . unsubscribe ( ) ;
2024-07-05 13:53:03 +00:00
if ( this . step === 'summary' ) {
this . unavailable . emit ( true ) ;
} else {
this . accelerateError = 'cannot_accelerate_tx' ;
}
2024-04-13 20:53:19 +09:00
return of ( null ) ;
} )
) . subscribe ( ) ;
}
2024-07-05 10:41:59 +00:00
validateChoice ( ) : void {
if ( ! this . canPay ) {
if ( this . estimate ? . availablePaymentMethods ? . balance ) {
if ( this . cost >= this . estimate ? . userBalance ) {
this . cantPayReason = 'not_enough_balance' ;
}
} else {
this . cantPayReason = 'cannot_accelerate_tx' ;
}
} else {
this . cantPayReason = '' ;
}
}
2024-06-27 09:10:32 +00:00
/ * *
* User changed his bid
* /
setUserBid ( { fee , index } : { fee : number , index : number } ) : void {
if ( this . estimate ) {
this . selectFeeRateIndex = index ;
this . userBid = Math . max ( 0 , fee ) ;
this . cost = this . userBid + this . estimate . mempoolBaseFee + this . estimate . vsizeFee ;
2024-10-14 14:49:53 +09:00
this . validateChoice ( ) ;
2024-06-27 09:10:32 +00:00
}
}
/ * *
* Account - based acceleration request
* /
accelerateWithMempoolAccount ( ) : void {
2024-09-12 16:02:11 +02:00
if ( ! this . canPay || this . calculating || this . processing ) {
2024-07-01 05:45:32 +00:00
return ;
}
2024-09-12 16:02:11 +02:00
this . processing = true ;
2024-06-27 09:10:32 +00:00
if ( this . accelerationSubscription ) {
this . accelerationSubscription . unsubscribe ( ) ;
}
this . accelerationSubscription = this . servicesApiService . accelerate $ (
this . tx . txid ,
this . userBid ,
) . subscribe ( {
next : ( ) = > {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-23 15:17:22 +00:00
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
2024-06-27 09:10:32 +00:00
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
this . showSuccess = true ;
this . estimateSubscription . unsubscribe ( ) ;
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'paid' , true ) ;
2024-06-27 09:10:32 +00:00
} ,
error : ( response ) = > {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-05 10:41:59 +00:00
this . accelerateError = response . error ;
2024-06-27 09:10:32 +00:00
}
} ) ;
}
2024-04-13 20:53:19 +09:00
/ * *
* Square
* /
insertSquare ( ) : void {
2024-08-05 00:47:35 +02:00
if ( ! this . isProdDomain && ! isDevMode ( ) ) {
return ;
}
2024-07-26 09:27:18 +00:00
if ( window [ 'Square' ] ) {
2024-04-13 20:53:19 +09:00
return ;
}
let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js' ;
2024-08-05 00:47:35 +02:00
if ( this . isProdDomain ) {
2024-08-02 23:35:30 +02:00
statsUrl = '/square/v1/square.js' ;
2024-04-13 20:53:19 +09:00
}
2024-07-26 09:27:18 +00:00
( function ( ) : void {
2024-04-13 20:53:19 +09:00
const d = document , g = d . createElement ( 'script' ) , s = d . getElementsByTagName ( 'script' ) [ 0 ] ;
g . type = 'text/javascript' ; g . src = statsUrl ; s . parentNode . insertBefore ( g , s ) ;
} ) ( ) ;
}
2024-07-26 09:27:18 +00:00
setupSquare ( ) : void {
2024-08-05 00:47:35 +02:00
if ( ! this . isProdDomain && ! isDevMode ( ) ) {
return ;
}
2024-07-26 09:27:18 +00:00
const init = ( ) : void = > {
2024-04-13 20:53:19 +09:00
this . initSquare ( ) ;
} ;
2024-07-26 09:27:18 +00:00
if ( ! window [ 'Square' ] ) {
2024-08-02 23:04:15 +02:00
console . debug ( 'Square.js failed to load properly. Retrying.' ) ;
setTimeout ( this . setupSquare . bind ( this ) , 100 ) ;
2024-04-13 20:53:19 +09:00
} else {
init ( ) ;
}
}
async initSquare ( ) : Promise < void > {
try {
2024-07-25 21:49:48 +02:00
this . servicesApiService . setupSquare $ ( ) . subscribe ( {
next : async ( ids ) = > {
2024-07-26 09:27:18 +00:00
this . payments = window [ 'Square' ] . payments ( ids . squareAppId , ids . squareLocationId ) ;
2024-07-25 21:49:48 +02:00
const urlParams = new URLSearchParams ( window . location . search ) ;
if ( this . _step === 'cashapp' || urlParams . get ( 'cash_request_id' ) ) {
await this . requestCashAppPayment ( ) ;
} else if ( this . _step === 'applepay' ) {
await this . requestApplePayPayment ( ) ;
} else if ( this . _step === 'googlepay' ) {
await this . requestGooglePayPayment ( ) ;
2025-01-19 17:56:19 +09:00
} else if ( this . _step === 'cardonfile' ) {
this . loadingCardOnFile = false ;
2024-07-25 21:49:48 +02:00
}
} ,
error : ( ) = > {
console . debug ( 'Error loading Square Payments' ) ;
this . accelerateError = 'cannot_setup_square' ;
}
2024-07-26 09:27:18 +00:00
} ) ;
2024-04-13 20:53:19 +09:00
} catch ( e ) {
2024-04-16 16:56:37 +09:00
console . debug ( 'Error loading Square Payments' , e ) ;
2024-07-25 21:49:48 +02:00
this . accelerateError = 'cannot_setup_square' ;
2024-04-13 20:53:19 +09:00
}
}
2024-07-21 22:17:47 +02:00
/ * *
* APPLE PAY
* /
2024-07-26 09:27:18 +00:00
async requestApplePayPayment ( ) : Promise < void > {
2024-09-12 16:02:11 +02:00
if ( this . processing ) {
return ;
}
2024-07-21 22:17:47 +02:00
if ( this . conversionsSubscription ) {
this . conversionsSubscription . unsubscribe ( ) ;
2024-04-13 20:53:19 +09:00
}
2024-07-26 09:27:18 +00:00
2024-09-12 16:02:11 +02:00
this . processing = true ;
2024-07-21 22:17:47 +02:00
this . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
this . conversions = conversions ;
if ( this . applePay ) {
this . applePay . destroy ( ) ;
}
const costUSD = this . cost / 100 _000_000 * conversions . USD ;
const paymentRequest = this . payments . paymentRequest ( {
countryCode : 'US' ,
currencyCode : 'USD' ,
total : {
amount : costUSD.toFixed ( 2 ) ,
label : 'Total' ,
} ,
} ) ;
try {
this . applePay = await this . payments . applePay ( paymentRequest ) ;
const applePayButton = document . getElementById ( 'apple-pay-button' ) ;
if ( ! applePayButton ) {
console . error ( ` Unable to find apple pay button id='apple-pay-button' ` ) ;
// Try again
setTimeout ( this . requestApplePayPayment . bind ( this ) , 500 ) ;
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-21 22:17:47 +02:00
return ;
}
this . loadingApplePay = false ;
applePayButton . addEventListener ( 'click' , async event = > {
2024-12-22 15:17:39 +00:00
if ( this . isCheckoutLocked > 0 || this . isTokenizing > 0 ) {
return ;
}
2024-07-21 22:17:47 +02:00
event . preventDefault ( ) ;
2024-12-22 12:27:29 +00:00
try {
// lock the checkout UI and show a loading spinner until the square modals are finished
2024-12-22 12:27:29 +00:00
this . isCheckoutLocked ++ ;
this . isTokenizing ++ ;
2024-12-22 12:27:29 +00:00
const tokenResult = await this . applePay . tokenize ( ) ;
if ( tokenResult ? . status === 'OK' ) {
const card = tokenResult . details ? . card ;
if ( ! card || ! card . brand || ! card . expMonth || ! card . expYear || ! card . last4 ) {
console . error ( ` Cannot retreive payment card details ` ) ;
this . accelerateError = 'apple_pay_no_card_details' ;
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-12-22 12:27:29 +00:00
return ;
}
const cardTag = md5 ( ` ${ card . brand } ${ card . expMonth } ${ card . expYear } ${ card . last4 } ` . toLowerCase ( ) ) ;
2024-12-22 12:27:29 +00:00
// keep checkout in loading state until the acceleration request completes
this . isTokenizing ++ ;
this . isCheckoutLocked ++ ;
2024-12-22 12:27:29 +00:00
this . servicesApiService . accelerateWithApplePay $ (
this . tx . txid ,
tokenResult . token ,
cardTag ,
` accelerator- ${ this . tx . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } ` ,
costUSD
) . subscribe ( {
next : ( ) = > {
this . processing = false ;
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
if ( this . applePay ) {
this . applePay . destroy ( ) ;
}
2024-07-21 22:17:47 +02:00
setTimeout ( ( ) = > {
2024-12-22 12:27:29 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'paid' , true ) ;
2024-12-22 12:27:29 +00:00
} , 1000 ) ;
} ,
error : ( response ) = > {
this . processing = false ;
this . accelerateError = response . error ;
if ( ! ( response . status === 403 && response . error === 'not_available' ) ) {
setTimeout ( ( ) = > {
2024-12-22 15:17:39 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
2024-12-22 12:27:29 +00:00
// Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams ( window . location . search ) ;
window . location . assign ( window . location . toString ( ) . replace ( ` ?cash_request_id= ${ urlParams . get ( 'cash_request_id' ) } ` , ` ` ) ) ;
2025-01-20 17:23:15 +09:00
} , 10000 ) ;
2024-12-22 12:27:29 +00:00
}
2024-07-21 22:17:47 +02:00
}
2024-12-22 12:27:29 +00:00
} ) ;
} else {
this . processing = false ;
let errorMessage = ` Tokenization failed with status: ${ tokenResult . status } ` ;
if ( tokenResult . errors ) {
errorMessage += ` and errors: ${ JSON . stringify (
tokenResult . errors ,
) } ` ;
2024-07-21 22:17:47 +02:00
}
2024-12-22 12:27:29 +00:00
throw new Error ( errorMessage ) ;
2024-07-21 22:17:47 +02:00
}
2024-12-22 12:27:29 +00:00
} finally {
// always unlock the checkout once we're finished
2024-12-22 12:27:29 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
2024-07-21 22:17:47 +02:00
}
} ) ;
} catch ( e ) {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-21 22:17:47 +02:00
console . error ( e ) ;
}
}
) ;
}
2024-07-25 15:54:24 +02:00
/ * *
* GOOGLE PAY
* /
2024-07-26 09:27:18 +00:00
async requestGooglePayPayment ( ) : Promise < void > {
2024-09-12 16:02:11 +02:00
if ( this . processing ) {
return ;
}
2024-07-25 15:54:24 +02:00
if ( this . conversionsSubscription ) {
this . conversionsSubscription . unsubscribe ( ) ;
}
2024-09-12 16:02:11 +02:00
this . processing = true ;
2024-07-25 15:54:24 +02:00
this . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
this . conversions = conversions ;
if ( this . googlePay ) {
this . googlePay . destroy ( ) ;
}
const costUSD = this . cost / 100 _000_000 * conversions . USD ;
const paymentRequest = this . payments . paymentRequest ( {
countryCode : 'US' ,
currencyCode : 'USD' ,
total : {
amount : costUSD.toFixed ( 2 ) ,
label : 'Total'
}
} ) ;
this . googlePay = await this . payments . googlePay ( paymentRequest , {
referenceId : ` accelerator- ${ this . tx . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } ` ,
} ) ;
await this . googlePay . attach ( ` #google-pay-button ` , {
buttonType : 'pay' ,
2024-07-26 17:39:58 +02:00
buttonSizeMode : 'fill' ,
2024-07-25 15:54:24 +02:00
} ) ;
this . loadingGooglePay = false ;
document . getElementById ( 'google-pay-button' ) . addEventListener ( 'click' , async event = > {
2024-12-22 15:17:39 +00:00
if ( this . isCheckoutLocked > 0 || this . isTokenizing > 0 ) {
return ;
}
2024-07-25 15:54:24 +02:00
event . preventDefault ( ) ;
2024-12-22 12:27:29 +00:00
try {
// lock the checkout UI and show a loading spinner until the square modals are finished
2024-12-22 12:27:29 +00:00
this . isCheckoutLocked ++ ;
this . isTokenizing ++ ;
2024-12-22 12:27:29 +00:00
const tokenResult = await this . googlePay . tokenize ( ) ;
if ( tokenResult ? . status === 'OK' ) {
const card = tokenResult . details ? . card ;
if ( ! card || ! card . brand || ! card . expMonth || ! card . expYear || ! card . last4 ) {
console . error ( ` Cannot retreive payment card details ` ) ;
this . accelerateError = 'apple_pay_no_card_details' ;
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-12-22 12:27:29 +00:00
return ;
}
const verificationToken = await this . $verifyBuyer ( this . payments , tokenResult . token , tokenResult . details , costUSD . toFixed ( 2 ) ) ;
2025-01-12 11:11:29 +09:00
if ( ! verificationToken || ! verificationToken . token ) {
2024-12-22 12:27:29 +00:00
console . error ( ` SCA verification failed ` ) ;
this . accelerateError = 'SCA Verification Failed. Payment Declined.' ;
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-12-22 12:27:29 +00:00
return ;
}
const cardTag = md5 ( ` ${ card . brand } ${ card . expMonth } ${ card . expYear } ${ card . last4 } ` . toLowerCase ( ) ) ;
2024-12-22 12:27:29 +00:00
// keep checkout in loading state until the acceleration request completes
this . isCheckoutLocked ++ ;
this . isTokenizing ++ ;
2024-12-22 12:27:29 +00:00
this . servicesApiService . accelerateWithGooglePay $ (
this . tx . txid ,
tokenResult . token ,
2025-01-12 11:11:29 +09:00
verificationToken . token ,
2024-12-22 12:27:29 +00:00
cardTag ,
` accelerator- ${ this . tx . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } ` ,
2025-01-12 11:11:29 +09:00
costUSD ,
verificationToken . userChallenged
2024-12-22 12:27:29 +00:00
) . subscribe ( {
next : ( ) = > {
this . processing = false ;
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
if ( this . googlePay ) {
this . googlePay . destroy ( ) ;
}
2024-07-25 15:54:24 +02:00
setTimeout ( ( ) = > {
2024-12-22 15:17:39 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
this . moveToStep ( 'paid' , true ) ;
2024-12-22 12:27:29 +00:00
} , 1000 ) ;
} ,
error : ( response ) = > {
this . processing = false ;
this . accelerateError = response . error ;
2024-12-22 12:27:29 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
2024-12-22 12:27:29 +00:00
if ( ! ( response . status === 403 && response . error === 'not_available' ) ) {
setTimeout ( ( ) = > {
// Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams ( window . location . search ) ;
window . location . assign ( window . location . toString ( ) . replace ( ` ?cash_request_id= ${ urlParams . get ( 'cash_request_id' ) } ` , ` ` ) ) ;
2025-01-20 17:23:15 +09:00
} , 10000 ) ;
2024-12-22 12:27:29 +00:00
}
2024-07-25 15:54:24 +02:00
}
2024-12-22 12:27:29 +00:00
} ) ;
} else {
this . processing = false ;
let errorMessage = ` Tokenization failed with status: ${ tokenResult . status } ` ;
if ( tokenResult . errors ) {
errorMessage += ` and errors: ${ JSON . stringify (
tokenResult . errors ,
) } ` ;
2024-07-25 15:54:24 +02:00
}
2024-12-22 12:27:29 +00:00
throw new Error ( errorMessage ) ;
2024-07-25 15:54:24 +02:00
}
2024-12-22 12:27:29 +00:00
} finally {
// always unlock the checkout once we're finished
2024-12-22 12:27:29 +00:00
this . isTokenizing -- ;
this . isCheckoutLocked -- ;
2024-07-26 09:27:18 +00:00
}
2024-07-25 15:54:24 +02:00
} ) ;
}
) ;
}
2025-01-19 17:56:19 +09:00
/ * *
* Card On File
* /
async requestCardOnFilePayment ( ) : Promise < void > {
if ( this . processing ) {
return ;
}
if ( this . conversionsSubscription ) {
this . conversionsSubscription . unsubscribe ( ) ;
}
this . processing = true ;
this . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
this . conversions = conversions ;
const costUSD = this . cost / 100 _000_000 * conversions . USD ;
if ( this . isCheckoutLocked > 0 ) {
return ;
}
const cardOnFile = this . estimate ? . availablePaymentMethods ? . cardOnFile ;
if ( ! cardOnFile ? . card ) {
this . accelerateError = 'card_on_file_not_found' ;
return ;
}
this . loadingCardOnFile = false ;
try {
this . isCheckoutLocked += 2 ;
this . isTokenizing += 2 ;
const nameParts = cardOnFile . card . name . split ( ' ' ) ;
const assumedGivenName = nameParts [ 0 ] ;
const assumedFamilyName = nameParts . length > 1 ? nameParts [ 1 ] : undefined ;
const verificationDetails = {
card : {
billing : {
givenName : assumedGivenName ,
familyName : assumedFamilyName ,
2025-01-23 18:00:27 +09:00
addressLines : [ cardOnFile . card . billing . addressLine1 ? ? '' ] ,
city : cardOnFile.card.billing.locality ? ? '' ,
state : cardOnFile.card.billing.administrativeDistrictLevel1 ? ? '' ,
2025-01-19 17:56:19 +09:00
countyCode : cardOnFile.card.billing.country ,
}
}
} ;
const verificationToken = await this . $verifyBuyer ( this . payments , cardOnFile . card . card_id , verificationDetails , costUSD . toFixed ( 2 ) ) ;
if ( ! verificationToken || ! verificationToken . token ) {
console . error ( ` SCA verification failed ` ) ;
this . accelerateError = 'SCA Verification Failed. Payment Declined.' ;
this . processing = false ;
return ;
}
this . servicesApiService . accelerateWithCardOnFile $ (
this . tx . txid ,
cardOnFile . card . card_id ,
verificationToken . token ,
` accelerator- ${ this . tx . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } ` ,
costUSD ,
verificationToken . userChallenged
) . subscribe ( {
next : ( ) = > {
this . processing = false ;
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
setTimeout ( ( ) = > {
this . isCheckoutLocked -- ;
this . isTokenizing -- ;
this . moveToStep ( 'paid' , true ) ;
} , 1000 ) ;
} ,
error : ( response ) = > {
this . processing = false ;
this . accelerateError = response . error ;
this . isCheckoutLocked -- ;
this . isTokenizing -- ;
if ( ! ( response . status === 403 && response . error === 'not_available' ) ) {
setTimeout ( ( ) = > {
// Reset everything by reloading the page :D, can be improved
const urlParams = new URLSearchParams ( window . location . search ) ;
window . location . assign ( window . location . toString ( ) . replace ( ` ?cash_request_id= ${ urlParams . get ( 'cash_request_id' ) } ` , ` ` ) ) ;
} , 3000 ) ;
}
}
} ) ;
} catch ( e ) {
console . log ( e ) ;
this . isCheckoutLocked -- ;
this . isTokenizing -- ;
this . processing = false ;
this . accelerateError = e . message ;
} finally {
// always unlock the checkout once we're finished
this . isCheckoutLocked -- ;
this . isTokenizing -- ;
}
}
) ;
}
2024-07-21 22:17:47 +02:00
/ * *
* CASHAPP
* /
2024-07-26 09:27:18 +00:00
async requestCashAppPayment ( ) : Promise < void > {
2024-09-12 16:02:11 +02:00
if ( this . processing ) {
return ;
}
2024-04-13 20:53:19 +09:00
if ( this . conversionsSubscription ) {
this . conversionsSubscription . unsubscribe ( ) ;
}
2024-07-26 09:27:18 +00:00
2024-09-12 16:02:11 +02:00
this . processing = true ;
2024-04-13 20:53:19 +09:00
this . conversionsSubscription = this . stateService . conversions $ . subscribe (
async ( conversions ) = > {
2024-07-01 18:18:13 +09:00
this . conversions = conversions ;
2024-04-13 20:53:19 +09:00
if ( this . cashAppPay ) {
2024-04-16 16:56:37 +09:00
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 : {
2024-07-21 22:17:47 +02:00
amount : costUSD.toFixed ( 2 ) ,
2024-04-13 20:53:19 +09:00
label : 'Total' ,
pending : true ,
2024-08-04 20:52:49 -04:00
productUrl : ` ${ redirectHostname } /tx/ ${ this . tx . txid } ` ,
2024-07-25 15:54:24 +02:00
}
2024-04-13 20:53:19 +09:00
} ) ;
this . cashAppPay = await this . payments . cashAppPay ( paymentRequest , {
2024-08-04 20:52:49 -04:00
redirectURL : ` ${ redirectHostname } /tx/ ${ this . tx . txid } ` ,
2024-07-25 15:54:24 +02:00
referenceId : ` accelerator- ${ this . tx . txid . substring ( 0 , 15 ) } - ${ Math . round ( new Date ( ) . getTime ( ) / 1000 ) } `
2024-04-13 20:53:19 +09:00
} ) ;
2024-04-13 23:07:19 +09:00
2024-07-26 17:39:58 +02:00
await this . cashAppPay . attach ( ` #cash-app-pay ` , { theme : 'dark' } ) ;
2024-04-13 23:07:19 +09:00
this . loadingCashapp = false ;
2024-07-21 22:17:47 +02:00
this . cashAppPay . addEventListener ( 'ontokenization' , event = > {
2024-04-13 20:53:19 +09:00
const { tokenResult , error } = event . detail ;
if ( error ) {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-05 10:41:59 +00:00
this . accelerateError = error ;
2024-04-13 20:53:19 +09:00
} else if ( tokenResult . status === 'OK' ) {
2024-07-21 22:17:47 +02:00
this . servicesApiService . accelerateWithCashApp $ (
this . tx . txid ,
2024-04-13 20:53:19 +09:00
tokenResult . token ,
tokenResult . details . cashAppPay . cashtag ,
tokenResult . details . cashAppPay . referenceId ,
2024-10-08 18:37:03 +09:00
costUSD
2024-04-13 20:53:19 +09:00
) . subscribe ( {
next : ( ) = > {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-23 15:17:22 +00:00
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
2024-07-21 22:17:47 +02:00
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
if ( this . cashAppPay ) {
this . cashAppPay . destroy ( ) ;
2024-04-16 16:56:37 +09:00
}
setTimeout ( ( ) = > {
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'paid' , true ) ;
2024-04-16 17:02:10 +09:00
if ( window . history . replaceState ) {
const urlParams = new URLSearchParams ( window . location . search ) ;
window . history . replaceState ( null , null , window . location . toString ( ) . replace ( ` ?cash_request_id= ${ urlParams . get ( 'cash_request_id' ) } ` , '' ) ) ;
}
2024-04-16 16:56:37 +09:00
} , 1000 ) ;
2024-04-13 20:53:19 +09:00
} ,
error : ( response ) = > {
2024-09-12 16:02:11 +02:00
this . processing = false ;
2024-07-21 22:17:47 +02:00
this . accelerateError = response . error ;
2024-07-05 10:41:59 +00:00
if ( ! ( response . status === 403 && response . error === 'not_available' ) ) {
2024-04-16 16:56:37 +09:00
setTimeout ( ( ) = > {
2024-04-16 17:02:10 +09:00
// Reset everything by reloading the page :D, can be improved
2024-04-16 16:56:37 +09:00
const urlParams = new URLSearchParams ( window . location . search ) ;
window . location . assign ( window . location . toString ( ) . replace ( ` ?cash_request_id= ${ urlParams . get ( 'cash_request_id' ) } ` , ` ` ) ) ;
2024-11-14 16:46:18 +01:00
} , 10000 ) ;
2024-04-13 20:53:19 +09:00
}
}
} ) ;
}
} ) ;
}
) ;
}
2024-12-10 15:16:19 +01:00
/ * *
2024-12-25 16:38:53 +08:00
* https : //developer.squareup.com/docs/sca-overview
2024-12-10 15:16:19 +01:00
* /
2024-12-25 16:38:53 +08:00
async $verifyBuyer ( payments , token , details , amount ) : Promise < { token : string , userChallenged : boolean } > {
2024-12-10 15:16:19 +01:00
const verificationDetails = {
amount : amount ,
currencyCode : 'USD' ,
intent : 'CHARGE' ,
billingContact : {
givenName : details.card?.billing?.givenName ,
familyName : details.card?.billing?.familyName ,
phone : details.card?.billing?.phone ,
addressLines : details.card?.billing?.addressLines ,
city : details.card?.billing?.city ,
state : details.card?.billing?.state ,
countryCode : details.card?.billing?.countryCode ,
} ,
} ;
const verificationResults = await payments . verifyBuyer (
token ,
verificationDetails ,
) ;
2024-12-25 16:38:53 +08:00
return verificationResults ;
2024-12-10 15:16:19 +01:00
}
2024-06-26 18:35:36 +09:00
/ * *
* BTCPay
* /
2024-07-26 09:27:18 +00:00
async requestBTCPayInvoice ( ) : Promise < void > {
2024-06-30 08:39:32 +00:00
this . servicesApiService . generateBTCPayAcceleratorInvoice $ ( this . tx . txid , this . userBid ) . pipe (
switchMap ( response = > {
return this . servicesApiService . retreiveInvoice $ ( response . btcpayInvoiceId ) ;
} ) ,
catchError ( error = > {
console . log ( error ) ;
2024-07-05 10:41:59 +00:00
this . btcpayInvoiceFailed = true ;
2024-06-30 08:39:32 +00:00
return of ( null ) ;
} )
) . subscribe ( ( invoice ) = > {
this . invoice = invoice ;
2024-06-26 18:35:36 +09:00
this . cd . markForCheck ( ) ;
} ) ;
}
2024-06-30 10:04:24 +00:00
bitcoinPaymentCompleted ( ) : void {
2024-07-23 15:17:22 +00:00
this . apiService . logAccelerationRequest $ ( this . tx . txid ) . subscribe ( ) ;
2024-06-30 10:04:24 +00:00
this . audioService . playSound ( 'ascend-chime-cartoon' ) ;
this . estimateSubscription . unsubscribe ( ) ;
2024-12-22 15:17:39 +00:00
this . moveToStep ( 'paid' , true ) ;
2024-06-30 10:04:24 +00:00
}
2024-06-27 09:10:32 +00:00
isLoggedIn ( ) : boolean {
2024-07-01 16:21:47 +09:00
return this . auth !== null ;
2024-06-27 09:10:32 +00:00
}
2024-07-01 06:19:11 +00:00
/ * *
* UI events
* /
2024-07-26 09:27:18 +00:00
selectedOptionChanged ( event ) : void {
2024-07-01 06:19:11 +00:00
this . selectedOption = event . target . id ;
}
2024-07-26 09:27:18 +00:00
get step ( ) : CheckoutStep {
2024-06-28 07:02:12 +00:00
return this . _step ;
}
2024-07-26 09:27:18 +00:00
get paymentMethods ( ) : PaymentMethod [ ] {
return Object . keys ( this . estimate ? . availablePaymentMethods || { } ) as PaymentMethod [ ] ;
2024-07-05 10:41:59 +00:00
}
2024-07-26 09:27:18 +00:00
get couldPayWithBitcoin ( ) : boolean {
2024-07-05 10:41:59 +00:00
return ! ! this . estimate ? . availablePaymentMethods ? . bitcoin ;
}
2024-07-26 09:27:18 +00:00
get couldPayWithCashapp ( ) : boolean {
2024-07-11 07:56:15 +00:00
if ( ! this . cashappEnabled ) {
2024-07-05 10:41:59 +00:00
return false ;
}
return ! ! this . estimate ? . availablePaymentMethods ? . cashapp ;
}
2024-07-26 09:27:18 +00:00
get couldPayWithApplePay ( ) : boolean {
2024-07-24 22:20:52 +02:00
if ( ! this . applePayEnabled ) {
return false ;
}
return ! ! this . estimate ? . availablePaymentMethods ? . applePay ;
}
2024-07-26 09:27:18 +00:00
get couldPayWithGooglePay ( ) : boolean {
2024-07-25 15:54:24 +02:00
if ( ! this . googlePayEnabled ) {
return false ;
}
return ! ! this . estimate ? . availablePaymentMethods ? . googlePay ;
}
2024-07-26 09:27:18 +00:00
get couldPayWithBalance ( ) : boolean {
2024-07-05 10:41:59 +00:00
if ( ! this . hasAccessToBalanceMode ) {
return false ;
}
return ! ! this . estimate ? . availablePaymentMethods ? . balance ;
}
2024-07-26 09:27:18 +00:00
get couldPay ( ) : boolean {
2024-07-26 09:06:47 +00:00
return this . couldPayWithBalance || this . couldPayWithBitcoin || this . couldPayWithCashapp || this . couldPayWithApplePay || this . couldPayWithGooglePay ;
2024-07-05 10:41:59 +00:00
}
2024-07-26 09:27:18 +00:00
get canPayWithBitcoin ( ) : boolean {
2024-07-01 18:18:13 +09:00
const paymentMethod = this . estimate ? . availablePaymentMethods ? . bitcoin ;
return paymentMethod && this . cost >= paymentMethod . min && this . cost <= paymentMethod . max ;
2024-06-28 07:02:12 +00:00
}
2024-07-26 09:27:18 +00:00
get canPayWithCashapp ( ) : boolean {
2024-08-05 01:43:58 +02:00
if ( ! this . cashappEnabled || ! this . conversions || ( ! this . isProdDomain && ! isDevMode ( ) ) ) {
2024-07-01 18:18:13 +09:00
return false ;
}
const paymentMethod = this . estimate ? . availablePaymentMethods ? . cashapp ;
if ( paymentMethod ) {
const costUSD = ( this . cost / 100 _000_000 * this . conversions . USD ) ;
if ( costUSD >= paymentMethod . min && costUSD <= paymentMethod . max ) {
return true ;
}
}
2024-07-24 22:20:52 +02:00
2024-07-01 18:18:13 +09:00
return false ;
2024-06-28 07:02:12 +00:00
}
2024-07-26 09:27:18 +00:00
get canPayWithApplePay ( ) : boolean {
2024-08-05 01:43:58 +02:00
if ( ! this . applePayEnabled || ! this . conversions || ( ! this . isProdDomain && ! isDevMode ( ) ) ) {
2024-07-21 22:17:47 +02:00
return false ;
}
const paymentMethod = this . estimate ? . availablePaymentMethods ? . applePay ;
if ( paymentMethod ) {
const costUSD = ( this . cost / 100 _000_000 * this . conversions . USD ) ;
if ( costUSD >= paymentMethod . min && costUSD <= paymentMethod . max ) {
return true ;
}
}
2024-07-24 22:20:52 +02:00
2024-07-21 22:17:47 +02:00
return false ;
}
2024-07-26 09:27:18 +00:00
get canPayWithGooglePay ( ) : boolean {
2024-08-05 01:43:58 +02:00
if ( ! this . googlePayEnabled || ! this . conversions || ( ! this . isProdDomain && ! isDevMode ( ) ) ) {
2024-07-25 15:54:24 +02:00
return false ;
}
const paymentMethod = this . estimate ? . availablePaymentMethods ? . googlePay ;
if ( paymentMethod ) {
2025-01-19 17:56:19 +09:00
const costUSD = ( this . cost / 100 _000_000 * this . conversions . USD ) ;
if ( costUSD >= paymentMethod . min && costUSD <= paymentMethod . max ) {
return true ;
}
}
return false ;
}
get canPayWithCardOnFile ( ) : boolean {
if ( ! this . cardOnFileEnabled || ! this . conversions || ( ! this . isProdDomain && ! isDevMode ( ) ) ) {
return false ;
}
const paymentMethod = this . estimate ? . availablePaymentMethods ? . cardOnFile ;
if ( paymentMethod ) {
2024-07-25 15:54:24 +02:00
const costUSD = ( this . cost / 100 _000_000 * this . conversions . USD ) ;
if ( costUSD >= paymentMethod . min && costUSD <= paymentMethod . max ) {
return true ;
}
}
return false ;
}
2024-07-26 09:27:18 +00:00
get canPayWithBalance ( ) : boolean {
2024-07-02 12:20:14 +00:00
if ( ! this . hasAccessToBalanceMode ) {
2024-07-01 18:18:13 +09:00
return false ;
}
const paymentMethod = this . estimate ? . availablePaymentMethods ? . balance ;
2024-07-05 10:41:59 +00:00
return paymentMethod && this . cost >= paymentMethod . min && this . cost <= paymentMethod . max && this . cost <= this . estimate ? . userBalance ;
2024-06-29 07:04:08 +00:00
}
2024-07-26 09:27:18 +00:00
get canPay ( ) : boolean {
2024-07-26 09:06:47 +00:00
return this . canPayWithBalance || this . canPayWithBitcoin || this . canPayWithCashapp || this . canPayWithApplePay || this . canPayWithGooglePay ;
2024-06-28 07:02:12 +00:00
}
2024-07-26 09:27:18 +00:00
get hasAccessToBalanceMode ( ) : boolean {
2024-07-02 12:20:14 +00:00
return this . isLoggedIn ( ) && this . estimate ? . hasAccess ;
}
2024-07-06 08:24:18 +00:00
get timeSincePaid ( ) : number {
return Date . now ( ) - this . timePaid ;
}
2024-06-27 09:10:32 +00:00
@HostListener ( 'window:resize' , [ '$event' ] )
onResize ( ) : void {
this . isMobile = window . innerWidth <= 767.98 ;
}
2024-04-13 16:11:49 +09:00
}