Merge branch 'master' into nymkappa/fido
This commit is contained in:
		
						commit
						a2009aa322
					
				| @ -45,6 +45,7 @@ __SERVICES_API__=${SERVICES_API:=https://mempool.space/api/v1/services} | ||||
| __PUBLIC_ACCELERATIONS__=${PUBLIC_ACCELERATIONS:=false} | ||||
| __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true} | ||||
| __ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false} | ||||
| __STRATUM_ENABLED__=${STRATUM_ENABLED:=false} | ||||
| 
 | ||||
| # Export as environment variables to be used by envsubst | ||||
| export __MAINNET_ENABLED__ | ||||
| @ -76,6 +77,7 @@ export __SERVICES_API__ | ||||
| export __PUBLIC_ACCELERATIONS__ | ||||
| export __HISTORICAL_PRICE__ | ||||
| export __ADDITIONAL_CURRENCIES__ | ||||
| export __STRATUM_ENABLED__ | ||||
| 
 | ||||
| folder=$(find /var/www/mempool -name "config.js" | xargs dirname) | ||||
| echo ${folder} | ||||
|  | ||||
| @ -397,13 +397,13 @@ | ||||
|                   </div> | ||||
|                 } | ||||
|               </div> | ||||
|               @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { | ||||
|               @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) { | ||||
|                 <div class="col-sm text-center flex-grow-0  d-flex flex-column justify-content-center align-items-center"> | ||||
|                   <p class="text-nowrap">—<span i18n="or">OR</span>—</p> | ||||
|                 </div> | ||||
|               } | ||||
|             } | ||||
|             @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { | ||||
|             @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay || canPayWithCardOnFile) { | ||||
|               <div class="col-sm text-center d-flex flex-column justify-content-center align-items-center"> | ||||
|                 <p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p> | ||||
|                 @if (canPayWithCashapp) { | ||||
| @ -421,6 +421,13 @@ | ||||
|                     <img src="/resources/google-pay.png" height=37> | ||||
|                   </div> | ||||
|                 } | ||||
|                 @if (canPayWithCardOnFile) { | ||||
|                   @if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) { <span class="mt-1 mb-1"></span> } | ||||
|                   <div class="paymentMethod mx-2 d-flex justify-content-center align-items-center" style="width: 200px; height: 55px" (click)="moveToStep('cardonfile')"> | ||||
|                     <fa-icon style="font-size: 24px; color: white" [icon]="['fas', 'credit-card']"></fa-icon> | ||||
|                     <span class="ml-2" style="font-size: 22px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.brand }} {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span> | ||||
|                   </div> | ||||
|                 } | ||||
|               </div> | ||||
|             } | ||||
|           </div> | ||||
| @ -443,7 +450,7 @@ | ||||
|         <button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button> | ||||
|       </div> | ||||
|     </div> | ||||
|   } @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay') { | ||||
|   } @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay' || step === 'cardonfile') { | ||||
|     <!-- Show checkout page --> | ||||
|     <div class="row mb-md-1 text-center" id="confirm-title"> | ||||
|       <div class="col-sm" id="confirm-payment-title"> | ||||
| @ -459,7 +466,7 @@ | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     @if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay) { | ||||
|     @if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay || step === 'cardonfile' && !loadingCardOnFile) { | ||||
|       <div class="row text-center mt-1"> | ||||
|         <div class="col-sm"> | ||||
|           <div class="form-group w-100"> | ||||
| @ -484,8 +491,13 @@ | ||||
|             <div id="cash-app-pay" class="d-inline-block" style="height: 50px" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div> | ||||
|           } @else if (step === 'googlepay') { | ||||
|             <div id="google-pay-button" class="d-inline-block" style="height: 50px" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div> | ||||
|           } @else if (step === 'cardonfile') { | ||||
|             <div class="paymentMethod mx-2 d-flex justify-content-center align-items-center ml-auto mr-auto" style="width: 200px; height: 55px" (click)="requestCardOnFilePayment()" [style]="loadingCardOnFile ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"> | ||||
|               <fa-icon style="font-size: 24px; color: white" [icon]="['fas', 'credit-card']"></fa-icon> | ||||
|               <span class="ml-2" style="font-size: 22px">{{ estimate?.availablePaymentMethods?.cardOnFile?.card?.brand }} {{ estimate?.availablePaymentMethods?.cardOnFile?.card?.last_4 }}</span> | ||||
|             </div> | ||||
|           } | ||||
|           @if (loadingCashapp || loadingApplePay || loadingGooglePay) { | ||||
|           @if (loadingCashapp || loadingApplePay || loadingGooglePay || loadingCardOnFile) { | ||||
|           <div display="d-flex flex-row justify-content-center"> | ||||
|             <span i18n="accelerator.loading-payment-method">Loading payment method...</span> | ||||
|             <div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div> | ||||
|  | ||||
| @ -13,7 +13,7 @@ import { EnterpriseService } from '@app/services/enterprise.service'; | ||||
| import { ApiService } from '@app/services/api.service'; | ||||
| import { isDevMode } from '@angular/core'; | ||||
| 
 | ||||
| export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay'; | ||||
| export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile'; | ||||
| 
 | ||||
| export type AccelerationEstimate = { | ||||
|   hasAccess: boolean; | ||||
| @ -26,7 +26,7 @@ export type AccelerationEstimate = { | ||||
|   mempoolBaseFee: number; | ||||
|   vsizeFee: number; | ||||
|   pools: number[]; | ||||
|   availablePaymentMethods: Record<PaymentMethod, {min: number, max: number}>; | ||||
|   availablePaymentMethods: Record<PaymentMethod, {min: number, max: number, card?: {card_id: string, last_4: string, brand: string, name: string, billing: any}}>; | ||||
|   unavailable?: boolean; | ||||
|   options: { // recommended bid options
 | ||||
|     fee: number; // recommended userBid in sats
 | ||||
| @ -49,7 +49,7 @@ export const MIN_BID_RATIO = 1; | ||||
| export const DEFAULT_BID_RATIO = 2; | ||||
| export const MAX_BID_RATIO = 4; | ||||
| 
 | ||||
| type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'processing' | 'paid' | 'success'; | ||||
| type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'cardonfile' | 'processing' | 'paid' | 'success'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-accelerate-checkout', | ||||
| @ -65,6 +65,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|   @Input() cashappEnabled: boolean = true; | ||||
|   @Input() applePayEnabled: boolean = false; | ||||
|   @Input() googlePayEnabled: boolean = true; | ||||
|   @Input() cardOnFileEnabled: boolean = true; | ||||
|   @Input() advancedEnabled: boolean = false; | ||||
|   @Input() forceMobile: boolean = false; | ||||
|   @Input() showDetails: boolean = false; | ||||
| @ -117,6 +118,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|   loadingCashapp = false; | ||||
|   loadingApplePay = false; | ||||
|   loadingGooglePay = false; | ||||
|   loadingCardOnFile = false; | ||||
|   payments: any; | ||||
|   cashAppPay: any; | ||||
|   applePay: any; | ||||
| @ -234,6 +236,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|       this.loadingGooglePay = true; | ||||
|       this.setupSquare(); | ||||
|       this.scrollToElementWithTimeout('confirm-title', 'center', 100); | ||||
|     } else if (this._step === 'cardonfile' && this.cardOnFileEnabled) { | ||||
|       this.loadingCardOnFile = true; | ||||
|       this.setupSquare(); | ||||
|       this.scrollToElementWithTimeout('confirm-title', 'center', 100); | ||||
|     } else if (this._step === 'paid') { | ||||
|       this.timePaid = Date.now(); | ||||
|       this.timeoutTimer = setTimeout(() => { | ||||
| @ -454,6 +460,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|             await this.requestApplePayPayment(); | ||||
|           } else if (this._step === 'googlepay') { | ||||
|             await this.requestGooglePayPayment(); | ||||
|           } else if (this._step === 'cardonfile') { | ||||
|             this.loadingCardOnFile = false; | ||||
|           } | ||||
|         }, | ||||
|         error: () => { | ||||
| @ -710,6 +718,109 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 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, | ||||
|                 addressLines: [cardOnFile.card.billing.addressLine1], | ||||
|                 city: cardOnFile.card.billing.locality, | ||||
|                 state: cardOnFile.card.billing.administrativeDistrictLevel1, | ||||
|                 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--; | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * CASHAPP | ||||
|    */ | ||||
| @ -955,6 +1066,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   get canPayWithCardOnFile(): boolean { | ||||
|     if (!this.cardOnFileEnabled || !this.conversions || (!this.isProdDomain && !isDevMode())) { | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     const paymentMethod = this.estimate?.availablePaymentMethods?.cardOnFile; | ||||
|     if (paymentMethod) { | ||||
|       const costUSD = (this.cost / 100_000_000 * this.conversions.USD); | ||||
|       if (costUSD >= paymentMethod.min && costUSD <= paymentMethod.max) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   get canPayWithBalance(): boolean { | ||||
|     if (!this.hasAccessToBalanceMode) { | ||||
|       return false; | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|           <th class="time text-right" i18n="accelerator.requested">Requested</th> | ||||
|         </ng-container> | ||||
|         <ng-container *ngIf="!pending"> | ||||
|           <th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th> | ||||
|           <th class="fee text-right text-truncate" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th> | ||||
|           <th class="block text-right" i18n="shared.block-title">Block</th> | ||||
|           <th class="pool text-right" i18n="mining.pool-name" *ngIf="!this.widget">Pool</th> | ||||
|           <th class="status text-right" i18n="transaction.status|Transaction Status">Status</th> | ||||
|  | ||||
| @ -146,6 +146,10 @@ export class ServicesApiServices { | ||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged }); | ||||
|   } | ||||
| 
 | ||||
|   accelerateWithCardOnFile$(txInput: string, token: string, verificationToken: string, referenceId: string, userApprovedUSD: number, userChallenged: boolean) { | ||||
|     return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cardOnFile`, { txInput: txInput, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD, userChallenged: userChallenged }); | ||||
|   } | ||||
| 
 | ||||
|   getAccelerations$(): Observable<Acceleration[]> { | ||||
|     return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`); | ||||
|   } | ||||
|  | ||||
| @ -7,7 +7,7 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa | ||||
|   faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, | ||||
|   faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, | ||||
|   faCircleCheck, faUserCircle, faCheck, faRocket, faScaleBalanced, faHourglassStart, faHourglassHalf, faHourglassEnd, faWandMagicSparkles, faFaucetDrip, faTimeline, | ||||
|   faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes } from '@fortawesome/free-solid-svg-icons'; | ||||
|   faCircleXmark, faCalendarCheck, faMoneyBillTrendUp, faRobot, faShareNodes, faCreditCard } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { InfiniteScrollModule } from 'ngx-infinite-scroll'; | ||||
| import { MenuComponent } from '@components/menu/menu.component'; | ||||
| import { PreviewTitleComponent } from '@components/master-page-preview/preview-title.component'; | ||||
| @ -463,5 +463,6 @@ export class SharedModule { | ||||
|     library.addIcons(faMoneyBillTrendUp); | ||||
|     library.addIcons(faRobot); | ||||
|     library.addIcons(faShareNodes); | ||||
|     library.addIcons(faCreditCard); | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user