Skip to content

Commit 118d6a8

Browse files
authored
Merge pull request #2 from goipay/feat/getting-started-usage
feat: add getting started tutorial
2 parents fd23530 + 09763ef commit 118d6a8

File tree

11 files changed

+841
-11
lines changed

11 files changed

+841
-11
lines changed

docs/development/architecture.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ sidebar_position: 1
55
displayed_sidebar: docsSidebar
66
---
77

8-
![DB Schema](../../static/img/goipay_db_scheme.svg)
8+
## DB Schema
9+
10+
![DB Schema](/img/goipay_db_scheme.svg)

docs/getting-started/configuration.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ slug: configuration
44
sidebar_position: 2
55
---
66

7-
Here you can see an example env file:
8-
```shell
7+
Here you can see an example .env file:
8+
```ini title=".env.example"
99
# Can be either 'prod' or 'dev'.
1010
# In 'dev' mode, a reflection server is established.
1111
MODE=dev
1212

13-
SERVER_HOST=0.0.0.0
13+
SERVER_HOST=localhost
1414
SERVER_PORT=3000
1515

16+
# As for now, only PostgreSQL is supported
1617
DATABASE_HOST=localhost
17-
DATABASE_PORT=5432
18+
DATABASE_PORT=54321
1819
DATABASE_USER=postgres
1920
DATABASE_PASS=postgres
20-
DATABASE_NAME=crypto_gateway_test
21+
DATABASE_NAME=goipay_db
2122

2223
XMR_DAEMON_URL=http://node.monerodevs.org:38089
2324
XMR_DAEMON_USER=

docs/getting-started/installation/docker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar_position: 2
77
The simplest way to set up GoiPay is by using the Docker image.
88

99
Here you can find an example Docker Compose file:
10-
```shell
10+
```yaml title="docker-compose.yml"
1111
version: '3.8'
1212

1313
services:

docs/getting-started/installation/prerequisites.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ goose -dir ./sql/migrations postgres postgresql://YOUR_DB_USER:YOUR_DB_PASS@YOUR
3737
Also you can apply migrations usign docker container.
3838

3939
#### Docker Compose
40-
```shell
40+
```yaml
4141
migrations:
4242
image: ghcr.io/kukymbr/goose-docker:3.21.1
4343
environment:

