Skip to content

Commit 5ac7708

Browse files
Feat/payment msg handler (#229)
* payment handler --------- Co-authored-by: vmidyllic <[email protected]>
1 parent a82be9b commit 5ac7708

File tree

10 files changed

+695
-5
lines changed

10 files changed

+695
-5
lines changed

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@0xpolygonid/js-sdk",
3-
"version": "1.12.0",
3+
"version": "1.13.0",
44
"description": "SDK to work with Polygon ID",
55
"main": "dist/node/cjs/index.js",
66
"module": "dist/node/esm/index.js",

src/iden3comm/constants.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
3636
// ProposalRequestMessageType is type for proposal-request message
3737
PROPOSAL_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/proposal-request',
3838
// ProposalMessageType is type for proposal message
39-
PROPOSAL_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/proposal'
39+
PROPOSAL_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/proposal',
40+
// PaymentRequestMessageType is type for payment-request message
41+
PAYMENT_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/payment-request',
42+
// PaymentMessageType is type for payment message
43+
PAYMENT_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/payment'
4044
});
4145

4246
/**

src/iden3comm/handlers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './revocation-status';
66
export * from './common';
77
export * from './credential-proposal';
88
export * from './message-handler';
9+
export * from './payment';

src/iden3comm/handlers/message-handler.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BasicMessage, IPackageManager } from '../types';
22
import { AuthMessageHandlerOptions } from './auth';
33
import { RevocationStatusMessageHandlerOptions } from './revocation-status';
44
import { ContractMessageHandlerOptions } from './contract-request';
5+
import { PaymentHandlerOptions, PaymentRequestMessageHandlerOptions } from './payment';
56
/**
67
* iden3 Protocol message handler interface
78
*/
@@ -117,7 +118,8 @@ export class MessageHandler {
117118
| AuthMessageHandlerOptions
118119
| ContractMessageHandlerOptions
119120
| RevocationStatusMessageHandlerOptions
120-
| ContractMessageHandlerOptions
121+
| PaymentRequestMessageHandlerOptions
122+
| PaymentHandlerOptions
121123
| { [key: string]: unknown }
122124
): Promise<Uint8Array | null> {
123125
const { unpackedMediaType, unpackedMessage: message } =

src/iden3comm/handlers/payment.ts

+318
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import { PROTOCOL_MESSAGE_TYPE } from '../constants';
2+
import { MediaType } from '../constants';
3+
import { BasicMessage, IPackageManager, PackerParams } from '../types';
4+
5+
import { DID } from '@iden3/js-iden3-core';
6+
import * as uuid from 'uuid';
7+
import { proving } from '@iden3/js-jwz';
8+
import { byteEncoder } from '../../utils';
9+
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
10+
import {
11+
PaymentInfo,
12+
PaymentMessage,
13+
PaymentRequestDataInfo,
14+
PaymentRequestInfo,
15+
PaymentRequestMessage
16+
} from '../types/protocol/payment';
17+
import { PaymentRequestDataType, PaymentRequestType, PaymentType } from '../../verifiable';
18+
19+
/**
20+
* @beta
21+
* createPaymentRequest is a function to create protocol payment-request message
22+
* @param {DID} sender - sender did
23+
* @param {DID} receiver - receiver did
24+
* @param {string} agent - agent URL
25+
* @param {PaymentRequestInfo[]} payments - payments
26+
* @returns `PaymentRequestMessage`
27+
*/
28+
export function createPaymentRequest(
29+
sender: DID,
30+
receiver: DID,
31+
agent: string,
32+
payments: PaymentRequestInfo[]
33+
): PaymentRequestMessage {
34+
const uuidv4 = uuid.v4();
35+
const request: PaymentRequestMessage = {
36+
id: uuidv4,
37+
thid: uuidv4,
38+
from: sender.string(),
39+
to: receiver.string(),
40+
typ: MediaType.PlainMessage,
41+
type: PROTOCOL_MESSAGE_TYPE.PAYMENT_REQUEST_MESSAGE_TYPE,
42+
body: {
43+
agent,
44+
payments
45+
}
46+
};
47+
return request;
48+
}
49+
50+
/**
51+
* @beta
52+
* createPayment is a function to create protocol payment message
53+
* @param {DID} sender - sender did
54+
* @param {DID} receiver - receiver did
55+
* @param {PaymentInfo[]} payments - payments
56+
* @returns `PaymentMessage`
57+
*/
58+
export function createPayment(sender: DID, receiver: DID, payments: PaymentInfo[]): PaymentMessage {
59+
const uuidv4 = uuid.v4();
60+
const request: PaymentMessage = {
61+
id: uuidv4,
62+
thid: uuidv4,
63+
from: sender.string(),
64+
to: receiver.string(),
65+
typ: MediaType.PlainMessage,
66+
type: PROTOCOL_MESSAGE_TYPE.PAYMENT_MESSAGE_TYPE,
67+
body: {
68+
payments
69+
}
70+
};
71+
return request;
72+
}
73+
74+
/**
75+
* @beta
76+
* Interface that allows the processing of the payment-request and payment protocol messages
77+
*
78+
* @interface IPaymentHandler
79+
*/
80+
export interface IPaymentHandler {
81+
/**
82+
* @beta
83+
* unpacks payment-request
84+
* @param {Uint8Array} request - raw byte message
85+
* @returns `Promise<PaymentRequestMessage>`
86+
*/
87+
parsePaymentRequest(request: Uint8Array): Promise<PaymentRequestMessage>;
88+
89+
/**
90+
* @beta
91+
* handle payment-request
92+
* @param {Uint8Array} request - raw byte message
93+
* @param {PaymentRequestMessageHandlerOptions} opts - handler options
94+
* @returns {Promise<Uint8Array>} - agent message or null
95+
*/
96+
handlePaymentRequest(
97+
request: Uint8Array,
98+
opts: PaymentRequestMessageHandlerOptions
99+
): Promise<Uint8Array | null>;
100+
101+
/**
102+
* @beta
103+
* handle payment protocol message
104+
* @param {PaymentMessage} payment - payment message
105+
* @param {PaymentHandlerOptions} opts - options
106+
* @returns `Promise<void>`
107+
*/
108+
handlePayment(payment: PaymentMessage, opts: PaymentHandlerOptions): Promise<void>;
109+
}
110+
111+
/** @beta PaymentRequestMessageHandlerOptions represents payment-request handler options */
112+
export type PaymentRequestMessageHandlerOptions = {
113+
paymentHandler: (data: PaymentRequestDataInfo) => Promise<string>;
114+
};
115+
116+
/** @beta PaymentHandlerOptions represents payment handler options */
117+
export type PaymentHandlerOptions = {
118+
paymentRequest: PaymentRequestMessage;
119+
paymentValidationHandler: (txId: string, data: PaymentRequestDataInfo) => Promise<void>;
120+
};
121+
122+
/** @beta PaymentHandlerParams represents payment handler params */
123+
export type PaymentHandlerParams = {
124+
packerParams: PackerParams;
125+
};
126+
127+
/**
128+
*
129+
* Allows to process PaymentRequest protocol message
130+
* @beta
131+
* @class PaymentHandler
132+
* @implements implements IPaymentHandler interface
133+
*/
134+
export class PaymentHandler
135+
extends AbstractMessageHandler
136+
implements IPaymentHandler, IProtocolMessageHandler
137+
{
138+
/**
139+
* @beta Creates an instance of PaymentHandler.
140+
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
141+
* @param {PaymentHandlerParams} _params - payment handler params
142+
*
143+
*/
144+
145+
constructor(
146+
private readonly _packerMgr: IPackageManager,
147+
private readonly _params: PaymentHandlerParams
148+
) {
149+
super();
150+
}
151+
152+
public async handle(
153+
message: BasicMessage,
154+
context: PaymentRequestMessageHandlerOptions | PaymentHandlerOptions
155+
): Promise<BasicMessage | null> {
156+
switch (message.type) {
157+
case PROTOCOL_MESSAGE_TYPE.PAYMENT_REQUEST_MESSAGE_TYPE:
158+
return await this.handlePaymentRequestMessage(
159+
message as PaymentRequestMessage,
160+
context as PaymentRequestMessageHandlerOptions
161+
);
162+
case PROTOCOL_MESSAGE_TYPE.PAYMENT_MESSAGE_TYPE:
163+
await this.handlePayment(message as PaymentMessage, context as PaymentHandlerOptions);
164+
return null;
165+
default:
166+
return super.handle(message, context as { [key: string]: unknown });
167+
}
168+
}
169+
170+
/**
171+
* @inheritdoc IPaymentHandler#parsePaymentRequest
172+
*/
173+
async parsePaymentRequest(request: Uint8Array): Promise<PaymentRequestMessage> {
174+
const { unpackedMessage: message } = await this._packerMgr.unpack(request);
175+
const paymentRequest = message as PaymentRequestMessage;
176+
if (message.type !== PROTOCOL_MESSAGE_TYPE.PAYMENT_REQUEST_MESSAGE_TYPE) {
177+
throw new Error('Invalid media type');
178+
}
179+
return paymentRequest;
180+
}
181+
182+
private async handlePaymentRequestMessage(
183+
paymentRequest: PaymentRequestMessage,
184+
ctx: PaymentRequestMessageHandlerOptions
185+
): Promise<BasicMessage | null> {
186+
if (!paymentRequest.to) {
187+
throw new Error(`failed request. empty 'to' field`);
188+
}
189+
190+
if (!paymentRequest.from) {
191+
throw new Error(`failed request. empty 'from' field`);
192+
}
193+
194+
if (!paymentRequest.body?.payments?.length) {
195+
throw new Error(`failed request. no 'payments' in body`);
196+
}
197+
198+
if (!ctx.paymentHandler) {
199+
throw new Error(`please provide payment handler in context`);
200+
}
201+
202+
const senderDID = DID.parse(paymentRequest.to);
203+
const receiverDID = DID.parse(paymentRequest.from);
204+
205+
const payments: PaymentInfo[] = [];
206+
for (let i = 0; i < paymentRequest.body.payments.length; i++) {
207+
const paymentReq = paymentRequest.body.payments[i];
208+
if (paymentReq.type !== PaymentRequestType.PaymentRequest) {
209+
throw new Error(`failed request. not supported '${paymentReq.type}' payment type `);
210+
}
211+
212+
if (paymentReq.data.type !== PaymentRequestDataType.Iden3PaymentRequestCryptoV1) {
213+
throw new Error(`failed request. not supported '${paymentReq.data.type}' payment type `);
214+
}
215+
216+
const txId = await ctx.paymentHandler(paymentReq.data);
217+
218+
payments.push({
219+
id: paymentReq.data.id,
220+
type: PaymentType.Iden3PaymentCryptoV1,
221+
paymentData: {
222+
txId
223+
}
224+
});
225+
}
226+
227+
const paymentMessage = createPayment(senderDID, receiverDID, payments);
228+
const response = await this.packMessage(paymentMessage, senderDID);
229+
230+
const agentResult = await fetch(paymentRequest.body.agent, {
231+
method: 'POST',
232+
body: response,
233+
headers: {
234+
'Content-Type': 'application/octet-stream'
235+
}
236+
});
237+
238+
const arrayBuffer = await agentResult.arrayBuffer();
239+
if (!arrayBuffer.byteLength) {
240+
return null;
241+
}
242+
const { unpackedMessage } = await this._packerMgr.unpack(new Uint8Array(arrayBuffer));
243+
return unpackedMessage;
244+
}
245+
246+
/**
247+
* @inheritdoc IPaymentHandler#handlePaymentRequest
248+
*/
249+
async handlePaymentRequest(
250+
request: Uint8Array,
251+
opts: PaymentRequestMessageHandlerOptions
252+
): Promise<Uint8Array | null> {
253+
if (
254+
this._params.packerParams.mediaType === MediaType.SignedMessage &&
255+
!this._params.packerParams.packerOptions
256+
) {
257+
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`);
258+
}
259+
260+
const paymentRequest = await this.parsePaymentRequest(request);
261+
if (!paymentRequest.from) {
262+
throw new Error(`failed request. empty 'from' field`);
263+
}
264+
265+
if (!paymentRequest.to) {
266+
throw new Error(`failed request. empty 'to' field`);
267+
}
268+
269+
const agentMessage = await this.handlePaymentRequestMessage(paymentRequest, opts);
270+
if (!agentMessage) {
271+
return null;
272+
}
273+
274+
const senderDID = DID.parse(paymentRequest.to);
275+
return this.packMessage(agentMessage, senderDID);
276+
}
277+
278+
/**
279+
* @inheritdoc IPaymentHandler#handlePayment
280+
*/
281+
async handlePayment(payment: PaymentMessage, opts: PaymentHandlerOptions) {
282+
if (opts.paymentRequest.from !== payment.to) {
283+
throw new Error(
284+
`sender of the request is not a target of response - expected ${opts.paymentRequest.from}, given ${payment.to}`
285+
);
286+
}
287+
288+
if (!payment.body?.payments.length) {
289+
throw new Error(`failed request. empty 'payments' field in body`);
290+
}
291+
292+
for (let i = 0; i < payment.body.payments.length; i++) {
293+
const p = payment.body?.payments[i];
294+
const paymentRequestData = opts.paymentRequest.body?.payments.find((r) => r.data.id === p.id);
295+
if (!paymentRequestData) {
296+
throw new Error(`can't find payment request for payment id ${p.id}`);
297+
}
298+
if (!opts.paymentValidationHandler) {
299+
throw new Error(`please provide payment validation handler in options`);
300+
}
301+
await opts.paymentValidationHandler(p.paymentData.txId, paymentRequestData.data);
302+
}
303+
}
304+
305+
private async packMessage(message: BasicMessage, senderDID: DID): Promise<Uint8Array> {
306+
const responseEncoded = byteEncoder.encode(JSON.stringify(message));
307+
const packerOpts =
308+
this._params.packerParams.mediaType === MediaType.SignedMessage
309+
? this._params.packerParams.packerOptions
310+
: {
311+
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg
312+
};
313+
return await this._packerMgr.pack(this._params.packerParams.mediaType, responseEncoded, {
314+
senderDID,
315+
...packerOpts
316+
});
317+
}
318+
}

src/iden3comm/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './protocol/proof';
55
export * from './protocol/revocation';
66
export * from './protocol/contract-request';
77
export * from './protocol/proposal-request';
8+
export * from './protocol/payment';
89

910
export * from './packer';
1011
export * from './packageManager';

0 commit comments

Comments
 (0)