Skip to content
This repository was archived by the owner on Mar 4, 2021. It is now read-only.

Feature/#13 payment interaction #14

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"history": "^4.7.2",
"isomorphic-fetch": "^2.2.1",
"jest": "22.4.2",
"jolocom-lib": "^2.2.9",
"jolocom-lib": "^2.2.12",
"material-ui": "^0.20.2",
"promise": "8.0.1",
"react": "^16.4.2",
Expand Down
65 changes: 63 additions & 2 deletions server/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import { extractDataFromClaims } from '../src/utils/'
import { RedisApi } from './types'
import { JSONWebToken } from 'jolocom-lib/js/interactionTokens/JSONWebToken'
import { IdentityWallet } from 'jolocom-lib/js/identityWallet/identityWallet'
import { keyIdToDid } from 'jolocom-lib/js/utils/helper'
import { keyIdToDid, publicKeyToAddress } from 'jolocom-lib/js/utils/helper'
import { CredentialResponse } from 'jolocom-lib/js/interactionTokens/credentialResponse'
import { CredentialOffer } from 'jolocom-lib/js/interactionTokens/credentialOffer'
import { JolocomLib } from 'jolocom-lib'
import { CredentialRequest } from 'jolocom-lib/js/interactionTokens/credentialRequest'
import { PaymentRequest } from 'jolocom-lib/js/interactionTokens/paymentRequest'
import { PaymentResponse } from 'jolocom-lib/js/interactionTokens/paymentResponse'


export const configureRoutes = async (app: Express, redisApi: RedisApi, iw: IdentityWallet, password: string) => {
const { setAsync, getAsync } = redisApi
Expand All @@ -23,6 +26,8 @@ export const configureRoutes = async (app: Express, redisApi: RedisApi, iw: Iden
res.sendFile(path.join(__dirname, `../dist/img/${name}`))
})

/// Endpoints for demo-sso-mobile app ///

/**
* An authentication endpoint route for deep linking for demo-sso-mobile;
*/
Expand All @@ -31,7 +36,7 @@ export const configureRoutes = async (app: Express, redisApi: RedisApi, iw: Iden
try {
const credentialRequest = await iw.create.interactionTokens.request.share(
{
callbackURL: 'demosso://authenticate/',
callbackURL: 'demosso://interaction/',
credentialRequirements
},
password
Expand Down Expand Up @@ -66,6 +71,34 @@ export const configureRoutes = async (app: Express, redisApi: RedisApi, iw: Iden
}
})

/**
* Route for mobile payment interaction
*/

app.get('/mobile/paymentRequest', async (req, res, next) => {
try {
const serviceEthAddress = publicKeyToAddress(iw.getPublicKey({
derivationPath: JolocomLib.KeyTypes.ethereumKey,
encryptionPass: password
}))

const paymentRequest = await iw.create.interactionTokens.request.payment({
callbackURL: 'demosso://interaction/',
description: 'Buy the Jolocom t-shirt on the go',
transactionDetails: {
receiverAddress: serviceEthAddress,
amountInEther: '0.00001'
}}, password)

const jwtCR = paymentRequest.encode()
res.send(jwtCR)
} catch (err) {
next(err)
}
})

/// web page endpoints ///

/**
* Route which expects the credential response from user
*/
Expand Down Expand Up @@ -164,4 +197,32 @@ export const configureRoutes = async (app: Express, redisApi: RedisApi, iw: Iden
next(err)
}
})


app.post('/payment-confirmation/:userId', async (req, res, next) => {
const { userId } = req.params
const { token } = req.body

try {
const localRecord = await getAsync(userId)
const encodedRequest: string = JSON.parse(localRecord).paymentRequest

const request: JSONWebToken<PaymentRequest> = JolocomLib.parse.interactionToken.fromJWT(encodedRequest)
const response: JSONWebToken<PaymentResponse> = JolocomLib.parse.interactionToken.fromJWT(token)

await iw.validateJWT(response, request)

const parsedUserData = JSON.parse(localRecord)
const userData = {
...parsedUserData.userData,
txHash: response.interactionToken.txHash
}

await setAsync(userId, JSON.stringify({ data: userData }))

res.json('OK')
} catch (err) {
next(err)
}
})
}
94 changes: 71 additions & 23 deletions server/sockets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as http from 'http'
import { DbWatcher } from './dbWatcher'
import { IdentityWallet } from 'jolocom-lib/js/identityWallet/identityWallet'
import { RedisApi } from './types'
import { JolocomLib } from 'jolocom-lib'
import { publicKeyToAddress } from 'jolocom-lib/js/utils/helper'
const SHA3 = require('sha3')

