Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signicat document signing #2

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SIGNICAT_CLIENT_ID=''
SIGNICAT_CLIENT_SECRET=''
SIGNICAT_API_URL='https://api.signicat.com';
9 changes: 4 additions & 5 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import 'react-native-gesture-handler'
import { StatusBar } from 'expo-status-bar'
import * as React from 'react'
import { AppRegistry, Platform, StyleSheet, View } from 'react-native'
import { AppRegistry, Platform } from 'react-native'
import 'react-native-gesture-handler'
import {
Provider as PaperProvider,
MD2LightTheme,
Provider as PaperProvider,
configureFonts,
} from 'react-native-paper'
import { expo } from './app.json'
import App from './src/App'
import { StatusBar } from 'expo-status-bar'
import IphoneDummy from './src/components/IphoneDummy'
import useNotifications from './src/hooks/useNotifications'

const fontConfig = {
web: {
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ Så här kommer du igång:

Nu startas automatiskt din iOS Simulator och du kan testa applikationen live där.

## Signera avtal

Vi använder oss av [**Signicat**](https://developer.signicat.com/) för att signera avtal digitalt mellan Producent och Beställare. För att enkelt kunna demonstrera flödet kör vi Norwegian BankID

**För att detta ska fungera korrekt behöver du förbereda följande:**

Döp om `.env.example` till `.env` och fyll i `SIGNICAT_CLIENT_ID` och `SIGNICAT_CLIENT_SECRET`

Följ sedan stegen under [**Order test user**](https://developer.signicat.com/identity-methods/nbid/test/#order-test-user) för att generera testanvändare hos BankID Norge.

När du signerar avtalet, välj "Norwegian BankID" och följ stegen.

## Screenshots (ej färdig layout)

![image](https://user-images.githubusercontent.com/395843/232560646-26c641a6-429d-46cc-8b9d-1ed460c9e119.png)
Expand Down
101 changes: 56 additions & 45 deletions api/server.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Server } from 'socket.io'
import express from 'express'
import { createServer } from 'http'
import path from 'path'
import uuid from 'react-native-uuid'
import { Server } from 'socket.io'

import buyers from '../src/data/buyers'
import { categories } from '../src/data/categories'
import deals from '../src/data/deals'
import offers from '../src/data/offers'
import tenderRequests from '../src/data/tenderRequests'
import suppliers from '../src/data/suppliers'
import { categories } from '../src/data/categories'
import tenderRequests from '../src/data/tenderRequests'
import { sendPushNotification as push } from './notifications'
import { createPDF } from './signicatFile'

const port = process.env.PORT || 3000
const app = express()
const server = createServer(app)
Expand Down Expand Up @@ -233,49 +235,58 @@ io.on('connection', (socket) => {
respond(state.offers)
})

socket.on('editOffer', (offer) => {
const index = state.offers.findIndex((d) => d.id === offer.id)
const oldOffer = state.offers[index]
if (!oldOffer.approved && offer.approved) {
const token = oldOffer.supplier.token
const tenderRequest = state.tenderRequests.find(
(tr) => tr.id === offer.tenderRequestId
)
const otherOffersForTender = state.offers.filter(
(o) => o.tenderRequestId === tenderRequest?.id && offer.id !== o.id
)
if (tenderRequest)
sendPushNotification({
to: [token],
title: 'Anbud godkänt',
body: `Ditt anbud på ${tenderRequest.title} har godkänts`,
data: {
date: new Date(),
type: 'offer',
to: [oldOffer.supplier.id],
id: offer.id,
tenderRequestId: offer.tenderRequestId,
},
})

if (tenderRequest && otherOffersForTender)
otherOffersForTender.forEach(o =>
sendPushNotification({
to: [o.supplier.token],
title: 'Anbud förkastat',
body: `Ditt bud på ${tenderRequest.title} har förkastats. ${offer.supplier.name}s bud har godkänts av följande skäl: ${offer.acceptanceMotivation}`,
data: {
date: new Date(),
type: 'offer',
to: [o.supplier.id],
id: offer.id,
tenderRequestId: tenderRequest?.id,
},
})
)
socket.on('editOffer', async (offer) => {
try {
const index = state.offers.findIndex((d) => d.id === offer.id)
const oldOffer = state.offers[index]
if (!oldOffer.approved && offer.approved) {
const token = oldOffer.supplier.token
const tenderRequest = state.tenderRequests.find(
(tr) => tr.id === offer.tenderRequestId
)
const otherOffersForTender = state.offers.filter(
(o) => o.tenderRequestId === tenderRequest?.id && offer.id !== o.id
)
if (tenderRequest) {
sendPushNotification({
to: [token],
title: 'Anbud godkänt',
body: `Ditt anbud på ${tenderRequest.title} har godkänts`,
data: {
date: new Date(),
type: 'offer',
to: [oldOffer.supplier.id],
id: offer.id,
tenderRequestId: offer.tenderRequestId,
},
})

if (otherOffersForTender)
otherOffersForTender.forEach((o) =>
sendPushNotification({
to: [o.supplier.token],
title: 'Anbud förkastat',
body: `Ditt bud på ${tenderRequest.title} har förkastats. ${offer.supplier.name}s bud har godkänts av följande skäl: ${offer.acceptanceMotivation}`,
data: {
date: new Date(),
type: 'offer',
to: [o.supplier.id],
id: offer.id,
tenderRequestId: tenderRequest?.id,
},
})
)

const contract = await createPDF(offer, tenderRequest)
if (contract) offer.contract = contract
}
state.offers[index] = offer
io.emit('offers', state.offers)
state.offers[index] = offer
}

io.emit('offers', state.offers)
} catch (error) {
console.error('Error editing offer:', error)
}
})

// TENDER REQUESTS
Expand Down
160 changes: 160 additions & 0 deletions api/signicatFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import axios from 'axios'
import PDFDocument from 'pdfkit'
import { Contract } from '../src/data/contract'
import { Offer } from '../src/data/offers'
import { TenderRequest } from '../src/data/tenderRequests'

const clientId = process.env.SIGNICAT_CLIENT_ID || ''
const clientSecret = process.env.SIGNICAT_CLIENT_SECRET || ''
const apiUrl = process.env.SIGNICAT_API_URL || ''

export async function createPDF(
offer: Offer,
tenderRequest: TenderRequest
): Promise<Contract | undefined> {
try {
const authToken = await getAuthToken()
const pdfContent = await createPdfContent(offer, tenderRequest)
const contract = await uploadPdf(
pdfContent,
offer,
tenderRequest,
authToken
)
return contract
} catch (error) {
console.error('An error occurred:', error)
}
}

async function getAuthToken() {
const body = {
grant_type: 'client_credentials',
scope: 'signicat-api',
client_id: clientId,
client_secret: clientSecret,
}
const response = await axios.post(`${apiUrl}/auth/open/connect/token`, body, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})

console.log(response.data)
return response.data['access_token']
}

async function createPdfContent(
offer: Offer,
tenderRequest: TenderRequest
): Promise<string> {
const doc = new PDFDocument({ margin: 100 })
const chunks: Buffer[] = []

doc.on('data', chunks.push.bind(chunks))

doc.fontSize(14).text('Tender contract')
doc.moveDown()
doc.fontSize(11).text(`Tender: ${tenderRequest.title}`)
doc.fontSize(11).text(`Posted by: ${tenderRequest.buyer.name}`)
doc.moveDown()
doc
.fontSize(11)
.text(`Accepted offer: ${offer.price.SEK}kr from ${offer.supplier.name}`)
doc.fontSize(11).text(`Acceptance reason: ${offer.acceptanceMotivation}`)

doc.end()

const pdfBase64Content = await new Promise<string>((resolve) => {
doc.on('end', function () {
const pdfBuffer = Buffer.concat(chunks)
const pdfBase64String = pdfBuffer.toString('base64')
console.log('PDF generation completed.')
console.log(`Base64 content: ${pdfBase64String}`)
resolve(pdfBase64String)
})
})

return pdfBase64Content
}

async function uploadPdf(
pdfContent: string,
offer: Offer,
tenderRequest: TenderRequest,
authToken: string
): Promise<Contract | undefined> {
try {
const body = {
title: tenderRequest.title,
externalId: tenderRequest.id,
dataToSign: {
base64Content: pdfContent,
fileName: `${tenderRequest.title} - contract.pdf`,
},
contactDetails: {
email: '[email protected]',
},
requiredSignatures: 2,
signers: [
{
externalSignerId: `buy${offer.buyer.id}`,
signerInfo: {
firstName: offer.buyer.name,
email: offer.buyer.email,
},
redirectSettings: {
redirectMode: 'donot_redirect',
},
signatureType: {
mechanism: 'pkisignature',
},
},
{
externalSignerId: `sup${offer.supplier.id}`,
signerInfo: {
firstName: offer.supplier.name,
email: offer.supplier.email,
},
redirectSettings: {
redirectMode: 'donot_redirect',
},
signatureType: {
mechanism: 'pkisignature',
},
},
],
advanced: {
requiredSignatures: 2,
},
}

const response = await axios.post(
`${apiUrl}/express/sign/documents`,
body,
{
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
}
)

console.log('File created in signicat successfully', response.data)

const signers = response.data['signers'] ?? []

return {
documentId: response.data['documentId'],
buyerSignUrl: signers.find(
(s: any) => s['externalSignerId'] == `buy${offer.buyer.id}`
)['url'],
supplierSignUrl: signers.find(
(s: any) => s['externalSignerId'] == `sup${offer.supplier.id}`
)['url'],
}
} catch (error: any) {
console.error('File creation in signicat failed', error)
console.log(JSON.stringify(error['response']['data']['errors']))
}
}
Loading