@moneypot/hub 1.9.0-dev.9 → 1.10.0-dev.1
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.
- package/dist/cli/add-casino.js +24 -17
- package/dist/src/__generated__/gql.d.ts +2 -2
- package/dist/src/__generated__/gql.js +1 -1
- package/dist/src/__generated__/graphql.d.ts +23 -5
- package/dist/src/__generated__/graphql.js +3 -1
- package/dist/src/context.d.ts +12 -0
- package/dist/src/context.js +31 -0
- package/dist/src/db/index.d.ts +3 -6
- package/dist/src/db/index.js +5 -13
- package/dist/src/db/types.d.ts +1 -0
- package/dist/src/db/util.d.ts +4 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +3 -2
- package/dist/src/index.d.ts +7 -4
- package/dist/src/index.js +23 -12
- package/dist/src/pg-versions/010-take-request-refunded-at.sql +1 -0
- package/dist/src/plugins/hub-add-casino.js +4 -2
- package/dist/src/plugins/hub-authenticate.js +3 -2
- package/dist/src/plugins/hub-claim-faucet.js +5 -3
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +1 -10
- package/dist/src/plugins/hub-make-outcome-bet.js +20 -55
- package/dist/src/plugins/hub-withdraw.js +3 -2
- package/dist/src/process-transfers/index.d.ts +5 -2
- package/dist/src/process-transfers/index.js +15 -11
- package/dist/src/process-transfers/polling-processor.d.ts +3 -1
- package/dist/src/process-transfers/polling-processor.js +17 -13
- package/dist/src/process-transfers/process-transfer.d.ts +3 -1
- package/dist/src/process-transfers/process-transfer.js +7 -8
- package/dist/src/process-transfers/websocket-processor.d.ts +3 -1
- package/dist/src/process-transfers/websocket-processor.js +3 -1
- package/dist/src/process-withdrawal-request.d.ts +3 -1
- package/dist/src/process-withdrawal-request.js +6 -5
- package/dist/src/risk-policy.d.ts +20 -0
- package/dist/src/risk-policy.js +61 -0
- package/dist/src/server/graphile.config.d.ts +3 -1
- package/dist/src/server/graphile.config.js +6 -5
- package/dist/src/server/index.d.ts +3 -1
- package/dist/src/server/index.js +5 -4
- package/dist/src/server/middleware/authentication.d.ts +2 -1
- package/dist/src/server/middleware/authentication.js +5 -5
- package/dist/src/take-request/process-take-request.d.ts +13 -2
- package/dist/src/take-request/process-take-request.js +70 -23
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { formatCurrency } from "./format-currency.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const RiskLimitsSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
maxWager: z.number().positive().optional(),
|
|
7
|
+
maxPayout: z.number().positive(),
|
|
8
|
+
})
|
|
9
|
+
.strict();
|
|
10
|
+
export function validateRisk(options) {
|
|
11
|
+
const { wager, bankroll, maxPotentialPayout, riskPolicy } = options;
|
|
12
|
+
if (maxPotentialPayout > bankroll) {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
error: `House cannot cover potential payout (${formatCurrency(maxPotentialPayout, {
|
|
16
|
+
displayUnitName: options.displayUnitName,
|
|
17
|
+
displayUnitScale: options.displayUnitScale,
|
|
18
|
+
})}). Bankroll: ${formatCurrency(bankroll, {
|
|
19
|
+
displayUnitName: options.displayUnitName,
|
|
20
|
+
displayUnitScale: options.displayUnitScale,
|
|
21
|
+
})}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (!riskPolicy) {
|
|
25
|
+
return { ok: true, value: undefined };
|
|
26
|
+
}
|
|
27
|
+
const limitsResult = RiskLimitsSchema.safeParse(riskPolicy(options));
|
|
28
|
+
if (!limitsResult.success) {
|
|
29
|
+
logger.error(limitsResult, "Invalid risk policy");
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: "Invalid risk policy",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const limits = limitsResult.data;
|
|
36
|
+
if (limits.maxWager !== undefined && wager > limits.maxWager) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
error: `Wager (${formatCurrency(wager, {
|
|
40
|
+
displayUnitName: options.displayUnitName,
|
|
41
|
+
displayUnitScale: options.displayUnitScale,
|
|
42
|
+
})}) exceeds limit (${formatCurrency(limits.maxWager, {
|
|
43
|
+
displayUnitName: options.displayUnitName,
|
|
44
|
+
displayUnitScale: options.displayUnitScale,
|
|
45
|
+
})})`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (maxPotentialPayout > limits.maxPayout) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: `Payout (${formatCurrency(maxPotentialPayout, {
|
|
52
|
+
displayUnitName: options.displayUnitName,
|
|
53
|
+
displayUnitScale: options.displayUnitScale,
|
|
54
|
+
})}) exceeds limit (${formatCurrency(limits.maxPayout, {
|
|
55
|
+
displayUnitName: options.displayUnitName,
|
|
56
|
+
displayUnitScale: options.displayUnitScale,
|
|
57
|
+
})})`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, value: undefined };
|
|
61
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "graphile-config";
|
|
2
2
|
import "postgraphile";
|
|
3
|
+
import { ServerContext } from "../context.js";
|
|
3
4
|
export type UserSessionContext = {
|
|
4
5
|
user_id: string;
|
|
5
6
|
mp_user_id: string;
|
|
@@ -15,9 +16,10 @@ export type PluginIdentity = {
|
|
|
15
16
|
};
|
|
16
17
|
export declare const requiredPlugins: readonly GraphileConfig.Plugin[];
|
|
17
18
|
export declare const defaultPlugins: readonly GraphileConfig.Plugin[];
|
|
18
|
-
export declare function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, }: {
|
|
19
|
+
export declare function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, }: {
|
|
19
20
|
plugins: readonly GraphileConfig.Plugin[];
|
|
20
21
|
exportSchemaSDLPath?: string;
|
|
21
22
|
extraPgSchemas: string[];
|
|
22
23
|
abortSignal: AbortSignal;
|
|
24
|
+
context: ServerContext;
|
|
23
25
|
}): GraphileConfig.Preset;
|
|
@@ -48,7 +48,7 @@ export const defaultPlugins = [
|
|
|
48
48
|
HubClaimFaucetPlugin,
|
|
49
49
|
customPgOmitArchivedPlugin("deleted"),
|
|
50
50
|
];
|
|
51
|
-
export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, }) {
|
|
51
|
+
export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, }) {
|
|
52
52
|
if (exportSchemaSDLPath) {
|
|
53
53
|
if (!exportSchemaSDLPath.startsWith("/")) {
|
|
54
54
|
throw new Error("exportSchemaSDLPath must be an absolute path");
|
|
@@ -56,6 +56,7 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
56
56
|
if (!exportSchemaSDLPath.endsWith(".graphql")) {
|
|
57
57
|
throw new Error("exportSchemaSDLPath must end with .graphql");
|
|
58
58
|
}
|
|
59
|
+
logger.info(`Will save generated graphql schema to ${exportSchemaSDLPath}`);
|
|
59
60
|
}
|
|
60
61
|
const mutablePlugins = [...plugins];
|
|
61
62
|
for (const requiredPlugin of requiredPlugins) {
|
|
@@ -64,7 +65,6 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
64
65
|
mutablePlugins.unshift(requiredPlugin);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
logger.info(`Will save generated graphql schema to ${exportSchemaSDLPath}`);
|
|
68
68
|
const preset = {
|
|
69
69
|
extends: [PostGraphileAmberPreset],
|
|
70
70
|
disablePlugins: ["NodePlugin"],
|
|
@@ -96,7 +96,7 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
96
96
|
grafast: {
|
|
97
97
|
context(ctx, _args) {
|
|
98
98
|
if (ctx.ws) {
|
|
99
|
-
return handleWebsocketContext(ctx.ws);
|
|
99
|
+
return handleWebsocketContext(context, ctx.ws);
|
|
100
100
|
}
|
|
101
101
|
const expressReq = ctx.expressv4.req;
|
|
102
102
|
const reqIdentity = expressReq.identity;
|
|
@@ -132,6 +132,7 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
|
|
|
132
132
|
pgSettings,
|
|
133
133
|
identity: pluginIdentity,
|
|
134
134
|
abortSignal,
|
|
135
|
+
superuserPool: context.superuserPool,
|
|
135
136
|
};
|
|
136
137
|
},
|
|
137
138
|
},
|
|
@@ -147,12 +148,12 @@ function getSessionIdFromWebsocketCtx(ws) {
|
|
|
147
148
|
const uuid = value.slice("session:".length);
|
|
148
149
|
return isUuid(uuid) ? uuid : "";
|
|
149
150
|
}
|
|
150
|
-
async function handleWebsocketContext(ws) {
|
|
151
|
+
async function handleWebsocketContext(context, ws) {
|
|
151
152
|
const sessionId = getSessionIdFromWebsocketCtx(ws);
|
|
152
153
|
if (!sessionId) {
|
|
153
154
|
throw new Error("Unauthorized");
|
|
154
155
|
}
|
|
155
|
-
const result = await db.userFromActiveSessionKey(
|
|
156
|
+
const result = await db.userFromActiveSessionKey(context.superuserPool, sessionId);
|
|
156
157
|
if (!result) {
|
|
157
158
|
throw new Error("Unauthorized");
|
|
158
159
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ServerOptions } from "../index.js";
|
|
2
|
+
import { ServerContext } from "../context.js";
|
|
2
3
|
export type HubServer = {
|
|
3
4
|
listen: () => Promise<void>;
|
|
4
5
|
shutdown: () => Promise<void>;
|
|
5
6
|
};
|
|
6
|
-
export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, }: Pick<ServerOptions, "plugins" | "exportSchemaSDLPath" | "extraPgSchemas" | "configureApp"> & {
|
|
7
|
+
export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, }: Pick<ServerOptions, "plugins" | "exportSchemaSDLPath" | "extraPgSchemas" | "configureApp"> & {
|
|
7
8
|
abortSignal: AbortSignal;
|
|
9
|
+
context: ServerContext;
|
|
8
10
|
}): HubServer;
|
package/dist/src/server/index.js
CHANGED
|
@@ -31,11 +31,11 @@ const dashboardDir = path.join(distDir, "dashboard");
|
|
|
31
31
|
if (!fs.existsSync(dashboardDir)) {
|
|
32
32
|
throw new Error(`Could not find dashboard directory. Expected it to be at "${dashboardDir}"`);
|
|
33
33
|
}
|
|
34
|
-
function createExpressServer() {
|
|
34
|
+
function createExpressServer(context) {
|
|
35
35
|
const app = express();
|
|
36
36
|
app.disable("x-powered-by");
|
|
37
37
|
app.use(cors());
|
|
38
|
-
app.use(authentication());
|
|
38
|
+
app.use(authentication(context));
|
|
39
39
|
app.use("/dashboard", express.static(dashboardDir));
|
|
40
40
|
app.use("/dashboard/*splat", (_, res) => {
|
|
41
41
|
res.sendFile(path.join(dashboardDir, "index.html"));
|
|
@@ -45,13 +45,14 @@ function createExpressServer() {
|
|
|
45
45
|
});
|
|
46
46
|
return app;
|
|
47
47
|
}
|
|
48
|
-
export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, }) {
|
|
49
|
-
const expressServer = createExpressServer();
|
|
48
|
+
export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, }) {
|
|
49
|
+
const expressServer = createExpressServer(context);
|
|
50
50
|
const preset = createPreset({
|
|
51
51
|
plugins: plugins ?? defaultPlugins,
|
|
52
52
|
exportSchemaSDLPath,
|
|
53
53
|
extraPgSchemas: extraPgSchemas ?? [],
|
|
54
54
|
abortSignal,
|
|
55
|
+
context,
|
|
55
56
|
});
|
|
56
57
|
const pgl = postgraphile.default(preset);
|
|
57
58
|
const serv = pgl.createServ(grafserv);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Response, NextFunction } from "express";
|
|
2
2
|
import { HubRequest } from "../../express.js";
|
|
3
|
-
|
|
3
|
+
import { ServerContext } from "../../context.js";
|
|
4
|
+
declare const authentication: (context: ServerContext) => (req: HubRequest, _: Response, next: NextFunction) => Promise<void>;
|
|
4
5
|
export default authentication;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isUuid } from "../../util.js";
|
|
2
2
|
import * as db from "../../db/index.js";
|
|
3
|
-
async function checkAndUpdateApiKey(key) {
|
|
4
|
-
const row = await
|
|
3
|
+
async function checkAndUpdateApiKey(key, pool) {
|
|
4
|
+
const row = await pool
|
|
5
5
|
.query(`
|
|
6
6
|
UPDATE hub.api_key
|
|
7
7
|
SET last_used_at = now()
|
|
@@ -11,7 +11,7 @@ async function checkAndUpdateApiKey(key) {
|
|
|
11
11
|
.then(db.maybeOneRow);
|
|
12
12
|
return !!row;
|
|
13
13
|
}
|
|
14
|
-
const authentication = () => {
|
|
14
|
+
const authentication = (context) => {
|
|
15
15
|
return async (req, _, next) => {
|
|
16
16
|
const header = req.get("authorization");
|
|
17
17
|
if (!header) {
|
|
@@ -28,7 +28,7 @@ const authentication = () => {
|
|
|
28
28
|
}
|
|
29
29
|
if (tokenType === "session") {
|
|
30
30
|
const sessionKey = token;
|
|
31
|
-
const result = await db.userFromActiveSessionKey(
|
|
31
|
+
const result = await db.userFromActiveSessionKey(context.superuserPool, sessionKey);
|
|
32
32
|
if (result) {
|
|
33
33
|
req.identity = {
|
|
34
34
|
kind: "user",
|
|
@@ -40,7 +40,7 @@ const authentication = () => {
|
|
|
40
40
|
}
|
|
41
41
|
else if (tokenType === "apikey") {
|
|
42
42
|
const apiKey = token;
|
|
43
|
-
const isValid = await checkAndUpdateApiKey(apiKey);
|
|
43
|
+
const isValid = await checkAndUpdateApiKey(apiKey, context.superuserPool);
|
|
44
44
|
if (isValid) {
|
|
45
45
|
req.identity = {
|
|
46
46
|
kind: "operator",
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { GraphQLClient } from "graphql-request";
|
|
2
2
|
import { MpTakeRequestFieldsFragment } from "../__generated__/graphql.js";
|
|
3
|
+
import * as pg from "pg";
|
|
3
4
|
export declare const MP_TAKE_REQUEST_FIELDS: import("@graphql-typed-document-node/core").TypedDocumentNode<MpTakeRequestFieldsFragment, unknown>;
|
|
4
5
|
export declare function mpGetTakeRequest(graphqlClient: GraphQLClient, id: string): Promise<MpTakeRequestFieldsFragment | null>;
|
|
5
|
-
export declare function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, }: {
|
|
6
|
+
export declare function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, pool, }: {
|
|
6
7
|
abortSignal: AbortSignal;
|
|
7
8
|
controllerId: string;
|
|
8
9
|
casinoId: string;
|
|
9
10
|
graphqlClient: GraphQLClient;
|
|
11
|
+
pool: pg.Pool;
|
|
10
12
|
}): Promise<void>;
|
|
11
|
-
export declare function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }: {
|
|
13
|
+
export declare function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, pool, }: {
|
|
12
14
|
mpTakeRequestId: string;
|
|
13
15
|
mpTakeRequest?: MpTakeRequestFieldsFragment;
|
|
14
16
|
casinoId: string;
|
|
15
17
|
graphqlClient: GraphQLClient;
|
|
18
|
+
pool: pg.Pool;
|
|
16
19
|
}): Promise<string | null>;
|
|
20
|
+
export declare function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, pool, }: {
|
|
21
|
+
mpTakeRequestId: string;
|
|
22
|
+
takeRequestId: string;
|
|
23
|
+
mpTransferId: string;
|
|
24
|
+
graphqlClient: GraphQLClient;
|
|
25
|
+
casinoId: string;
|
|
26
|
+
pool: pg.Pool;
|
|
27
|
+
}): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { gql } from "../__generated__/gql.js";
|
|
2
2
|
import { TakeRequestStatus as MpTakeRequestStatus, TransferStatusKind as MpTransferStatus, } from "../__generated__/graphql.js";
|
|
3
|
-
import { exactlyOneRow, maybeOneRow,
|
|
3
|
+
import { exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
|
|
4
4
|
import { assert } from "tsafe";
|
|
5
5
|
import { PgAdvisoryLock } from "../pg-advisory-lock.js";
|
|
6
6
|
import { useFragment } from "../__generated__/fragment-masking.js";
|
|
@@ -124,10 +124,10 @@ const MP_COMPLETE_TRANSFER = gql(`
|
|
|
124
124
|
mutation CompleteTransfer2($mpTransferId: UUID!) {
|
|
125
125
|
completeTransfer(input: { id: $mpTransferId }) {
|
|
126
126
|
result {
|
|
127
|
+
__typename
|
|
127
128
|
... on CompleteTransferSuccess {
|
|
128
129
|
__typename
|
|
129
130
|
transfer {
|
|
130
|
-
__typename
|
|
131
131
|
id
|
|
132
132
|
status
|
|
133
133
|
... on ExperienceTransfer {
|
|
@@ -139,8 +139,10 @@ const MP_COMPLETE_TRANSFER = gql(`
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
... on InsufficientBalance {
|
|
143
|
+
message
|
|
144
|
+
}
|
|
142
145
|
... on InvalidTransferStatus {
|
|
143
|
-
__typename
|
|
144
146
|
currentStatus
|
|
145
147
|
message
|
|
146
148
|
transfer {
|
|
@@ -166,7 +168,7 @@ var LocalTakeRequestStatus;
|
|
|
166
168
|
LocalTakeRequestStatus["FAILED"] = "FAILED";
|
|
167
169
|
LocalTakeRequestStatus["REJECTED"] = "REJECTED";
|
|
168
170
|
})(LocalTakeRequestStatus || (LocalTakeRequestStatus = {}));
|
|
169
|
-
export async function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, }) {
|
|
171
|
+
export async function processTakeRequests({ abortSignal, controllerId, casinoId, graphqlClient, pool, }) {
|
|
170
172
|
if (abortSignal.aborted) {
|
|
171
173
|
return;
|
|
172
174
|
}
|
|
@@ -180,14 +182,16 @@ export async function processTakeRequests({ abortSignal, controllerId, casinoId,
|
|
|
180
182
|
mpTakeRequest: takeRequest,
|
|
181
183
|
casinoId,
|
|
182
184
|
graphqlClient,
|
|
185
|
+
pool,
|
|
183
186
|
});
|
|
184
187
|
}
|
|
185
188
|
await processPendingTransferCompletions({
|
|
186
189
|
casinoId,
|
|
187
190
|
graphqlClient,
|
|
188
191
|
abortSignal,
|
|
192
|
+
pool,
|
|
189
193
|
});
|
|
190
|
-
await processStuckRequests({ casinoId, graphqlClient, abortSignal });
|
|
194
|
+
await processStuckRequests({ casinoId, graphqlClient, abortSignal, pool });
|
|
191
195
|
}
|
|
192
196
|
async function fetchPendingTakeRequests(graphqlClient, controllerId) {
|
|
193
197
|
const result = await graphqlClient.request(MP_PAGINATE_PENDING_TAKE_REQUESTS, {
|
|
@@ -200,8 +204,8 @@ async function fetchPendingTakeRequests(graphqlClient, controllerId) {
|
|
|
200
204
|
return useFragment(MP_TAKE_REQUEST_FIELDS, takeRequest);
|
|
201
205
|
}) || []);
|
|
202
206
|
}
|
|
203
|
-
export async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }) {
|
|
204
|
-
return withPgPoolTransaction(
|
|
207
|
+
export async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, pool, }) {
|
|
208
|
+
return withPgPoolTransaction(pool, async (pgClient) => {
|
|
205
209
|
await PgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
206
210
|
mpTakeRequestId,
|
|
207
211
|
casinoId,
|
|
@@ -247,10 +251,7 @@ async function createAndProcessNewTakeRequest({ pgClient, mpTakeRequest, casinoI
|
|
|
247
251
|
currencyKey: mpTakeRequest.currencyKey,
|
|
248
252
|
casinoId,
|
|
249
253
|
});
|
|
250
|
-
|
|
251
|
-
await rejectMpTakeRequest(pgClient, graphqlClient, mpTakeRequest.id);
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
+
assert(dbUser && dbExperience && dbCurrency && dbBalance, "Required entities not found");
|
|
254
255
|
const amountToTransfer = Math.floor(typeof mpTakeRequest.amount === "number"
|
|
255
256
|
? Math.min(mpTakeRequest.amount, dbBalance.amount)
|
|
256
257
|
: dbBalance.amount);
|
|
@@ -434,11 +435,11 @@ async function attemptTransfer({ pgClient, takeRequestId, mpTakeRequestId, mpExp
|
|
|
434
435
|
return null;
|
|
435
436
|
}
|
|
436
437
|
}
|
|
437
|
-
async function processPendingTransferCompletions({ casinoId, graphqlClient, abortSignal, }) {
|
|
438
|
+
async function processPendingTransferCompletions({ casinoId, graphqlClient, abortSignal, pool, }) {
|
|
438
439
|
if (abortSignal.aborted) {
|
|
439
440
|
return;
|
|
440
441
|
}
|
|
441
|
-
const pendingCompletions = await
|
|
442
|
+
const pendingCompletions = await pool
|
|
442
443
|
.query({
|
|
443
444
|
text: `
|
|
444
445
|
SELECT id, mp_take_request_id, mp_transfer_id, transfer_completion_attempted_at
|
|
@@ -476,15 +477,29 @@ async function processPendingTransferCompletions({ casinoId, graphqlClient, abor
|
|
|
476
477
|
mpTransferId: request.mp_transfer_id,
|
|
477
478
|
graphqlClient,
|
|
478
479
|
casinoId,
|
|
480
|
+
pool,
|
|
479
481
|
});
|
|
480
482
|
}
|
|
481
483
|
}
|
|
482
|
-
async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, }) {
|
|
483
|
-
return withPgPoolTransaction(
|
|
484
|
+
export async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, pool, }) {
|
|
485
|
+
return withPgPoolTransaction(pool, async (pgClient) => {
|
|
484
486
|
await PgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
485
487
|
mpTakeRequestId,
|
|
486
488
|
casinoId,
|
|
487
489
|
});
|
|
490
|
+
const dbTakeRequest = await pgClient
|
|
491
|
+
.query({
|
|
492
|
+
text: `
|
|
493
|
+
SELECT transfer_needs_completion
|
|
494
|
+
FROM hub.take_request
|
|
495
|
+
WHERE id = $1
|
|
496
|
+
`,
|
|
497
|
+
values: [takeRequestId],
|
|
498
|
+
})
|
|
499
|
+
.then(exactlyOneRow);
|
|
500
|
+
if (!dbTakeRequest.transfer_needs_completion) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
488
503
|
await pgClient.query({
|
|
489
504
|
text: `
|
|
490
505
|
UPDATE hub.take_request
|
|
@@ -553,26 +568,57 @@ async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId,
|
|
|
553
568
|
if (!mpStatus) {
|
|
554
569
|
throw new Error("No MP status returned from MP API");
|
|
555
570
|
}
|
|
571
|
+
const takeRequestData = await pgClient
|
|
572
|
+
.query({
|
|
573
|
+
text: `
|
|
574
|
+
SELECT *
|
|
575
|
+
FROM hub.take_request
|
|
576
|
+
WHERE id = $1
|
|
577
|
+
`,
|
|
578
|
+
values: [takeRequestId],
|
|
579
|
+
})
|
|
580
|
+
.then(exactlyOneRow);
|
|
581
|
+
if (takeRequestData.refunded_at) {
|
|
582
|
+
logger.warn({ mpTransferId, takeRequestId }, `[completeTransfer] Transfer was already refunded. This should never happen.`);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
await pgClient.query({
|
|
586
|
+
text: `
|
|
587
|
+
UPDATE hub.balance
|
|
588
|
+
SET amount = amount + $1
|
|
589
|
+
WHERE user_id = $2
|
|
590
|
+
AND experience_id = $3
|
|
591
|
+
AND casino_id = $4
|
|
592
|
+
AND currency_key = $5
|
|
593
|
+
`,
|
|
594
|
+
values: [
|
|
595
|
+
takeRequestData.reserved_amount,
|
|
596
|
+
takeRequestData.user_id,
|
|
597
|
+
takeRequestData.experience_id,
|
|
598
|
+
takeRequestData.casino_id,
|
|
599
|
+
takeRequestData.currency_key,
|
|
600
|
+
],
|
|
601
|
+
});
|
|
556
602
|
await pgClient.query({
|
|
557
603
|
text: `
|
|
558
604
|
UPDATE hub.take_request
|
|
559
605
|
SET
|
|
560
606
|
transfer_needs_completion = FALSE,
|
|
561
607
|
mp_transfer_status = $2,
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
608
|
+
mp_status = $3,
|
|
609
|
+
debug = $4,
|
|
610
|
+
refunded_at = now(),
|
|
611
|
+
updated_at = now()
|
|
565
612
|
WHERE id = $1
|
|
566
613
|
`,
|
|
567
614
|
values: [
|
|
568
615
|
takeRequestId,
|
|
569
616
|
currentStatus,
|
|
570
|
-
LocalTakeRequestStatus.FAILED,
|
|
571
617
|
mpStatus,
|
|
572
|
-
`MP transfer was ${currentStatus}`,
|
|
618
|
+
`MP transfer was ${currentStatus}. Refunded ${takeRequestData.reserved_amount} to user`,
|
|
573
619
|
],
|
|
574
620
|
});
|
|
575
|
-
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}
|
|
621
|
+
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}. Refunded ${takeRequestData.reserved_amount} base units of ${takeRequestData.currency_key} to hub user.`);
|
|
576
622
|
}
|
|
577
623
|
else {
|
|
578
624
|
logger.info(`[completeTransfer] Transfer ${mpTransferId} has status ${currentStatus}, will retry later`);
|
|
@@ -636,11 +682,11 @@ async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId,
|
|
|
636
682
|
}
|
|
637
683
|
});
|
|
638
684
|
}
|
|
639
|
-
async function processStuckRequests({ casinoId, graphqlClient, abortSignal, }) {
|
|
685
|
+
async function processStuckRequests({ casinoId, graphqlClient, abortSignal, pool, }) {
|
|
640
686
|
if (abortSignal.aborted) {
|
|
641
687
|
return;
|
|
642
688
|
}
|
|
643
|
-
const stuckRequests = await
|
|
689
|
+
const stuckRequests = await pool
|
|
644
690
|
.query({
|
|
645
691
|
text: `
|
|
646
692
|
SELECT id, mp_take_request_id
|
|
@@ -665,6 +711,7 @@ async function processStuckRequests({ casinoId, graphqlClient, abortSignal, }) {
|
|
|
665
711
|
mpTakeRequestId: request.mp_take_request_id,
|
|
666
712
|
casinoId,
|
|
667
713
|
graphqlClient,
|
|
714
|
+
pool,
|
|
668
715
|
});
|
|
669
716
|
}
|
|
670
717
|
}
|