docs/getting-started/tutorial.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
title: Tutorial
3+
slug: tutorial
4+
sidebar_position: 3
5+
---
6+
7+
## Introduction
8+
9+
In this section, we’ll look at an example of how you can integrate GoiPay into your project.
10+
11+
The showcase project itself you can run using [folowing guide](https://github.com/goipay/example#getting-started).
12+
13+
14+
## Basic Workflow
15+
16+
The interaction workflow between the external service and GoiPay is described in the diagram below.
17+
18+
![Workflow](/diagrams/basic_workflow.svg)
19+
20+
All these aspects will be explained further with code examples.
21+
22+
23+
## User Registration
24+
25+
Before creating an invoice, we need to register a user and set the user's private view and public spend XMR keys.
26+
This is necessary to generate subaddresses for payments and track the transaction state on the blockchain to verify the payment.
27+
28+
Although GoiPay is designed to support multiple users, for simplicity, we will use only one predefined user and it's keys in the `.env` file.
29+
30+
```ini title=".env"
31+
USER_ID=de92d9a9-9e2e-4456-ba0b-20424c97fde2 # UUID
32+
33+
# XMR KEYS
34+
XMR_PRIV_VIEW=8aa763d1c8d9da4ca75cb6ca22a021b5cca376c1367be8d62bcc9cdf4b926009
35+
XMR_PUB_SPEND=38e9908d33d034de0ba1281aa7afe3907b795cea14852b3d8fe276e8931cb130
36+
37+
GOIPAY_ADDRESS=localhost:3001
38+
```
39+
40+
### User Registration and Keys Setup
41+
42+
Here is the startup script that runs on server initialization.
43+
It uses the gRPC client to create a user and set up XMR keys.
44+
45+
```ts title="startup.ts"
46+
import { RegisterUserRequest, RegisterUserResponse, UpdateCryptoKeysRequest, UpdateCryptoKeysResponse } from '@/generated/goipay/user_pb'
47+
import { userGrpcClient } from '@/lib/grpc-clients'
48+
import { getEnvOrThrow } from '@/lib/utils'
49+
import { XmrKeysUpdateRequest } from '@/generated/goipay/crypto_pb'
50+
import { promisify } from 'util'
51+
import { userId } from '@/lib/const'
52+
53+
(async () => {
54+
// User Registration
55+
const registerUserPromise = promisify(userGrpcClient.registerUser.bind(userGrpcClient)) as (
56+
request: RegisterUserRequest
57+
) => Promise<RegisterUserResponse>
58+
59+
try {
60+
const res = await registerUserPromise(new RegisterUserRequest().setUserid(userId))
61+
console.log(res.toObject())
62+
} catch (err) {
63+
console.error(err)
64+
}
65+
66+
// Keys Update
67+
const updateKeysPromise = promisify(userGrpcClient.updateCryptoKeys.bind(userGrpcClient)) as (
68+
request: UpdateCryptoKeysRequest
69+
) => Promise<UpdateCryptoKeysResponse>
70+
71+
try {
72+
await updateKeysPromise(
73+
new UpdateCryptoKeysRequest()
74+
.setUserid(userId)
75+
.setXmrreq(new XmrKeysUpdateRequest().setPrivviewkey(getEnvOrThrow('XMR_PRIV_VIEW')).setPubspendkey(getEnvOrThrow('XMR_PUB_SPEND')))
76+
)
77+
console.log('Keys Updated')
78+
} catch (err) {
79+
console.error(err)
80+
}
81+
})()
82+
```
83+
84+
:::note
85+
In the [DB Schema](/docs/development/architecture#db-schema), crypto keys have a unique constraint, meaning each user must have unique keys.
86+
:::
87+
88+
89+
## Subscribe to Invoice Status Notification Stream
90+
91+
In this API route, Server-Sent Events (SSE) are used as the transport mechanism between the client and server to receive updated invoices from the gRPC Invoice Status Stream.
92+
93+
```ts title="app/api/socket/route.ts"
94+
import { InvoiceStatusStreamRequest, InvoiceStatusStreamResponse } from '@/generated/goipay/invoice_pb'
95+
import { invoiceGrpcClient } from '@/lib/grpc-clients'
96+
97+
const encoder = new TextEncoder()
98+
// The collection of subscribed clients.
99+
const connectedClients = new Set<WritableStreamDefaultWriter>()
100+
101+
// Helper function to broadcast SSE messages to all subscribed clients.
102+
function broadcast(message: string) {
103+
Array.from(connectedClients).forEach((client) => {
104+
client.write(encoder.encode(message)).catch((err) => {
105+
console.error('Error writing to client:', err)
106+
connectedClients.delete(client)
107+
})
108+
})
109+
}
110+
111+
// The gRPC Invoice Status Stream that broadcasts each updated invoice to all SSE clients.
112+
const stream = invoiceGrpcClient.invoiceStatusStream(new InvoiceStatusStreamRequest())
113+
stream.on('data', (invRes: InvoiceStatusStreamResponse) => {
114+
broadcast(`event: new-invoice\ndata: ${JSON.stringify(invRes.getInvoice()?.toObject())}\n\n`)
115+
})
116+
117+
// HTTP handler for establishing an SSE connection.
118+
export async function GET() {
119+
const clientStream = new TransformStream()
120+
const writer = clientStream.writable.getWriter()
121+
connectedClients.add(writer)
122+
123+
writer.write(encoder.encode('event: connected\ndata: keepalive\n\n')).catch(() => {
124+
connectedClients.delete(writer)
125+
})
126+
127+
return new Response(clientStream.readable, {
128+
headers: {
129+
'Content-Type': 'text/event-stream',
130+
Connection: 'keep-alive',
131+
'Cache-Control': 'no-cache, no-transform',
132+
},
133+
})
134+
}
135+
```
136+
137+
So, we can establish an SSE connection on the client-side, subscribe to the `new-invoice` event, and render the received invoices on the frontend.
138+
139+
```tsx title="app/components/main.tsx"
140+
// Client-side subscription to the SSE connection.
141+
useEffect(() => {
142+
const sse = new EventSource('/api/socket')
143+
144+
sse.onopen = () => {
145+
console.log('Connected to SSE')
146+
}
147+
sse.addEventListener('new-invoice', (e) => {
148+
const invoice = JSON.parse(e.data) as InvoiceGrpc.AsObject
149+
setInvoices((prev) => [mapInvoiceAsObjectToInvoice(invoice), ...prev.filter((i) => i.id !== invoice.id)])
150+
})
151+
sse.onerror = (e) => {
152+
console.error('SSE connection error:', e)
153+
}
154+
155+
return () => {
156+
sse.close()
157+
console.log('SSE connection closed')
158+
}
159+
}, [])
160+
```
161+
162+
```tsx title="app/components/main.tsx"
163+
// Rendering the content of each tab, displaying invoices filtered by their status.
164+
{tabs.map((t) => (
165+
<TabsContent key={t.val} value={t.val} className="h-[calc(100vh-250px)] overflow-auto">
166+
<InvoicesPageTab header={t.header} invoices={invoices.filter((i) => i.status === t.status)} />
167+
</TabsContent>
168+
))}
169+
```
170+
171+
## Create Invoice
172+
173+
:::note
174+
Partial payments are not supported.
175+
If you pay less than the required amount, you will need to pay the full amount again.
176+
:::
177+
178+
```tsx title="app/components/footer.tsx"
179+
// Creating a new invoice via the gRPC client.
180+
async function handleNewInvoiceSubmit(formData: FormData) {
181+
'use server'
182+
const createInvoicePromise = promisify(invoiceGrpcClient.createInvoice.bind(invoiceGrpcClient)) as (
183+
request: CreateInvoiceRequest
184+
) => Promise<CreateInvoiceResponse>
185+
186+
const userId = formData.get('userId') as string
187+
const amount = formData.get('amount') as string
188+
const confirmations = formData.get('confirmations') as string
189+
const timeout = formData.get('timeout') as string
190+
if (!amount || !confirmations || !userId || !timeout) return
191+
192+
try {
193+
await createInvoicePromise(
194+
new CreateInvoiceRequest()
195+
.setUserid(userId)
196+
.setCoin(CoinType.XMR)
197+
.setAmount(parseFloat(amount))
198+
.setConfirmations(parseInt(confirmations))
199+
.setTimeout(parseInt(timeout))
200+
)
201+
} catch (err) {
202+
console.error(err)
203+
}
204+
}
205+
```
206+
207+
:::note
208+
The timeout option in the request applies to all conditions, including amount and confirmations.
209+
If the required amount or more is paid, but the required number of confirmations hasn't been met by the time the timeout expires, the invoice will be considered expired.
210+
:::

docs/intro.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ sidebar_position: 1
88

99
GoiPay is a lightweight cryptocurrency payment processor microservice, written in Golang, designed for creating and processing cryptocurrency invoices via gRPC.
1010

11+
It was built as a service-to-service solution with limited functionality, primarily focusing on the basic features described above, so you can easily integrate it into your own projects.
12+
1113
## Key Features
1214

1315
- **Self-hosted**: Run it on your own infrastructure

docusaurus.config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ const config: Config = {
4646
],
4747
],
4848

49+
plugins: [
50+
[
51+
require.resolve('@easyops-cn/docusaurus-search-local'),
52+
{
53+
hashed: true,
54+
indexBlog: false
55+
},
56+
],
57+
],
58+
4959
themeConfig: {
5060
// Replace with your project's social card
5161
image: 'img/goipay-social-card.jpg',
@@ -73,6 +83,10 @@ const config: Config = {
7383
label: 'GitHub',
7484
position: 'right',
7585
},
86+
{
87+
type: 'search', // Add this line to include the search bar
88+
position: 'right',
89+
},
7690
],
7791
},
7892
footer: {
@@ -85,6 +99,10 @@ const config: Config = {
8599
label: 'Docs',
86100
to: '/docs/introduction',
87101
},
102+
{
103+
label: 'API Reference',
104+
to: '/docs/api/grpc',
105+
},
88106
],
89107
},
90108
{
@@ -111,6 +129,7 @@ const config: Config = {
111129
prism: {
112130
theme: prismThemes.github,
113131
darkTheme: prismThemes.dracula,
132+
additionalLanguages: ['ini', 'bash']
114133
},
115134
} satisfies Preset.ThemeConfig,
116135
};

0 commit comments

Comments
 (0)