2015-12-25 22:42:14 +01:00
/ *
This file is part of TALER
( C ) 2015 GNUnet e . V .
TALER is free software ; you can redistribute it and / or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation ; either version 3 , or ( at your option ) any later version .
TALER is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License along with
2016-07-07 17:59:29 +02:00
TALER ; see the file COPYING . If not , see < http : / / www.gnu.org / licenses / >
2015-12-25 22:42:14 +01:00
* /
2016-03-01 19:46:20 +01:00
/ * *
* Page shown to the user to confirm entering
* a contract .
* /
2016-11-13 23:30:18 +01:00
2017-05-24 16:14:23 +02:00
/ * *
* Imports .
* /
2017-05-28 23:15:41 +02:00
import * as i18n from "../../i18n" ;
2018-01-03 14:42:06 +01:00
2017-05-29 16:27:53 +02:00
import {
ExchangeRecord ,
2018-01-17 03:49:54 +01:00
ProposalDownloadRecord ,
2018-01-03 14:42:06 +01:00
} from "../../dbTypes" ;
import { ContractTerms } from "../../talerTypes" ;
import {
CheckPayResult ,
} from "../../walletTypes" ;
2017-05-28 23:15:41 +02:00
2017-08-30 17:08:54 +02:00
import { renderAmount } from "../renderHtml" ;
2017-05-29 16:27:53 +02:00
import * as wxApi from "../wxApi" ;
2017-05-28 23:15:41 +02:00
2017-04-20 03:09:25 +02:00
import * as React from "react" ;
import * as ReactDOM from "react-dom" ;
import URI = require ( "urijs" ) ;
2015-12-16 10:45:16 +01:00
2016-10-05 17:04:57 +02:00
interface DetailState {
collapsed : boolean ;
}
interface DetailProps {
2017-06-01 18:46:07 +02:00
contractTerms : ContractTerms ;
2017-05-29 16:58:03 +02:00
collapsed : boolean ;
2016-11-15 15:07:17 +01:00
exchanges : null | ExchangeRecord [ ] ;
2016-10-05 17:04:57 +02:00
}
2016-11-13 08:16:12 +01:00
class Details extends React . Component < DetailProps , DetailState > {
2016-10-19 20:16:01 +02:00
constructor ( props : DetailProps ) {
super ( props ) ;
2016-11-13 10:17:39 +01:00
console . log ( "new Details component created" ) ;
this . state = {
2016-10-19 20:16:01 +02:00
collapsed : props.collapsed ,
2016-11-13 10:17:39 +01:00
} ;
2016-10-19 23:27:46 +02:00
console . log ( "initial state:" , this . state ) ;
2016-10-05 17:04:57 +02:00
}
2016-11-13 08:16:12 +01:00
render() {
if ( this . state . collapsed ) {
2016-10-05 17:38:02 +02:00
return (
< div >
< button className = "linky"
2017-05-29 16:58:03 +02:00
onClick = { ( ) = > { this . setState ( { collapsed : false } as any ) ; } } >
2016-11-23 01:14:45 +01:00
< i18n.Translate wrap = "span" >
2016-10-05 17:38:02 +02:00
show more details
2016-11-23 01:14:45 +01:00
< / i18n.Translate >
2016-10-05 17:38:02 +02:00
< / button >
< / div >
) ;
2016-03-02 02:16:18 +01:00
} else {
2016-10-07 17:10:22 +02:00
return (
< div >
< button className = "linky"
2016-10-19 20:16:01 +02:00
onClick = { ( ) = > this . setState ( { collapsed : true } as any ) } >
2016-10-07 17:10:22 +02:00
show less details
< / button >
< div >
2016-11-27 22:13:24 +01:00
{ i18n . str ` Accepted exchanges: ` }
2016-10-07 17:10:22 +02:00
< ul >
2017-06-01 18:46:07 +02:00
{ this . props . contractTerms . exchanges . map (
2017-05-29 16:58:03 +02:00
( e ) = > < li > { ` ${ e . url } : ${ e . master_pub } ` } < / li > ) }
2016-10-07 17:10:22 +02:00
< / ul >
2016-11-27 22:13:24 +01:00
{ i18n . str ` Exchanges in the wallet: ` }
2016-10-19 20:16:01 +02:00
< ul >
2016-11-14 03:54:51 +01:00
{ ( this . props . exchanges || [ ] ) . map (
2016-11-15 15:07:17 +01:00
( e : ExchangeRecord ) = >
2016-10-19 20:16:01 +02:00
< li > { ` ${ e . baseUrl } : ${ e . masterPublicKey } ` } < / li > ) }
< / ul >
2016-10-07 17:10:22 +02:00
< / div >
< / div > ) ;
2016-03-02 02:16:18 +01:00
}
}
2016-10-05 17:04:57 +02:00
}
2016-03-02 02:16:18 +01:00
2016-10-05 17:04:57 +02:00
interface ContractPromptProps {
2018-01-17 03:49:54 +01:00
proposalId? : number ;
contractUrl? : string ;
sessionId? : string ;
2016-10-05 17:04:57 +02:00
}
interface ContractPromptState {
2018-01-17 03:49:54 +01:00
proposalId : number | undefined ;
proposal : ProposalDownloadRecord | null ;
error : string | null ;
2016-10-05 17:04:57 +02:00
payDisabled : boolean ;
2017-06-02 02:51:17 +02:00
alreadyPaid : boolean ;
2016-11-15 15:07:17 +01:00
exchanges : null | ExchangeRecord [ ] ;
2017-06-04 17:56:55 +02:00
/ * *
* Don ' t request updates to proposal state while
* this is set to true , to avoid UI flickering
* when pressing pay .
* /
holdCheck : boolean ;
2017-08-30 17:08:54 +02:00
payStatus? : CheckPayResult ;
2016-10-05 17:04:57 +02:00
}
2016-11-13 08:16:12 +01:00
class ContractPrompt extends React . Component < ContractPromptProps , ContractPromptState > {
2017-12-10 23:02:00 +01:00
constructor ( props : ContractPromptProps ) {
super ( props ) ;
2016-10-05 17:04:57 +02:00
this . state = {
2017-06-03 21:06:23 +02:00
alreadyPaid : false ,
2016-10-05 17:04:57 +02:00
error : null ,
2017-05-29 16:58:03 +02:00
exchanges : null ,
2017-06-04 17:56:55 +02:00
holdCheck : false ,
2017-10-15 19:28:35 +02:00
payDisabled : true ,
proposal : null ,
2018-01-17 03:49:54 +01:00
proposalId : props.proposalId ,
2017-05-29 16:58:03 +02:00
} ;
2016-10-05 17:04:57 +02:00
}
2016-01-26 17:21:17 +01:00
2016-10-05 17:04:57 +02:00
componentWillMount() {
2016-11-13 10:17:39 +01:00
this . update ( ) ;
2016-10-05 17:04:57 +02:00
}
componentWillUnmount() {
// FIXME: abort running ops
}
2016-01-26 17:21:17 +01:00
2016-11-13 10:17:39 +01:00
async update() {
2018-01-17 03:49:54 +01:00
let proposalId = this . props . proposalId ;
if ( proposalId === undefined ) {
if ( this . props . contractUrl === undefined ) {
// Nothing we can do ...
return ;
}
proposalId = await wxApi . downloadProposal ( this . props . contractUrl ) ;
}
const proposal = await wxApi . getProposal ( proposalId ) ;
this . setState ( { proposal , proposalId } ) ;
2016-11-13 10:17:39 +01:00
this . checkPayment ( ) ;
2017-05-29 16:58:03 +02:00
const exchanges = await wxApi . getExchanges ( ) ;
2018-01-17 03:49:54 +01:00
this . setState ( { exchanges } ) ;
2016-11-13 10:17:39 +01:00
}
2017-05-29 16:27:53 +02:00
async checkPayment() {
2017-06-04 17:56:55 +02:00
window . setTimeout ( ( ) = > this . checkPayment ( ) , 500 ) ;
if ( this . state . holdCheck ) {
return ;
}
2018-01-17 03:49:54 +01:00
const proposalId = this . state . proposalId ;
if ( proposalId === undefined ) {
return ;
}
const payStatus = await wxApi . checkPay ( proposalId ) ;
2017-08-30 17:08:54 +02:00
if ( payStatus . status === "insufficient-balance" ) {
2017-05-29 16:58:03 +02:00
const msgInsufficient = i18n . str ` You have insufficient funds of the requested currency in your wallet. ` ;
// tslint:disable-next-line:max-line-length
const msgNoMatch = i18n . str ` You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet. ` ;
2017-05-31 16:04:14 +02:00
if ( this . state . exchanges && this . state . proposal ) {
const acceptedExchangePubs = this . state . proposal . contractTerms . exchanges . map ( ( e ) = > e . master_pub ) ;
2017-05-29 16:58:03 +02:00
const ex = this . state . exchanges . find ( ( e ) = > acceptedExchangePubs . indexOf ( e . masterPublicKey ) >= 0 ) ;
2017-05-29 16:27:53 +02:00
if ( ex ) {
2018-01-17 03:49:54 +01:00
this . setState ( { error : msgInsufficient } ) ;
2017-05-29 16:27:53 +02:00
} else {
2018-01-17 03:49:54 +01:00
this . setState ( { error : msgNoMatch } ) ;
2016-04-27 06:03:04 +02:00
}
} else {
2018-01-17 03:49:54 +01:00
this . setState ( { error : msgInsufficient } ) ;
2016-04-27 06:03:04 +02:00
}
2018-01-17 03:49:54 +01:00
this . setState ( { payDisabled : true } ) ;
2017-08-30 17:08:54 +02:00
} else if ( payStatus . status === "paid" ) {
2018-01-17 03:49:54 +01:00
this . setState ( { alreadyPaid : true , payDisabled : false , error : null , payStatus } ) ;
2017-05-29 16:27:53 +02:00
} else {
2018-01-17 03:49:54 +01:00
this . setState ( { payDisabled : false , error : null , payStatus } ) ;
2017-05-29 16:27:53 +02:00
}
2016-04-27 06:03:04 +02:00
}
2017-05-29 16:27:53 +02:00
async doPayment() {
2017-05-31 16:04:14 +02:00
const proposal = this . state . proposal ;
2017-06-04 17:56:55 +02:00
this . setState ( { holdCheck : true } ) ;
2017-05-31 16:04:14 +02:00
if ( ! proposal ) {
2017-05-29 16:27:53 +02:00
return ;
}
2018-01-17 03:49:54 +01:00
const proposalId = proposal . id ;
if ( proposalId === undefined ) {
console . error ( "proposal has no id" ) ;
return ;
2017-05-29 16:27:53 +02:00
}
2018-01-17 03:49:54 +01:00
const payResult = await wxApi . confirmPay ( proposalId , this . props . sessionId ) ;
document . location . href = payResult . nextUrl ;
this . setState ( { holdCheck : true } ) ;
2016-01-26 17:21:17 +01:00
}
2016-10-05 17:04:57 +02:00
2016-11-13 08:16:12 +01:00
render() {
2018-01-17 03:49:54 +01:00
if ( this . props . contractUrl === undefined && this . props . proposalId === undefined ) {
return < span > Error : either contractUrl or proposalId must be given < / span > ;
}
if ( this . state . proposalId === undefined ) {
return < span > Downloading contract terms < / span > ;
}
2017-05-31 16:04:14 +02:00
if ( ! this . state . proposal ) {
2016-11-13 10:17:39 +01:00
return < span > . . . < / span > ;
}
2017-05-31 16:04:14 +02:00
const c = this . state . proposal . contractTerms ;
2017-08-30 17:08:54 +02:00
let merchantName ;
if ( c . merchant && c . merchant . name ) {
merchantName = < strong > { c . merchant . name } < / strong > ;
} else {
merchantName = < strong > ( pub : { c . merchant_pub } ) < / strong > ;
}
const amount = < strong > { renderAmount ( c . amount ) } < / strong > ;
console . log ( "payStatus" , this . state . payStatus ) ;
2016-10-07 17:10:22 +02:00
return (
< div >
2016-11-13 10:17:39 +01:00
< div >
2017-08-30 17:08:54 +02:00
< i18n.Translate wrap = "p" >
The merchant < span > { merchantName } < / span > { " " }
offers you to purchase :
< / i18n.Translate >
< ul >
{ c . products . map (
( p : any , i : number ) = > ( < li key = { i } > { p . description } : { renderAmount ( p . price ) } < / li > ) )
}
< / ul >
2017-10-15 19:28:35 +02:00
{ ( this . state . payStatus && this . state . payStatus . coinSelection )
? < p >
The total price is < span > { amount } < / span > { " " }
( plus < span > { renderAmount ( this . state . payStatus . coinSelection . totalFees ) } < / span > fees ) .
< / p >
2017-08-30 17:08:54 +02:00
:
< p > The total price is < span > { amount } < / span > . < / p >
}
2016-11-13 10:17:39 +01:00
< / div >
2017-08-30 17:08:54 +02:00
< button className = "pure-button button-success"
2016-11-13 08:16:12 +01:00
disabled = { this . state . payDisabled }
2017-08-30 17:08:54 +02:00
onClick = { ( ) = > this . doPayment ( ) } >
{ i18n . str ` Confirm payment ` }
2016-10-07 17:10:22 +02:00
< / button >
2016-11-13 10:17:39 +01:00
< div >
2017-10-15 19:28:35 +02:00
{ ( this . state . alreadyPaid
? < p className = "okaybox" >
You already paid for this , clicking "Confirm payment" will not cost money again .
< / p >
: < p / > ) }
2016-11-13 10:17:39 +01:00
{ ( this . state . error ? < p className = "errorbox" > { this . state . error } < / p > : < p / > ) }
< / div >
2017-06-01 18:46:07 +02:00
< Details exchanges = { this . state . exchanges } contractTerms = { c } collapsed = { ! this . state . error } / >
2016-10-07 17:10:22 +02:00
< / div >
2016-10-05 17:04:57 +02:00
) ;
}
}
2017-04-20 03:09:25 +02:00
document . addEventListener ( "DOMContentLoaded" , ( ) = > {
2017-05-29 16:58:03 +02:00
const url = new URI ( document . location . href ) ;
const query : any = URI . parseQuery ( url . query ( ) ) ;
2016-10-05 17:04:57 +02:00
2018-01-17 03:49:54 +01:00
let proposalId ;
try {
proposalId = JSON . parse ( query . proposalId ) ;
} catch {
// ignore error
}
const sessionId = query . sessionId ;
const contractUrl = query . contractUrl ;
ReactDOM . render (
< ContractPrompt { ... { proposalId , contractUrl , sessionId }} / > ,
document . getElementById ( "contract" ) ! ) ;
2017-04-20 03:09:25 +02:00
} ) ;