export const configureSockets = (
Expand All @@ -18,11 +20,45 @@ export const configureSockets = (

const baseSocket = io(server).origins('*:*')

const authQrCodeSocket = baseSocket.of('/qr-code')
const receiveQrCodeSocket = baseSocket.of('/qr-receive')
const dataSocket = baseSocket.of('/sso-status')
const authQrCodeSocket = baseSocket.of('/qr-auth')
const credReceiveQrCodeSocket = baseSocket.of('/qr-credReceive')
const paymentQrCodeSocket = baseSocket.of('/qr-payment')
const userDataSocket = baseSocket.of('/sso-status')

/**
* @description Used by the frontend to request credential request QR codes
* @param {string} userId - The session identifier
* @emits qrCode - encoded credential request JWT
*/

receiveQrCodeSocket.on('connection', async socket => {
authQrCodeSocket.on('connection', async socket => {
const { userId } = socket.handshake.query

const callbackURL = `${serviceUrl}/authentication/${userId}`
const credentialRequest = await identityWallet.create.interactionTokens.request.share(
{
callbackURL,
credentialRequirements
},
password
)

/** Encoded credential request is saved for validation purposes later */
await redisApi.setAsync(userId, JSON.stringify({ userId, request: credentialRequest.encode(), status: 'pending' }))
const qrCode = await new SSO().JWTtoQR(credentialRequest.encode())

console.log(`[DEBUG] : JWT for ${userId} : ${credentialRequest.encode()}`)
socket.emit(userId, qrCode)
})

/**
* @description Used by frontend to broadcast issue of credential by service
* @param {string} did - did of user requesting credential
* @param {string} answer - answer to the data question on webpage
* @emits qrCode - ecoded credential offer request JWT
*/

credReceiveQrCodeSocket.on('connection', async socket => {
const { did, answer } = socket.handshake.query

const didHash = SHA3.SHA3Hash()
Expand All @@ -38,39 +74,51 @@ export const configureSockets = (
password
)

console.log(credOfferRequest.encode())
const qrCode = await new SSO().JWTtoQR(credOfferRequest.encode())
socket.emit(did, qrCode)
})


/**
* @description Used by the frontend to request credential request QR codes
* @param {string} userId - The session identifier
* @emits qrCode
* @description Used by frontend to request payment request QR codes
* @param {string} userId - session identifier
* @emits qrCode - ecoded payment request JWT
*/

authQrCodeSocket.on('connection', async socket => {
paymentQrCodeSocket.on('connection', async socket => {
const { userId } = socket.handshake.query

const callbackURL = `${serviceUrl}/authentication/${userId}`
const credentialRequest = await identityWallet.create.interactionTokens.request.share(
{
callbackURL,
credentialRequirements
},
password
)

/** Encoded credential request is saved for validation purposes later */
await redisApi.setAsync(userId, JSON.stringify({ userId, request: credentialRequest.encode(), status: 'pending' }))
const qrCode = await new SSO().JWTtoQR(credentialRequest.encode())
const callbackURL = `${serviceUrl}/payment-confirmation/${userId}`
const serviceEthAddress = publicKeyToAddress(identityWallet.getPublicKey({
derivationPath: JolocomLib.KeyTypes.ethereumKey,
encryptionPass: password
}))

const paymentRequest = await identityWallet.create.interactionTokens.request.payment({
callbackURL,
description: `Special edition Jolocom t-shirt`,
transactionDetails: {
receiverAddress: serviceEthAddress,
amountInEther: `0.00033`
}
}, password)

console.log(`[DEBUG] : JWT for ${userId} : ${credentialRequest.encode()}`)
// Encoded payment request is saved for validation purposes later
await redisApi.setAsync(userId, JSON.stringify({ userId, paymentRequest: paymentRequest.encode()}))

const qrCode = await new SSO().JWTtoQR(paymentRequest.encode())
socket.emit(userId, qrCode)
})

dataSocket.on('connection', async socket => {
/**
* @description queries data for userId
* @param {srting} userId - session identifier
* @emits userData - temporary stored data connected to userId
*/

userDataSocket.on('connection', async socket => {
const { userId } = socket.handshake.query

dbWatcher.addSubscription(userId)
dbWatcher.on(userId, async () => {
const userData = await getAsync(userId)
Expand Down
36 changes: 25 additions & 11 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ export const initiateLogin = (loginProvider: loginProviders) => {
}

const randomId = randomString(5)
const qrCode = await getQrCode(randomId)
const authQrCode = await getAuthQrCode(randomId)

dispatch(setQRCode(qrCode))
dispatch(setQRCode(authQrCode))
dispatch(showDialog(loginProvider))

const data = await awaitUserData(randomId)
const parsed = JSON.parse(data)
const userData = { ...parsed.data, userId: randomId }

dispatch(setQRCode(undefined))
dispatch(setUserData(parsed))
dispatch(setUserData(userData))
dispatch(push('/dashboard'))
}
}
Expand All @@ -66,27 +67,40 @@ export const initiateReceiving = (did: string, answer: string) => {
}
}

export const initiatePayment = (userId: string) => {
return async (dispatch: Function) => {
const qrCode = await getPaymentRequestQrCode(userId)
dispatch(setReceivedQr(qrCode))
}
}

export const getPaymentRequestQrCode = (userId: string) => {
const socket = io(`/qr-payment`, { query: { userId } })
return new Promise<string>(resolve => {
socket.on(userId, (qrCode: string) => resolve(qrCode))
})
}

const getOfferQrCode = async (did: string, answer: string): Promise<string> => {
const socket = io(`/qr-receive`, { query: { did, answer} })
const socket = io(`/qr-credReceive`, { query: { did, answer } })
return new Promise<string>(resolve => {
socket.on(did, (qrCode: string) => resolve(qrCode))
})
}


const getQrCode = async (randomId: string): Promise<string> => {
const socket = io(`/qr-code`, { query: { userId: randomId } })
const getAuthQrCode = async (randomId: string): Promise<string> => {
const socket = io(`/qr-auth`, { query: { userId: randomId } })
return new Promise<string>(resolve => {
socket.on(randomId, (qrCode: string) => resolve(qrCode))
})
}

export const awaitUserData = async (randomId: string): Promise<string> => {
export const awaitUserData = async (userId: string): Promise<string> => {
const socket = io(`/sso-status`, {
query: { userId: randomId }
query: { userId }
})

return new Promise<string>(resolve => {
socket.on(randomId, (data: string) => resolve(data))
socket.on(userId, (data: string) => resolve(data))
})
}
}
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="./assets/app.bundle.js"></script></body>
<script type="text/javascript" src="./assets/js/app.bundle.js"></script></body>
</body>
</html>
4 changes: 3 additions & 1 deletion src/reducers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface DefaultState {
qrReceiveCode: string
}
userData: {
userId: string
did: string
email: string
givenName: string
Expand All @@ -26,6 +27,7 @@ export const defaultState: DefaultState = {
qrReceiveCode: ''
},
userData: {
userId: '',
did: '',
email: '',
givenName: '',
Expand All @@ -36,7 +38,7 @@ export const defaultState: DefaultState = {
export function rootReducer(state = defaultState, action: any) {
switch (action.type) {
case 'USER_DATA_SET':
return {...state, userData: {...action.value.data}}
return {...state, userData: {...action.value}}
case 'USER_DATA_RESET':
return {...state, userData: defaultState.userData}
case 'DIALOG_SHOW':
Expand Down
32 changes: 27 additions & 5 deletions src/ui/components/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ interface Props {
name: string
inputValue: string
handleUserInput: (e: React.FormEvent<HTMLInputElement>) => void
handleButtonClick: (e: React.MouseEvent<HTMLElement>) => void
handleIssueCredential: (e: React.MouseEvent<HTMLElement>) => void
handleRequestPayment: (e: React.MouseEvent<HTMLElement>) => void
}

const styles = {
Expand All @@ -34,11 +35,18 @@ const styles = {
button: {
marginBottom: '3%',
width: '15%'
}
},
paymentArea: {
paddingTop: '10%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between'
} as React.CSSProperties
}

export const DashboardComponent: React.SFC<Props> = props => {
const { container, responseArea, welcomeMsg, button } = styles
const { name, inputValue, handleButtonClick, handleUserInput } = props
const { container, responseArea, welcomeMsg, button, paymentArea } = styles
const { name, inputValue, handleIssueCredential, handleUserInput, handleRequestPayment } = props

return (
<div style={container}>
Expand All @@ -51,7 +59,21 @@ export const DashboardComponent: React.SFC<Props> = props => {
</div>
</div>
<div style={button}>
<AbstractedButton text="Receive" onClick={handleButtonClick} color={'primary'} />
<AbstractedButton
text="Get Claim"
onClick={handleIssueCredential}
imageName={'JO_icon.svg'}
color={'primary'}
/>
</div>
<div style={welcomeMsg}>Buy the limited edition Jolocom t-shirt for 0.0033 ETH</div>
<div style={button}>
<AbstractedButton
text="Buy"
onClick={handleRequestPayment}
imageName={`JO_icon.svg`}
color={'primary'}
/>
</div>
</div>
)
Expand Down
Loading