@wdft/micropayments-sdk 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ import { it } from "vitest";
2
+ import { getViaMsg } from "./index.js";
3
+ import { mockFetch } from "./test_utils/mock_fetcher.js";
4
+
5
+ it("getViaMsg", async () => {
6
+ const mock = mockFetch();
7
+
8
+ await new Promise((resolve, reject) => {
9
+ mock.next(200, {
10
+ click_url: "mock_click_url",
11
+ incoming_message_device: "mock_num",
12
+ expires_at: Date.now() + 30_000,
13
+ commitment_id: "sample_commitment_id",
14
+ });
15
+
16
+ mock.next(200, {
17
+ message: "waiting for sms",
18
+ status: "waiting",
19
+ commitment_id: "sample_commitment_id",
20
+ });
21
+
22
+ mock.next(200, {
23
+ message: "waiting for sms",
24
+ status: "waiting",
25
+ commitment_id: "sample_commitment_id",
26
+ });
27
+
28
+ mock.next(200, {
29
+ message: "waiting for sms",
30
+ status: "resolved",
31
+ commitment_id: "sample_commitment_id",
32
+ });
33
+
34
+ getViaMsg(
35
+ {
36
+ amount: 10,
37
+ reference_id: "ref",
38
+ currency: "PLN",
39
+ tenant_id: "my-tenant",
40
+ endpoint: "http://localhost:8787",
41
+ fetcher: mock.fetcher.bind(mock),
42
+ retry_ms: 100,
43
+ },
44
+ (error, payload) => {
45
+ if (!payload || error) {
46
+ console.log(error);
47
+ reject(error);
48
+ return;
49
+ }
50
+
51
+ const { status } = payload;
52
+
53
+ switch (status) {
54
+ case "waiting":
55
+ case "created":
56
+ case "duplicated":
57
+ case "expired":
58
+ console.log(payload);
59
+ break;
60
+ case "resolved":
61
+ resolve(status);
62
+ }
63
+ },
64
+ );
65
+ });
66
+ });
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { ICallback } from "./client.js";
2
+ import type { Fetcher } from "./service.js";
3
+
4
+ import { Client } from "./client.js";
5
+
6
+ export interface IGetViaMsgInit {
7
+ reference_id: string;
8
+ amount: number;
9
+ currency: "PLN" | "EUR" | "GBP" | "USD" | "CHF" | string;
10
+ tenant_id: string;
11
+ endpoint?: string;
12
+ fetcher?: Fetcher;
13
+ retry_ms?: number;
14
+ }
15
+
16
+ export function getViaMsg(init: IGetViaMsgInit, callback: ICallback) {
17
+ const client = Client.new({
18
+ currency: init.currency,
19
+ endpoint: init.endpoint ?? "https://api.micropayments.wdft.ovh/",
20
+ tenant_id: init.tenant_id,
21
+ fetcher: init.fetcher ?? fetch,
22
+ retry_ms: init.retry_ms ?? 1000,
23
+ });
24
+
25
+ client.create(init.reference_id, init.amount, callback);
26
+ }
@@ -0,0 +1,18 @@
1
+ import { it } from "vitest";
2
+ import { expect } from "vitest";
3
+ import { mockFetch } from "./test_utils/mock_fetcher.js";
4
+
5
+ it("mocking works", async () => {
6
+ const mock = mockFetch();
7
+
8
+ mock.next(200, {
9
+ test: 123,
10
+ });
11
+
12
+ const response = await mock.fetcher();
13
+
14
+ expect(response.status).toBe(200);
15
+ expect(await response.json()).toMatchObject({
16
+ test: 123,
17
+ });
18
+ });
@@ -0,0 +1,53 @@
1
+ import { it } from "vitest";
2
+ import { expect } from "vitest";
3
+ import { Service } from "./service.js";
4
+ import { mockFetch } from "./test_utils/mock_fetcher.js";
5
+
6
+ it("service.createCommitment - 200 - resolved good data", async () => {
7
+ const mock = mockFetch();
8
+
9
+ const my = new Service({
10
+ endpoint: "http://localhost:8787",
11
+ tenant_id: "testing-dev-tenant",
12
+ fetcher: () => mock.fetcher(),
13
+ });
14
+
15
+ mock.next(200, {
16
+ click_url: "mock_click_url",
17
+ incoming_message_device: "mock_num",
18
+ expires_at: Date.now() + 30_000,
19
+ commitment_id: "sample_commitment_id",
20
+ });
21
+
22
+ const commitment = await my.createCommitment("sample-content-id", 10, "PLN");
23
+
24
+ expect(commitment).toMatchObject({
25
+ click_url: "mock_click_url",
26
+ incoming_message_device: "mock_num",
27
+ commitment_id: "sample_commitment_id",
28
+ });
29
+ });
30
+
31
+ it("service.checkCommitment - 200 - resolved good data", async () => {
32
+ const mock = mockFetch();
33
+
34
+ const my = new Service({
35
+ endpoint: "http://localhost:8787",
36
+ tenant_id: "testing-dev-tenant",
37
+ fetcher: () => mock.fetcher(),
38
+ });
39
+
40
+ mock.next(200, {
41
+ message: "waiting for sms",
42
+ status: "waiting",
43
+ commitment_id: "sample_commitment_id",
44
+ });
45
+
46
+ const status = await my.checkCommitment("sample_commitment_id");
47
+
48
+ expect(status).toMatchObject({
49
+ message: "waiting for sms",
50
+ status: "waiting",
51
+ commitment_id: "sample_commitment_id",
52
+ });
53
+ });
package/src/service.ts ADDED
@@ -0,0 +1,171 @@
1
+ interface CustomResponse {
2
+ status: number;
3
+ error: boolean;
4
+ }
5
+
6
+ interface SuccessResponse<T> extends CustomResponse {
7
+ error: false;
8
+ message: null;
9
+ data: T;
10
+ }
11
+
12
+ interface ErrorResponse extends CustomResponse {
13
+ error: true;
14
+ message: string;
15
+ data: null;
16
+ }
17
+
18
+ interface ICommitment {
19
+ click_url: string;
20
+ incoming_message_device: string;
21
+ expires_at: number;
22
+ commitment_id: string;
23
+ }
24
+
25
+ type ResponseDTO<T> = SuccessResponse<T> | ErrorResponse;
26
+
27
+ async function parseResponse<T = unknown>(response: Response): Promise<T> {
28
+ if (response.headers.get("Content-Type")?.includes("application/json")) {
29
+ return (await response.json()) as T;
30
+ }
31
+ return (await response.text()) as T;
32
+ }
33
+
34
+ export type Fetcher = (
35
+ input: RequestInfo | URL,
36
+ init?: RequestInit,
37
+ ) => Promise<Response>;
38
+
39
+ async function request<T = unknown>(
40
+ path: string,
41
+ req: RequestInit,
42
+ fetcher: Fetcher,
43
+ ): Promise<ResponseDTO<T | null>> {
44
+ try {
45
+ const response = await fetcher(new Request(path, req));
46
+ if (response.ok) {
47
+ return {
48
+ status: response.status,
49
+ error: false,
50
+ data: (await parseResponse(response)) as T,
51
+ } as ResponseDTO<T>;
52
+ }
53
+ const errorPayload = await parseResponse(response);
54
+ return {
55
+ status: response.status,
56
+ error:
57
+ errorPayload &&
58
+ typeof errorPayload === "object" &&
59
+ "error" in errorPayload
60
+ ? errorPayload.error
61
+ : true,
62
+ message:
63
+ errorPayload &&
64
+ typeof errorPayload === "object" &&
65
+ "message" in errorPayload
66
+ ? errorPayload.message
67
+ : response.statusText,
68
+ data: null,
69
+ } as ResponseDTO<T>;
70
+ } catch (error) {
71
+ return {
72
+ status: 500,
73
+ error: true,
74
+ message: error instanceof Error ? error.message : `${error}`,
75
+ data: null,
76
+ } as ErrorResponse;
77
+ }
78
+ }
79
+
80
+ function buildUrl(endpoint: string, path: string) {
81
+ const validEndpoint = endpoint.endsWith("/")
82
+ ? endpoint.substring(0, endpoint.length - 1)
83
+ : endpoint;
84
+ const validPath = path.startsWith("/") ? path : "/".concat(path);
85
+ return validEndpoint + validPath;
86
+ }
87
+
88
+ export interface IServiceOptions {
89
+ tenant_id: string;
90
+ endpoint: string;
91
+ fetcher?: Fetcher;
92
+ }
93
+
94
+ export interface ICommitmentStatus {
95
+ commitment_id: string;
96
+ status: string;
97
+ message: string;
98
+ }
99
+
100
+ export class Service {
101
+ private readonly fetcher: Fetcher;
102
+
103
+ constructor(private readonly options: IServiceOptions) {
104
+ if (!options.fetcher && !globalThis.fetch) {
105
+ throw new Error(
106
+ 'fetch is not available in environment, pass "fetcher" option with satisfies fetch interface',
107
+ );
108
+ }
109
+
110
+ this.fetcher = this.options.fetcher ?? globalThis.fetch;
111
+ }
112
+
113
+ async createCommitment(
114
+ reference_id: string,
115
+ amount: number,
116
+ currency: string,
117
+ ): Promise<ICommitment> {
118
+ const response = await request<ICommitment>(
119
+ buildUrl(this.options.endpoint, "/commitment"),
120
+ {
121
+ method: "POST",
122
+ body: JSON.stringify({
123
+ tenant_id: this.options.tenant_id,
124
+ reference_id,
125
+ currency: currency,
126
+ amount,
127
+ }),
128
+ },
129
+ this.fetcher,
130
+ );
131
+
132
+ if (response.error) {
133
+ throw new Error(response.message);
134
+ }
135
+
136
+ if (!response.data) {
137
+ throw new Error("Bad response");
138
+ }
139
+
140
+ return response.data;
141
+ }
142
+
143
+ async checkCommitment(commitment: string): Promise<ICommitmentStatus> {
144
+ const response = await request<ICommitmentStatus>(
145
+ buildUrl(this.options.endpoint, `/commitment/${commitment}`),
146
+ {
147
+ method: "GET",
148
+ },
149
+ this.fetcher,
150
+ );
151
+
152
+ switch (response.status) {
153
+ case 410:
154
+ return {
155
+ commitment_id: commitment,
156
+ message: response.message || "Expired",
157
+ status: "expired",
158
+ };
159
+ }
160
+
161
+ if (response.error) {
162
+ throw new Error(response.message);
163
+ }
164
+
165
+ if (!response.data) {
166
+ throw new Error("Bad response");
167
+ }
168
+
169
+ return response.data;
170
+ }
171
+ }
@@ -0,0 +1,24 @@
1
+ export const mockFetch = () => ({
2
+ __responses: [] as Response[],
3
+ next(status: number, body: null | string | object, headers: object = {}) {
4
+ this.__responses.push(
5
+ new Response(typeof body === "object" ? JSON.stringify(body) : body, {
6
+ status,
7
+ headers: {
8
+ "Content-Type":
9
+ typeof body === "object" ? "application/json" : "plain/text",
10
+ ...headers,
11
+ },
12
+ }),
13
+ );
14
+ },
15
+ async fetcher() {
16
+ const next = this.__responses.shift();
17
+
18
+ if (!next) {
19
+ throw new Error("not found mock item");
20
+ }
21
+
22
+ return next;
23
+ },
24
+ });