From 5fd2a93f056f253d8d08bea448e70232edd01de1 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 26 Mar 2024 17:01:37 -0600 Subject: [PATCH 1/2] feat(release): update 2007959 --- package.json | 4 ++-- src/constants.ts | 5 ++++ src/database/postgres.ts | 31 ++++++++++++++++++++++++- src/routes/checkBalance.ts | 29 +++++++++++++---------- src/routes/topUp.ts | 13 +++++++---- tests/helpers/signData.ts | 4 ++-- tests/helpers/testExpectations.ts | 4 ++-- yarn.lock | 38 +++++++++++++++++++------------ 8 files changed, 89 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 5dce594..0eaae39 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "@ardrive/ardrive-promise-cache": "^1.1.4", "@aws-sdk/client-secrets-manager": "^3.290.0", "@aws-sdk/client-ssm": "^3.369.0", - "@koa/cors": "^4.0.0", + "@koa/cors": "^5.0.0", "arweave": "^1.13.4", - "axios": "0.27.2", + "axios": "^1.6.7", "axios-retry": "^3.4.0", "bignumber.js": "^9.1.0", "dotenv": "^16.0.3", diff --git a/src/constants.ts b/src/constants.ts index ab151de..709782b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -322,3 +322,8 @@ export const giftingEmailAddress = /** gifting on top up via email depends on GIFTING_ENABLED="true" env var */ export const isGiftingEnabled = process.env.GIFTING_ENABLED === "true"; + +const thirtyMinutesMs = 1000 * 60 * 30; +export const topUpQuoteExpirationMs = +( + process.env.TOP_UP_QUOTE_EXPIRATION_MS ?? thirtyMinutesMs +); diff --git a/src/database/postgres.ts b/src/database/postgres.ts index d20af7b..48b6ea1 100644 --- a/src/database/postgres.ts +++ b/src/database/postgres.ts @@ -723,7 +723,36 @@ export class PostgresDatabase implements Database { adjustments = [], }: CreateBalanceReservationParams): Promise { await this.writer.transaction(async (knexTransaction) => { - const user = await this.getUser(userAddress, knexTransaction); + let user: User; + try { + user = await this.getUser(userAddress, knexTransaction); + } catch (error) { + if (error instanceof UserNotFoundWarning) { + if (reservedWincAmount.winc.isNonZeroNegativeInteger()) { + throw new UserNotFoundWarning(userAddress); + } + this.log.info( + `No user found with address '${userAddress}', but this reservation is free. Creating a new user without balance.` + ); + const userDbInsert: UserDBInsert = { + user_address: userAddress, + // TODO: user_address_type should be injected as a parameter + user_address_type: "arweave", + winston_credit_balance: "0", + }; + const dbResult = await knexTransaction(tableNames.user) + .insert(userDbInsert) + .returning("user_creation_date"); + user = { + userAddress, + winstonCreditBalance: new Winston("0"), + promotionalInfo: {}, + userAddressType: "arweave", + userCreationDate: dbResult[0].user_creation_date, + }; + } + throw error; // Re throw the error if it's not a UserNotFoundWarning + } const currentWinstonBalance = user.winstonCreditBalance; const newBalance = currentWinstonBalance.minus(reservedWincAmount.winc); diff --git a/src/routes/checkBalance.ts b/src/routes/checkBalance.ts index 80bd47c..0e8c981 100644 --- a/src/routes/checkBalance.ts +++ b/src/routes/checkBalance.ts @@ -19,6 +19,7 @@ import { Next } from "koa"; import { UserNotFoundWarning } from "../database/errors"; import { WincForBytesResponse } from "../pricing/pricing"; import { KoaContext } from "../server"; +import { W, Winston } from "../types"; import { validateAuthorizedRoute, validateByteCount, @@ -66,18 +67,22 @@ export async function checkBalance(ctx: KoaContext, next: Next) { const { finalPrice, adjustments } = priceWithAdjustments; try { - const userBalance = await paymentDatabase.getBalance(walletAddress); + let userBalance: Winston = W("0"); + // If price is more than 0, check if user has sufficient balance + if (finalPrice.winc.isNonZeroPositiveInteger() === true) { + userBalance = await paymentDatabase.getBalance(walletAddress); - if (finalPrice.winc.isGreaterThan(userBalance)) { - ctx.response.status = 402; - ctx.response.message = "Insufficient balance"; - ctx.body = { - userHasSufficientBalance: false, - bytesCostInWinc: finalPrice.winc.toString(), - userBalanceInWinc: userBalance.toString(), - adjustments: adjustments, - }; - return next(); + if (finalPrice.winc.isGreaterThan(userBalance)) { + ctx.response.status = 402; + ctx.response.message = "Insufficient balance"; + ctx.body = { + userHasSufficientBalance: false, + bytesCostInWinc: finalPrice.winc.toString(), + userBalanceInWinc: userBalance.toString(), + adjustments: adjustments, + }; + return next(); + } } ctx.response.status = 200; @@ -85,8 +90,8 @@ export async function checkBalance(ctx: KoaContext, next: Next) { ctx.body = { userHasSufficientBalance: true, bytesCostInWinc: finalPrice.winc.toString(), - userBalanceInWinc: userBalance.toString(), adjustments: adjustments, + userBalanceInWinc: userBalance.toString(), }; } catch (error: UserNotFoundWarning | unknown) { if (error instanceof UserNotFoundWarning) { diff --git a/src/routes/topUp.ts b/src/routes/topUp.ts index 1a12f7b..2c11c43 100644 --- a/src/routes/topUp.ts +++ b/src/routes/topUp.ts @@ -25,6 +25,7 @@ import { isGiftingEnabled, paymentIntentTopUpMethod, topUpMethods, + topUpQuoteExpirationMs, } from "../constants"; import { CreateTopUpQuoteParams } from "../database/dbTypes"; import { PaymentValidationError, PromoCodeError } from "../database/errors"; @@ -175,10 +176,10 @@ export async function topUp(ctx: KoaContext, next: Next) { return next(); } - const oneSecondMs = 1000; - const oneMinuteMs = oneSecondMs * 60; - const fiveMinutesMs = oneMinuteMs * 5; - const fiveMinutesFromNow = new Date(Date.now() + fiveMinutesMs).toISOString(); + const quoteExpirationDate = new Date( + Date.now() + topUpQuoteExpirationMs + ).toISOString(); + const quoteExpirationMs = new Date(quoteExpirationDate).getTime(); const { adjustments, @@ -198,7 +199,7 @@ export async function topUp(ctx: KoaContext, next: Next) { winstonCreditAmount: finalPrice.winc, destinationAddress, currencyType: payment.type, - quoteExpirationDate: fiveMinutesFromNow, + quoteExpirationDate, paymentProvider: "stripe", adjustments, giftMessage, @@ -272,6 +273,8 @@ export async function topUp(ctx: KoaContext, next: Next) { automatic_tax: { enabled: !!process.env.ENABLE_AUTO_STRIPE_TAX || false, }, + // Convert to stripe compatible timestamp, trim off precision + expires_at: Math.floor(quoteExpirationMs / 1000), payment_method_types: ["card"], line_items: [ { diff --git a/tests/helpers/signData.ts b/tests/helpers/signData.ts index 1afcf26..ff98b17 100644 --- a/tests/helpers/signData.ts +++ b/tests/helpers/signData.ts @@ -16,7 +16,7 @@ */ import Arweave from "arweave/node/common"; import { stringToBuffer } from "arweave/node/lib/utils"; -import { AxiosRequestHeaders } from "axios"; +import { RawAxiosRequestHeaders } from "axios"; import { Buffer } from "buffer"; import { randomUUID } from "crypto"; @@ -36,7 +36,7 @@ export async function signedRequestHeadersFromJwk( jwk: JWKInterface, nonce: string = randomUUID(), data = "" -): Promise { +): Promise { const signature = await signData(jwk, data + nonce); return { diff --git a/tests/helpers/testExpectations.ts b/tests/helpers/testExpectations.ts index 28a6399..c24fba1 100644 --- a/tests/helpers/testExpectations.ts +++ b/tests/helpers/testExpectations.ts @@ -14,11 +14,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { AxiosResponseHeaders } from "axios"; +import { RawAxiosRequestHeaders } from "axios"; import { expect } from "chai"; export function assertExpectedHeadersWithContentLength( - headers: AxiosResponseHeaders, + headers: RawAxiosRequestHeaders, contentLength: number ) { expect(headers.date).to.exist; diff --git a/yarn.lock b/yarn.lock index 0eaa50c..a8474bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1081,12 +1081,12 @@ __metadata: languageName: node linkType: hard -"@koa/cors@npm:^4.0.0": - version: 4.0.0 - resolution: "@koa/cors@npm:4.0.0" +"@koa/cors@npm:^5.0.0": + version: 5.0.0 + resolution: "@koa/cors@npm:5.0.0" dependencies: vary: ^1.1.2 - checksum: e0760544823532f2d71d792e3076858e38bab9b1c090abea175f1319fd91ea58a1da384a2fe7f5108f1c681e3830b01f62a1cafe271d6406751976af443187aa + checksum: 050701fb57dede2fefe0217459782bab7c9488fd07ff1f87fff680005cab43e03b7509e6015ea68082aadb1b31fe3eea7858ebdc93a2cf6f26d36d071190d50c languageName: node linkType: hard @@ -2478,13 +2478,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:0.27.2": - version: 0.27.2 - resolution: "axios@npm:0.27.2" +"axios@npm:^1.6.7": + version: 1.6.7 + resolution: "axios@npm:1.6.7" dependencies: - follow-redirects: ^1.14.9 + follow-redirects: ^1.15.4 form-data: ^4.0.0 - checksum: 38cb7540465fe8c4102850c4368053c21683af85c5fdf0ea619f9628abbcb59415d1e22ebc8a6390d2bbc9b58a9806c874f139767389c862ec9b772235f06854 + proxy-from-env: ^1.1.0 + checksum: 87d4d429927d09942771f3b3a6c13580c183e31d7be0ee12f09be6d5655304996bb033d85e54be81606f4e89684df43be7bf52d14becb73a12727bf33298a082 languageName: node linkType: hard @@ -3598,13 +3599,13 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.9": - version: 1.15.2 - resolution: "follow-redirects@npm:1.15.2" +"follow-redirects@npm:^1.15.4": + version: 1.15.5 + resolution: "follow-redirects@npm:1.15.5" peerDependenciesMeta: debug: optional: true - checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 + checksum: 5ca49b5ce6f44338cbfc3546823357e7a70813cecc9b7b768158a1d32c1e62e7407c944402a918ea8c38ae2e78266312d617dc68783fac502cbb55e1047b34ec languageName: node linkType: hard @@ -5650,7 +5651,7 @@ __metadata: "@aws-sdk/client-secrets-manager": ^3.290.0 "@aws-sdk/client-ssm": ^3.369.0 "@istanbuljs/nyc-config-typescript": ^1.0.2 - "@koa/cors": ^4.0.0 + "@koa/cors": ^5.0.0 "@trivago/prettier-plugin-sort-imports": ^3.3.0 "@types/bn.js": ^5.1.0 "@types/chai": ^4.3.1 @@ -5669,7 +5670,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.25.0 "@typescript-eslint/parser": ^5.25.0 arweave: ^1.13.4 - axios: 0.27.2 + axios: ^1.6.7 axios-mock-adapter: ^1.21.2 axios-retry: ^3.4.0 bignumber.js: ^9.1.0 @@ -5913,6 +5914,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 + languageName: node + linkType: hard + "pstree.remy@npm:^1.1.8": version: 1.1.8 resolution: "pstree.remy@npm:1.1.8" From 2fa3cced2b8ccfc07c32ffd65231885f96c7b725 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 2 Apr 2024 06:09:14 -0600 Subject: [PATCH 2/2] chore: fix adjustment tests --- tests/router.int.test.ts | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/router.int.test.ts b/tests/router.int.test.ts index 2378873..67c8962 100644 --- a/tests/router.int.test.ts +++ b/tests/router.int.test.ts @@ -245,19 +245,19 @@ describe("Router tests", () => { expect(data).to.deep.equal({ // No Infra Fee fiat: { - aud: 8.888074843239, - brl: 29.787061636803, - cad: 8.055890229538, - eur: 5.48212338304, - gbp: 4.770047888842, - hkd: 47.177146296308, - inr: 493.502634370348, - jpy: 809.295247212831, - sgd: 7.987256446965, - usd: 6.022614420805, + aud: 7.756335974575, + brl: 25.994207049929, + cad: 7.030115328307, + eur: 4.784072092426, + gbp: 4.162666797166, + hkd: 41.169972513698, + inr: 430.663816858609, + jpy: 706.245835090421, + sgd: 6.970220842017, + usd: 5.255741171961, }, // No Subsidy - winc: "857922282166", + winc: "748681078627", adjustments: [], }); clock.restore(); @@ -277,18 +277,18 @@ describe("Router tests", () => { expect(data).to.deep.equal({ // No Subsidy fiat: { - aud: 11.110093554049, - brl: 37.233827046004, - cad: 10.069862786923, - eur: 6.8526542288, - gbp: 5.962559861053, - hkd: 58.971432870385, - inr: 616.878292962935, - jpy: 1011.619059016038, - sgd: 9.984070558706, - usd: 7.528268026006, + aud: 9.695419968219, + brl: 32.492758812411, + cad: 8.787644160384, + eur: 5.980090115533, + gbp: 5.203333496457, + hkd: 51.462465642123, + inr: 538.329771073261, + jpy: 882.807293863027, + sgd: 8.712776052521, + usd: 6.569676464951, }, - winc: "857922282166", + winc: "748681078627", adjustments: [], }); clock.restore(); @@ -308,21 +308,21 @@ describe("Router tests", () => { expect(data.fiat).to.deep.equal({ // 23.4% Infra Fee applied - aud: 6.381776976212, - brl: 21.387576893252, - cad: 5.78425538674, - eur: 3.936250470849, - gbp: 3.424969110785, - hkd: 33.873930108293, - inr: 354.34262258945, - jpy: 581.086665752968, - sgd: 5.73497525565, - usd: 4.324331503186, + aud: 5.569170738913, + brl: 18.664247881764, + cad: 5.047732938069, + eur: 3.435038708654, + gbp: 2.988859971849, + hkd: 29.560685225179, + inr: 309.223367195491, + jpy: 507.095573497297, + sgd: 5.004727758618, + usd: 3.773704496831, }); // 45% Subsidy Event applied - expect(data.winc).to.equal("471857255191"); - expect(data.adjustments[0].adjustmentAmount).to.equal("-386065026975"); + expect(data.winc).to.equal("411774593244"); + expect(data.adjustments[0].adjustmentAmount).to.equal("-336906485383"); clock.restore(); });