From 49b9a6f53d5bf4015f4a96ade805e30951279621 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 13 Apr 2024 16:11:49 +0900 Subject: [PATCH 1/3] [accelerator] concept accelerator a/b ctas --- .../accelerate-checkout.component.html | 42 +++++++++++++++++++ .../accelerate-checkout.component.scss | 0 .../accelerate-checkout.component.ts | 17 ++++++++ frontend/src/app/shared/shared.module.ts | 3 ++ ...empool-accelerator-sparkles-compressed.svg | 1 + 5 files changed, 63 insertions(+) create mode 100644 frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html create mode 100644 frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss create mode 100644 frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts create mode 100644 frontend/src/resources/mempool-accelerator-sparkles-compressed.svg diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html new file mode 100644 index 000000000..6010fe47f --- /dev/null +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -0,0 +1,42 @@ +
+ +
+
+

Accelerate your Bitcoin transaction?

+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts new file mode 100644 index 000000000..0591e58a2 --- /dev/null +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; + +@Component({ + selector: 'app-accelerate-checkout', + templateUrl: './accelerate-checkout.component.html', + styleUrls: ['./accelerate-checkout.component.scss'] +}) +export class AccelerateCheckout implements OnInit, OnDestroy { + constructor() { + } + + ngOnInit() { + } + + ngOnDestroy() { + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 72d9eb04d..4c0acf222 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -96,6 +96,7 @@ import { MempoolErrorComponent } from './components/mempool-error/mempool-error. import { AccelerationsListComponent } from '../components/acceleration/accelerations-list/accelerations-list.component'; import { PendingStatsComponent } from '../components/acceleration/pending-stats/pending-stats.component'; import { AccelerationStatsComponent } from '../components/acceleration/acceleration-stats/acceleration-stats.component'; +import { AccelerateCheckout } from '../components/accelerate-checkout/accelerate-checkout.component'; import { BlockViewComponent } from '../components/block-view/block-view.component'; import { EightBlocksComponent } from '../components/eight-blocks/eight-blocks.component'; @@ -212,6 +213,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir MempoolErrorComponent, AccelerationsListComponent, AccelerationStatsComponent, + AccelerateCheckout, PendingStatsComponent, HttpErrorComponent, ], @@ -334,6 +336,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir MempoolErrorComponent, AccelerationsListComponent, AccelerationStatsComponent, + AccelerateCheckout, PendingStatsComponent, HttpErrorComponent, diff --git a/frontend/src/resources/mempool-accelerator-sparkles-compressed.svg b/frontend/src/resources/mempool-accelerator-sparkles-compressed.svg new file mode 100644 index 000000000..4a5bac3cc --- /dev/null +++ b/frontend/src/resources/mempool-accelerator-sparkles-compressed.svg @@ -0,0 +1 @@ + \ No newline at end of file From 24e9ae64403854dbc93a02c0535aa63f95bb8660 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 13 Apr 2024 20:53:19 +0900 Subject: [PATCH 2/3] [accelerator] re-integrate square payment WIP --- frontend/src/app/app-routing.module.ts | 9 + .../accelerate-checkout.component.html | 113 +++++++-- .../accelerate-checkout.component.scss | 3 + .../accelerate-checkout.component.ts | 223 +++++++++++++++++- .../src/app/components/time/time.component.ts | 32 ++- 5 files changed, 347 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 7ec9a37d3..51509309e 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -7,6 +7,7 @@ import { MempoolBlockViewComponent } from './components/mempool-block-view/mempo import { ClockComponent } from './components/clock/clock.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; import { AddressGroupComponent } from './components/address-group/address-group.component'; +import { AccelerateCheckout } from './components/accelerate-checkout/accelerate-checkout.component'; const browserWindow = window || {}; // @ts-ignore @@ -105,6 +106,14 @@ let routes: Routes = [ loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule), data: { preload: true }, }, + { + path: 'accelerate-checkout', + children: [], + component: AccelerateCheckout, + data: { + networkSpecific: true, + } + }, { path: 'wallet', children: [], diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index 6010fe47f..f19419aa8 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,42 +1,103 @@
-
-
-

Accelerate your Bitcoin transaction?

+ @if (!showCheckoutPage) { + +
+
+

Accelerate your Bitcoin transaction?

+
-
-
-
+ +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ +
+
+ + } + + @else { + +
-
- - +

Confirm your payment

+
+
+ +
+
+
+ Payment to mempool.space for acceleration of txid {{ txid.substr(0, 10) }}..{{ txid.substr(-10) }}
-
+ + @if (!loadingCashapp) { +
+
+
+ Total additional cost
+ + Pay + + with + +
+
+
+
+ } + +
-
- - +
+
+ @if (loadingCashapp) { +
+ }
-
-
- + +
+
+
+ Changed your mind? +
- + }
\ No newline at end of file diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss index e69de29bb..395b14d25 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.scss @@ -0,0 +1,3 @@ +.estimating { + color: var(--green) +} \ No newline at end of file diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 0591e58a2..1576c173b 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -1,4 +1,9 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +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'; @Component({ selector: 'app-accelerate-checkout', @@ -6,12 +11,224 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; styleUrls: ['./accelerate-checkout.component.scss'] }) export class AccelerateCheckout implements OnInit, OnDestroy { - constructor() { - } + @Input() eta: number = Date.now() + 123456789; + @Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad'; + + calculating = true; + choosenOption: 'wait' | 'accelerate' = 'wait'; + showCheckoutPage = false; + error = ''; + + // accelerator stuff + square: { appId: string, locationId: string}; + accelerationUUID: string; + estimateSubscription: Subscription; + cost: number; // sats + + // square + cashappSubmit: any; + payments: any; + cashAppPay: any; + cashAppSubscription: Subscription; + conversionsSubscription: Subscription; + loadingCashapp = true; + processingPayment = true; + + constructor( + private websocketService: WebsocketService, + private servicesApiService: ServicesApiServices, + private stateService: StateService + ) {} ngOnInit() { + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get('cash_request_id')) { // Redirected from cashapp + this.processingPayment = true; + window.scrollTo(0, 0); + } else { + this.servicesApiService.setupSquare$().subscribe(ids => { + this.square = { + appId: ids.squareAppId, + locationId: ids.squareLocationId + }; + this.estimate(); + }); + } } ngOnDestroy() { + 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 + const minExtraCost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee)); + const DEFAULT_BID_RATIO = 2; + this.cost = minExtraCost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee; + } + }), + + 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 { + 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() { + this.loadingCashapp = true; + + if (this.cashAppSubscription) { + this.cashAppSubscription.unsubscribe(); + } + if (this.conversionsSubscription) { + this.conversionsSubscription.unsubscribe(); + } + + this.conversionsSubscription = this.stateService.conversions$.subscribe( + async (conversions) => { + if (this.cashAppPay) { + this.cashAppPay.destroy(); + } + + const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`; + const costUSD = this.cost / 100_000_000 * conversions.USD; + 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, { + redirectURL: `${redirectHostname}/tracker/${this.txid}?acceleration=false`, + referenceId: `accelerator-${this.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, + button: { shape: 'semiround', size: 'small', theme: 'light'} + }); + this.cashappSubmit = await this.cashAppPay.CashAppPayInstance.render('#cash-app-pay', { button: { theme: 'light', size: 'small', shape: 'semiround' }, manage: 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.servicesApiService.accelerateWithCashApp$( + that.txid, + that.cost, + tokenResult.token, + tokenResult.details.cashAppPay.cashtag, + tokenResult.details.cashAppPay.referenceId, + that.accelerationUUID + ).subscribe({ + next: () => { + that.estimateSubscription.unsubscribe(); + }, + error: (response) => { + if (response.status === 403 && response.error === 'not_available') { + that.error = 'waitlisted'; + } else { + that.error = response.error; + } + } + }); + } + }); + this.loadingCashapp = false; + } + ); + } + submitCashappPay(): void { + if (this.cashappSubmit) { + this.cashappSubmit?.begin(); + this.processingPayment = true; + } + } + + /** + * UI events + */ + enableCheckoutPage() { + this.showCheckoutPage = true; + this.insertSquare(); + this.setupSquare(); + } + selectedOptionChanged(event) { + this.choosenOption = event.target.id; + } + restart() { + this.showCheckoutPage = false + this.choosenOption = 'wait'; } } diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts index 45070ad67..6ed3d8cf6 100644 --- a/frontend/src/app/components/time/time.component.ts +++ b/frontend/src/app/components/time/time.component.ts @@ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { @Input() time: number; @Input() dateString: number; - @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain'; + @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within' = 'plain'; @Input() fastRender = false; @Input() fixedRender = false; @Input() relative = false; @@ -80,6 +80,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { seconds = Math.floor((+new Date() - +new Date(this.dateString || this.time * 1000)) / 1000); break; case 'until': + case 'within': seconds = (+new Date(this.time) - +new Date()) / 1000; break; default: @@ -91,7 +92,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { } else if (seconds < 60) { if (this.relative || this.kind === 'since') { return $localize`:@@date-base.just-now:Just now`; - } else if (this.kind === 'until') { + } else if (this.kind === 'until' || this.kind === 'within') { seconds = 60; } } @@ -112,12 +113,12 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { if (counter > 0) { let rounded; const roundFactor = Math.pow(10,this.fractionDigits || 0); - if (this.kind === 'until' && usedUnits < this.numUnits) { + if ((this.kind === 'until' || this.kind === 'within') && usedUnits < this.numUnits) { rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; } else { rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor; } - if (this.kind !== 'until' || this.numUnits === 1) { + if ((this.kind !== 'until' && this.kind !== 'within')|| this.numUnits === 1) { return this.formatTime(this.kind, precisionUnit, rounded); } else { if (!usedUnits) { @@ -185,6 +186,29 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { } } break; + case 'within': + if (number === 1) { + switch (unit) { // singular (In ~1 day) + case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break; + case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break; + case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break; + case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break; + case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break; + case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`; + case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`; + } + } else { + switch (unit) { // plural (In ~2 days) + case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break; + case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break; + case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break; + case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break; + case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break; + case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break; + case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break; + } + } + break; case 'span': if (number === 1) { switch (unit) { // singular (1 day) From fb086b5ad5789038e10c6f900c19b95e34fe1610 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 13 Apr 2024 23:07:19 +0900 Subject: [PATCH 3/3] [accelerator] polish UI prepaid accel --- .../accelerate-checkout.component.html | 218 +++++++++++------- .../accelerate-checkout.component.ts | 89 ++++--- .../accelerate-preview.component.ts | 1 - .../components/tracker/tracker.component.html | 2 +- .../components/tracker/tracker.component.ts | 5 + .../src/app/services/services-api.service.ts | 4 +- 6 files changed, 192 insertions(+), 127 deletions(-) diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html index 28e0a5832..5a73a48cf 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.html @@ -1,103 +1,147 @@
- @if (!showCheckoutPage) { - -
-
-

Accelerate your Bitcoin transaction?

-
-
+ @if (error) { + + } @else { -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-
-
- -
-
-
- } - - @else { - -
-
-

Confirm your payment

-
-
- -
-
-
- Payment to mempool.space for acceleration of txid {{ txid.substr(0, 10) }}..{{ txid.substr(-10) }} -
-
-
- - @if (!loadingCashapp) { -
+ @if (step === 'completed') { +
- Total additional cost
- - Pay - - with - -
+
+
Transaction is now being accelerated!
+
} -
-
-
-
- @if (loadingCashapp) { -
- } + @else if (step === 'cta') { + +
+
+

Accelerate your Bitcoin transaction?

-
-
-
-
- Changed your mind? - +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ } + + @else if (step === 'checkout') { + +
+
+

Confirm your payment

+
-
- } +
+
+
+ Payment to mempool.space for acceleration of txid {{ txid.substr(0, 10) }}..{{ txid.substr(-10) }} +
+
+
+ + @if (!loadingCashapp) { +
+
+
+ Total additional cost
+ + Pay + + with + +
+
+
+
+ } + +
+
+
+
+ @if (loadingCashapp) { +
+ Loading payment method... +
+
+ } +
+
+
+ +
+
+
+ Changed your mind? + +
+
+ } + + @else if (step === 'processing') { +
+
+

Confirm your payment

+
+
+ + +
+ +
+
+
+
+ We are processing your payment... +
+
+
+
+
+ } + + + } +
diff --git a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts index 0aef93814..622256461 100644 --- a/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts +++ b/frontend/src/app/components/accelerate-checkout/accelerate-checkout.component.ts @@ -1,9 +1,10 @@ -import { Component, OnInit, OnDestroy, Output, EventEmitter, Input } from '@angular/core'; +import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef } from '@angular/core'; 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'; +import { AudioService } from '../../services/audio.service'; @Component({ selector: 'app-accelerate-checkout', @@ -13,47 +14,56 @@ import { StateService } from '../../services/state.service'; export class AccelerateCheckout implements OnInit, OnDestroy { @Input() eta: number = Date.now() + 123456789; @Input() txid: string = '70c18d76cdb285a1b5bd87fdaae165880afa189809c30b4083ff7c0e69ee09ad'; + @Output() close = new EventEmitter(); calculating = true; choosenOption: 'wait' | 'accelerate' = 'wait'; - showCheckoutPage = false; error = ''; // accelerator stuff square: { appId: string, locationId: string}; accelerationUUID: string; estimateSubscription: Subscription; + maxBidBoost: number; // sats cost: number; // sats // square + loadingCashapp = false; cashappSubmit: any; payments: any; cashAppPay: any; cashAppSubscription: Subscription; conversionsSubscription: Subscription; - loadingCashapp = true; - processingPayment = true; + step: 'cta' | 'checkout' | 'processing' | 'completed' = 'completed'; constructor( private websocketService: WebsocketService, private servicesApiService: ServicesApiServices, - private stateService: StateService - ) {} + private stateService: StateService, + private audioService: AudioService, + private cd: ChangeDetectorRef + ) { + this.accelerationUUID = window.crypto.randomUUID(); + } ngOnInit() { const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('cash_request_id')) { // Redirected from cashapp - this.processingPayment = true; - window.scrollTo(0, 0); - } else { - this.servicesApiService.setupSquare$().subscribe(ids => { - this.square = { - appId: ids.squareAppId, - locationId: ids.squareLocationId - }; - this.estimate(); - }); + this.insertSquare(); + this.setupSquare(); + this.step = 'processing'; } + + this.servicesApiService.setupSquare$().subscribe(ids => { + this.square = { + appId: ids.squareAppId, + locationId: ids.squareLocationId + }; + if (this.step === 'cta') { + this.estimate(); + } + }); + } ngOnDestroy() { @@ -82,9 +92,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { return; } // Make min extra fee at least 50% of the current tx fee - const minExtraCost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee)); + const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee)); const DEFAULT_BID_RATIO = 2; - this.cost = minExtraCost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee; + this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO; + this.cost = this.maxBidBoost * DEFAULT_BID_RATIO + estimation.mempoolBaseFee + estimation.vsizeFee; } }), @@ -143,8 +154,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } } async requestCashAppPayment() { - this.loadingCashapp = true; - if (this.cashAppSubscription) { this.cashAppSubscription.unsubscribe(); } @@ -155,11 +164,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy { this.conversionsSubscription = this.stateService.conversions$.subscribe( async (conversions) => { if (this.cashAppPay) { - this.cashAppPay.destroy(); + await this.cashAppPay.destroy(); } const redirectHostname = document.location.hostname === 'localhost' ? `http://localhost:4200`: `https://${document.location.hostname}`; - const costUSD = this.cost / 100_000_000 * conversions.USD; + 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 const paymentRequest = this.payments.paymentRequest({ countryCode: 'US', currencyCode: 'USD', @@ -172,12 +181,16 @@ export class AccelerateCheckout implements OnInit, OnDestroy { button: { shape: 'semiround', size: 'small', theme: 'light'} }); this.cashAppPay = await this.payments.cashAppPay(paymentRequest, { - redirectURL: `${redirectHostname}/tracker/${this.txid}?acceleration=false`, + redirectURL: `${redirectHostname}/tracker/${this.txid}`, referenceId: `accelerator-${this.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`, button: { shape: 'semiround', size: 'small', theme: 'light'} }); - this.cashappSubmit = await this.cashAppPay.CashAppPayInstance.render('#cash-app-pay', { button: { theme: 'light', size: 'small', shape: 'semiround' }, manage: false }); - + + if (this.step === 'checkout') { + await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' }) + } + this.loadingCashapp = false; + const that = this; this.cashAppPay.addEventListener('ontokenization', function (event) { const { tokenResult, error } = event.detail; @@ -186,14 +199,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy { } else if (tokenResult.status === 'OK') { that.servicesApiService.accelerateWithCashApp$( that.txid, - that.cost, tokenResult.token, tokenResult.details.cashAppPay.cashtag, tokenResult.details.cashAppPay.referenceId, that.accelerationUUID ).subscribe({ next: () => { - that.estimateSubscription.unsubscribe(); + that.audioService.playSound('ascend-chime-cartoon'); + that.step = 'completed'; + setTimeout(() => { + that.closeModal(); + }, 10000); }, error: (response) => { if (response.status === 403 && response.error === 'not_available') { @@ -205,33 +221,34 @@ export class AccelerateCheckout implements OnInit, OnDestroy { }); } }); - this.loadingCashapp = false; } ); } - submitCashappPay(): void { - if (this.cashappSubmit) { - this.cashappSubmit?.begin(); - this.processingPayment = true; - } - } /** * UI events */ enableCheckoutPage() { - this.showCheckoutPage = true; + this.step = 'checkout'; + this.loadingCashapp = true; this.insertSquare(); this.setupSquare(); } selectedOptionChanged(event) { this.choosenOption = event.target.id; + if (this.choosenOption === 'wait') { + this.restart(); + this.closeModal(); + } } restart() { - this.showCheckoutPage = false + this.step = 'cta'; this.choosenOption = 'wait'; } closeModal(): void { + if (this.cashAppPay) { + this.cashAppPay.destroy(); + } this.close.emit(); } } diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index ca4e61c06..ec36107a4 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -362,7 +362,6 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$( that.tx.txid, - that.userBid, tokenResult.token, tokenResult.details.cashAppPay.cashtag, tokenResult.details.cashAppPay.referenceId, diff --git a/frontend/src/app/components/tracker/tracker.component.html b/frontend/src/app/components/tracker/tracker.component.html index 3e2eed5d2..9f1388d22 100644 --- a/frontend/src/app/components/tracker/tracker.component.html +++ b/frontend/src/app/components/tracker/tracker.component.html @@ -48,7 +48,7 @@ } @if (isMobile && paymentType === 'cashapp' && accelerationEligible && !tx.acceleration && acceleratorAvailable && accelerateCtaType === 'button' && !tx?.acceleration) { - Accelerate + Accelerate }
diff --git a/frontend/src/app/components/tracker/tracker.component.ts b/frontend/src/app/components/tracker/tracker.component.ts index 1a33eae66..03f7d3f0c 100644 --- a/frontend/src/app/components/tracker/tracker.component.ts +++ b/frontend/src/app/components/tracker/tracker.component.ts @@ -146,6 +146,11 @@ export class TrackerComponent implements OnInit, OnDestroy { if (this.acceleratorAvailable && this.stateService.ref === 'https://cash.app/') { this.paymentType = 'cashapp'; } + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get('cash_request_id')) { + this.showAccelerationSummary = true; + } + this.showAccelerationSummary = true; this.enterpriseService.page(); diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index 44d253efa..89ea9a603 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -132,8 +132,8 @@ export class ServicesApiServices { return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid, accelerationUUID: accelerationUUID }); } - accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string, accelerationUUID: string) { - return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID }); + accelerateWithCashApp$(txInput: string, token: string, cashtag: string, referenceId: string, accelerationUUID: string) { + return this.httpClient.post(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID }); } getAccelerations$(): Observable {