From b80ce59804e8fdc47fde12a0ff80d81cec59fb87 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 26 Aug 2025 21:52:57 -0700 Subject: wip --- lib/square.ts | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 lib/square.ts (limited to 'lib/square.ts') diff --git a/lib/square.ts b/lib/square.ts new file mode 100644 index 00000000..e00f0a44 --- /dev/null +++ b/lib/square.ts @@ -0,0 +1,249 @@ +// squareSync.ts — Square v40+, per-user OAuth creds from Redis +import { SquareClient, SquareEnvironment, Square } from "square"; +import config from "$config"; +import { g, s } from "$lib/db"; +import { l, warn } from "$lib/logging"; + +// ----- Types coming from your code ----- +type CoinosInv = { + id: string; + currency: string | Square.Currency; + rate: number; // fiat per BTC captured at invoice time + items?: Array; + memo?: string; +}; + +type CoinosPayment = { + id: string; // Coinos payment UUID + iid: string; // invoice id + amount: number; // sats, excludes tip + tip: number; // sats + rate: number; // fiat per BTC (final) + currency: string | Square.Currency; + type: "bitcoin" | "liquid" | "lightning" | "internal" | string; + ref?: string; // on-chain: "...:txid:vout", LN: preimage etc. + payment_hash?: string; // LN hash if available + memo?: string; +}; + +type CoinosUser = { id: string; username?: string }; + +// ----- Helpers ----- +const toBigCents = (n: number) => BigInt(n); +const satsToCents = (sats: number, fiatPerBTC: number) => + Math.round((sats / 100_000_000) * fiatPerBTC * 100); + +const nowISO = () => new Date().toISOString(); +const isPast = (iso?: string, skewSeconds = 60) => + !!iso && Date.parse(iso) - skewSeconds * 1000 <= Date.now(); + +const normalizeCurrency = (c: string | Square.Currency): Square.Currency => + (typeof c === "string" ? c : String(c)).toUpperCase() as Square.Currency; + +const networkLabel = (t: CoinosPayment["type"]) => + t === "lightning" ? "LIGHTNING" : t === "bitcoin" || t === "liquid" ? "ONCHAIN" : "OTHER"; + +// ----- Per-user Square auth in Redis ----- +// Key: `${uid}:square` -> JSON: { accessToken, tokenType, expiresAt, merchantId, refreshToken, shortLived, refreshTokenExpiresAt } +type RedisSquareAuth = { + accessToken: string; + tokenType: "bearer"; + expiresAt?: string; // ISO + merchantId?: string; + refreshToken?: string; + shortLived?: boolean; + refreshTokenExpiresAt?: string; +}; + +async function loadSquareAuth(uid: string): Promise { + const raw = await g(`${uid}:square`); + if (!raw) return null; + return typeof raw === "string" ? JSON.parse(raw) : (raw as RedisSquareAuth); +} + +// Save back (preserve unknown fields) +async function saveSquareAuth(uid: string, next: Partial) { + const current = (await loadSquareAuth(uid)) || ({} as RedisSquareAuth); + const merged = { ...current, ...next }; + await s(`${uid}:square`, JSON.stringify(merged)); + return merged; +} + +// Create an SDK client for any token (or no token for OAuth) +function makeClient(token?: string) { + return new SquareClient({ + token: token ?? "", // OAuth obtainToken doesn’t require bearer auth + environment: + config.env === "production" + ? SquareEnvironment.Production + : SquareEnvironment.Sandbox, + }); +} + +// Refresh the user’s access token if needed +async function ensureFreshAccessToken(uid: string): Promise { + const auth = await loadSquareAuth(uid); + if (!auth) return null; + + // If no expiry recorded, just use the token as-is + if (!auth.expiresAt || !isPast(auth.expiresAt)) return auth; + + // Expired — try to refresh + if (!auth.refreshToken) { + warn("Square: token expired and no refresh token present", uid); + return auth; + } + if (!config.square?.clientId || !config.square?.clientSecret) { + warn("Square: missing clientId/clientSecret for OAuth refresh"); + return auth; + } + + try { + const oauth = makeClient().oAuth; // new SDK OAuth resource + const res = await oauth.obtainToken({ + clientId: config.square.clientId, + clientSecret: config.square.clientSecret, + grantType: "refresh_token", + refreshToken: auth.refreshToken, + }); + // Update stored creds (Square may rotate fields) + const updated = await saveSquareAuth(uid, { + accessToken: res.accessToken!, + tokenType: (res.tokenType as "bearer") ?? "bearer", + expiresAt: res.expiresAt, + merchantId: res.merchantId ?? auth.merchantId, + refreshToken: res.refreshToken ?? auth.refreshToken, + // shortLived is optional; keep existing if not provided + shortLived: typeof (res as any).shortLived === "boolean" ? (res as any).shortLived : auth.shortLived, + refreshTokenExpiresAt: (res as any).refreshTokenExpiresAt ?? auth.refreshTokenExpiresAt, + }); + l("Square OAuth refresh ok", { uid, oldExp: auth.expiresAt, newExp: updated.expiresAt }); + return updated; + } catch (e: any) { + warn("Square OAuth refresh failed", e?.message || e); + return auth; // fall back to (expired) auth — caller can decide to skip + } +} + +// Pick/cached location for the seller +async function getOrCacheLocationId(uid: string, client: SquareClient): Promise { + const cacheKey = `${uid}:square:locationId`; + const fromCache = await g(cacheKey); + if (typeof fromCache === "string" && fromCache) return fromCache; + if (fromCache && (fromCache as any).id) return (fromCache as any).id; + + // Query seller’s locations and choose the first ACTIVE (or first if none ACTIVE) + const list = await client.locations.list(); + // Auto-pagination can return a pager; handle both shapes + let firstActiveId: string | undefined; + if (Array.isArray((list as any).data)) { + const page = (list as any).data as Square.Location[]; + firstActiveId = page.find((loc) => loc.status === "ACTIVE")?.id || page[0]?.id; + } else { + const resp = list as { locations?: Square.Location[] }; + firstActiveId = resp.locations?.find((loc) => loc.status === "ACTIVE")?.id || resp.locations?.[0]?.id; + } + + if (!firstActiveId) { + warn("Square: no locations found for seller", uid); + return null; + } + + await s(cacheKey, firstActiveId); + return firstActiveId; +} + +// ----- Public entry: syncSquare ----- +export async function syncSquare(inv: CoinosInv, p: CoinosPayment, user: CoinosUser) { + // 1) Load/refresh per-user OAuth creds + const auth = await ensureFreshAccessToken(user.id); + if (!auth?.accessToken) { + warn("Square: no credentials for user; skipping sync", user.id); + return; + } + + // 2) Build a client for this seller and find a location + const client = makeClient(auth.accessToken); + const locationId = await getOrCacheLocationId(user.id, client); + if (!locationId) { + warn("Square: no locationId; skipping sync"); + return; + } + + // 3) Amounts and currency + const itemSats = p.amount || 0; + const tipSats = p.tip || 0; + const itemCents = satsToCents(itemSats, p.rate); + const tipCents = satsToCents(tipSats, p.rate); + const totalCents = itemCents + tipCents; + const cur = normalizeCurrency(p.currency || inv.currency); + + // 4) Create (or reuse) an Order + const orderResp = await client.orders.create({ + idempotencyKey: `coinos-order-${inv.id}`, + order: { + locationId, + metadata: { + coinos_invoice_id: inv.id, + coinos_payment_id: p.id, + coinos_user_id: user.id, + coinos_synced_at: nowISO(), + }, + lineItems: [ + { + name: inv.memo?.slice(0, 255) || "Coinos Order", + quantity: "1", + basePriceMoney: { amount: toBigCents(itemCents), currency: cur }, + }, + ], + ...(tipCents > 0 + ? { + serviceCharges: [ + { + name: "Tip", + calculationPhase: "TOTAL_PHASE", + taxable: false, + amountMoney: { amount: toBigCents(tipCents), currency: cur }, + }, + ], + } + : {}), + }, + }); + const orderId = orderResp.order?.id; + if (!orderId) throw new Error("Square: create order returned no order.id"); + + // 5) Record an EXTERNAL payment (approved, not autocompleted) + const short = (s?: string, n = 18) => (s ? (s.length > n ? s.slice(0, n) + "…" : s) : ""); + const refParts = (p.ref || "").split(":"); + const maybeTxid = refParts.length >= 2 ? refParts[refParts.length - 2] : undefined; + + const note = [ + `Coinos invoice ${inv.id}`, + p.payment_hash ? `hash ${short(p.payment_hash)}` : "", + maybeTxid ? `txid ${short(maybeTxid)}` : "", + inv.memo ? `memo ${short(inv.memo, 60)}` : "", + ].filter(Boolean).join(" | "); + + const payResp = await client.payments.create({ + idempotencyKey: `coinos-pay-${p.id}`, + locationId, + orderId, + sourceId: "EXTERNAL", + amountMoney: { amount: toBigCents(totalCents), currency: cur }, + autocomplete: false, // keep APPROVED; we’ll pay the order next + externalDetails: { type: "OTHER", source: `Coinos ${networkLabel(p.type)}` }, + note, + }); + const paymentId = payResp.payment?.id; + if (!paymentId) throw new Error("Square: create payment returned no payment.id"); + + // 6) Pay the order with our external payment + await client.orders.pay({ + orderId, + idempotencyKey: `coinos-payorder-${orderId}-${paymentId}`, + paymentIds: [paymentId], + }); + + l("Square sync complete", { orderId, paymentId, invoice: inv.id, user: user.id, locationId }); +} -- cgit v1.2.3 From fdfbb781f40775f39dbaa10269f559695f670b37 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 4 Sep 2025 08:20:11 -0700 Subject: create external payment --- index.ts | 1 + lib/square.ts | 1 - routes/square.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 2 deletions(-) (limited to 'lib/square.ts') diff --git a/index.ts b/index.ts index 02a6e0b5..d4748059 100644 --- a/index.ts +++ b/index.ts @@ -86,6 +86,7 @@ app.post("/replace", auth, payments.replace); app.get("/decode/:bolt11", payments.decode); app.post("/fetchinvoice", payments.fetchinvoice); +app.get("/square/sync", auth, square.sync); app.get("/square/connect", auth, square.connect); app.get("/square/auth", auth, square.auth); app.post("/square/payment", square.payment); diff --git a/lib/square.ts b/lib/square.ts index e00f0a44..11710774 100644 --- a/lib/square.ts +++ b/lib/square.ts @@ -153,7 +153,6 @@ async function getOrCacheLocationId(uid: string, client: SquareClient): Promise< return firstActiveId; } -// ----- Public entry: syncSquare ----- export async function syncSquare(inv: CoinosInv, p: CoinosPayment, user: CoinosUser) { // 1) Load/refresh per-user OAuth creds const auth = await ensureFreshAccessToken(user.id); diff --git a/routes/square.ts b/routes/square.ts index fd8c3abc..eaffff1e 100644 --- a/routes/square.ts +++ b/routes/square.ts @@ -7,8 +7,9 @@ import { sha256 } from "@noble/hashes/sha256"; import { utf8ToBytes } from "@noble/hashes/utils"; import { base64urlnopad as base64 } from "@scure/base"; import { SquareClient } from "square"; +import { v4 } from "uuid"; -const { scopes, url, appId, environment } = config.square; +const { scopes, url, appId, clientSecret, environment } = config.square; export default { async connect(req, res) { @@ -31,6 +32,62 @@ export default { ); }, + async sync(req, res) { + try { + const { user } = req; + let square = await g(`${user.id}:square`); + + let client = new SquareClient({ + environment, + }); + + const { refreshToken } = square; + + square = await client.oAuth.obtainToken({ + clientId: appId, + grantType: "refresh_token", + refreshToken, + }); + + await s(`${user.id}:square`, square); + + const { accessToken } = square; + + client = new SquareClient({ + token: accessToken, + environment, + }); + + // const payments = await authedClient.payments.list({}); + // console.log(payments); + const locs = await client.locations.list({}); + + const pay = await client.payments.create({ + sourceId: "EXTERNAL", + idempotencyKey: v4(), + amountMoney: { + amount: BigInt(1000), + currency: "CAD", + }, + // appFeeMoney: { + // amount: BigInt(10), + // currency: "CAD", + // }, + autocomplete: true, + externalDetails: { type: "OTHER", source: `Coinos ${v4()}` }, + locationId: locs.locations[0].id, + referenceId: v4(), + note: `Brief description ${v4()}`, + }); + + console.log(pay); + + res.send({}); + } catch (e) { + console.log(e); + } + }, + async auth(req, res) { const { user } = req; -- cgit v1.2.3 From 66ea0286057f43c699f4701cb4e444ff2308a414 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 6 Sep 2025 16:46:39 -0700 Subject: wip --- lib/payments.ts | 4 + lib/square.ts | 271 ++++++++------------------------------------------------ 2 files changed, 43 insertions(+), 232 deletions(-) (limited to 'lib/square.ts') diff --git a/lib/payments.ts b/lib/payments.ts index 655786fd..bd045f98 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -6,6 +6,7 @@ import ln from "$lib/ln"; import { err, l, warn } from "$lib/logging"; import { handleZap } from "$lib/nostr"; import { notify, nwcNotify } from "$lib/notifications"; +import { squarePayment } from "$lib/square"; import { SATS, btc, @@ -320,6 +321,9 @@ export const completePayment = async (inv, p, user) => { nwcNotify(p); notify(p, user, withdrawal); + + if (user.square) squarePayment(user); + l(username, "received", p.type, p.amount); callWebhook(inv, p); }; diff --git a/lib/square.ts b/lib/square.ts index 11710774..3fbc8774 100644 --- a/lib/square.ts +++ b/lib/square.ts @@ -1,248 +1,55 @@ -// squareSync.ts — Square v40+, per-user OAuth creds from Redis -import { SquareClient, SquareEnvironment, Square } from "square"; -import config from "$config"; import { g, s } from "$lib/db"; -import { l, warn } from "$lib/logging"; - -// ----- Types coming from your code ----- -type CoinosInv = { - id: string; - currency: string | Square.Currency; - rate: number; // fiat per BTC captured at invoice time - items?: Array; - memo?: string; -}; - -type CoinosPayment = { - id: string; // Coinos payment UUID - iid: string; // invoice id - amount: number; // sats, excludes tip - tip: number; // sats - rate: number; // fiat per BTC (final) - currency: string | Square.Currency; - type: "bitcoin" | "liquid" | "lightning" | "internal" | string; - ref?: string; // on-chain: "...:txid:vout", LN: preimage etc. - payment_hash?: string; // LN hash if available - memo?: string; -}; - -type CoinosUser = { id: string; username?: string }; - -// ----- Helpers ----- -const toBigCents = (n: number) => BigInt(n); -const satsToCents = (sats: number, fiatPerBTC: number) => - Math.round((sats / 100_000_000) * fiatPerBTC * 100); - -const nowISO = () => new Date().toISOString(); -const isPast = (iso?: string, skewSeconds = 60) => - !!iso && Date.parse(iso) - skewSeconds * 1000 <= Date.now(); - -const normalizeCurrency = (c: string | Square.Currency): Square.Currency => - (typeof c === "string" ? c : String(c)).toUpperCase() as Square.Currency; - -const networkLabel = (t: CoinosPayment["type"]) => - t === "lightning" ? "LIGHTNING" : t === "bitcoin" || t === "liquid" ? "ONCHAIN" : "OTHER"; - -// ----- Per-user Square auth in Redis ----- -// Key: `${uid}:square` -> JSON: { accessToken, tokenType, expiresAt, merchantId, refreshToken, shortLived, refreshTokenExpiresAt } -type RedisSquareAuth = { - accessToken: string; - tokenType: "bearer"; - expiresAt?: string; // ISO - merchantId?: string; - refreshToken?: string; - shortLived?: boolean; - refreshTokenExpiresAt?: string; -}; +import config from "$config"; +import { SquareClient } from "square"; +import { v4 } from "uuid"; -async function loadSquareAuth(uid: string): Promise { - const raw = await g(`${uid}:square`); - if (!raw) return null; - return typeof raw === "string" ? JSON.parse(raw) : (raw as RedisSquareAuth); -} +const { appId, environment } = config.square; -// Save back (preserve unknown fields) -async function saveSquareAuth(uid: string, next: Partial) { - const current = (await loadSquareAuth(uid)) || ({} as RedisSquareAuth); - const merged = { ...current, ...next }; - await s(`${uid}:square`, JSON.stringify(merged)); - return merged; -} +export const squarePayment = async (user) => { + let square = await g(`${user.id}:square`); -// Create an SDK client for any token (or no token for OAuth) -function makeClient(token?: string) { - return new SquareClient({ - token: token ?? "", // OAuth obtainToken doesn’t require bearer auth - environment: - config.env === "production" - ? SquareEnvironment.Production - : SquareEnvironment.Sandbox, + let client = new SquareClient({ + environment, }); -} - -// Refresh the user’s access token if needed -async function ensureFreshAccessToken(uid: string): Promise { - const auth = await loadSquareAuth(uid); - if (!auth) return null; - // If no expiry recorded, just use the token as-is - if (!auth.expiresAt || !isPast(auth.expiresAt)) return auth; + const { refreshToken } = square; - // Expired — try to refresh - if (!auth.refreshToken) { - warn("Square: token expired and no refresh token present", uid); - return auth; - } - if (!config.square?.clientId || !config.square?.clientSecret) { - warn("Square: missing clientId/clientSecret for OAuth refresh"); - return auth; - } - - try { - const oauth = makeClient().oAuth; // new SDK OAuth resource - const res = await oauth.obtainToken({ - clientId: config.square.clientId, - clientSecret: config.square.clientSecret, - grantType: "refresh_token", - refreshToken: auth.refreshToken, - }); - // Update stored creds (Square may rotate fields) - const updated = await saveSquareAuth(uid, { - accessToken: res.accessToken!, - tokenType: (res.tokenType as "bearer") ?? "bearer", - expiresAt: res.expiresAt, - merchantId: res.merchantId ?? auth.merchantId, - refreshToken: res.refreshToken ?? auth.refreshToken, - // shortLived is optional; keep existing if not provided - shortLived: typeof (res as any).shortLived === "boolean" ? (res as any).shortLived : auth.shortLived, - refreshTokenExpiresAt: (res as any).refreshTokenExpiresAt ?? auth.refreshTokenExpiresAt, - }); - l("Square OAuth refresh ok", { uid, oldExp: auth.expiresAt, newExp: updated.expiresAt }); - return updated; - } catch (e: any) { - warn("Square OAuth refresh failed", e?.message || e); - return auth; // fall back to (expired) auth — caller can decide to skip - } -} - -// Pick/cached location for the seller -async function getOrCacheLocationId(uid: string, client: SquareClient): Promise { - const cacheKey = `${uid}:square:locationId`; - const fromCache = await g(cacheKey); - if (typeof fromCache === "string" && fromCache) return fromCache; - if (fromCache && (fromCache as any).id) return (fromCache as any).id; - - // Query seller’s locations and choose the first ACTIVE (or first if none ACTIVE) - const list = await client.locations.list(); - // Auto-pagination can return a pager; handle both shapes - let firstActiveId: string | undefined; - if (Array.isArray((list as any).data)) { - const page = (list as any).data as Square.Location[]; - firstActiveId = page.find((loc) => loc.status === "ACTIVE")?.id || page[0]?.id; - } else { - const resp = list as { locations?: Square.Location[] }; - firstActiveId = resp.locations?.find((loc) => loc.status === "ACTIVE")?.id || resp.locations?.[0]?.id; - } - - if (!firstActiveId) { - warn("Square: no locations found for seller", uid); - return null; - } - - await s(cacheKey, firstActiveId); - return firstActiveId; -} - -export async function syncSquare(inv: CoinosInv, p: CoinosPayment, user: CoinosUser) { - // 1) Load/refresh per-user OAuth creds - const auth = await ensureFreshAccessToken(user.id); - if (!auth?.accessToken) { - warn("Square: no credentials for user; skipping sync", user.id); - return; - } + square = await client.oAuth.obtainToken({ + clientId: appId, + grantType: "refresh_token", + refreshToken, + }); - // 2) Build a client for this seller and find a location - const client = makeClient(auth.accessToken); - const locationId = await getOrCacheLocationId(user.id, client); - if (!locationId) { - warn("Square: no locationId; skipping sync"); - return; - } + await s(`${user.id}:square`, square); - // 3) Amounts and currency - const itemSats = p.amount || 0; - const tipSats = p.tip || 0; - const itemCents = satsToCents(itemSats, p.rate); - const tipCents = satsToCents(tipSats, p.rate); - const totalCents = itemCents + tipCents; - const cur = normalizeCurrency(p.currency || inv.currency); + const { accessToken } = square; - // 4) Create (or reuse) an Order - const orderResp = await client.orders.create({ - idempotencyKey: `coinos-order-${inv.id}`, - order: { - locationId, - metadata: { - coinos_invoice_id: inv.id, - coinos_payment_id: p.id, - coinos_user_id: user.id, - coinos_synced_at: nowISO(), - }, - lineItems: [ - { - name: inv.memo?.slice(0, 255) || "Coinos Order", - quantity: "1", - basePriceMoney: { amount: toBigCents(itemCents), currency: cur }, - }, - ], - ...(tipCents > 0 - ? { - serviceCharges: [ - { - name: "Tip", - calculationPhase: "TOTAL_PHASE", - taxable: false, - amountMoney: { amount: toBigCents(tipCents), currency: cur }, - }, - ], - } - : {}), - }, + client = new SquareClient({ + token: accessToken, + environment, }); - const orderId = orderResp.order?.id; - if (!orderId) throw new Error("Square: create order returned no order.id"); - - // 5) Record an EXTERNAL payment (approved, not autocompleted) - const short = (s?: string, n = 18) => (s ? (s.length > n ? s.slice(0, n) + "…" : s) : ""); - const refParts = (p.ref || "").split(":"); - const maybeTxid = refParts.length >= 2 ? refParts[refParts.length - 2] : undefined; - const note = [ - `Coinos invoice ${inv.id}`, - p.payment_hash ? `hash ${short(p.payment_hash)}` : "", - maybeTxid ? `txid ${short(maybeTxid)}` : "", - inv.memo ? `memo ${short(inv.memo, 60)}` : "", - ].filter(Boolean).join(" | "); + // const payments = await authedClient.payments.list({}); + // console.log(payments); + const locs = await client.locations.list({}); - const payResp = await client.payments.create({ - idempotencyKey: `coinos-pay-${p.id}`, - locationId, - orderId, + const pay = await client.payments.create({ sourceId: "EXTERNAL", - amountMoney: { amount: toBigCents(totalCents), currency: cur }, - autocomplete: false, // keep APPROVED; we’ll pay the order next - externalDetails: { type: "OTHER", source: `Coinos ${networkLabel(p.type)}` }, - note, - }); - const paymentId = payResp.payment?.id; - if (!paymentId) throw new Error("Square: create payment returned no payment.id"); - - // 6) Pay the order with our external payment - await client.orders.pay({ - orderId, - idempotencyKey: `coinos-payorder-${orderId}-${paymentId}`, - paymentIds: [paymentId], + idempotencyKey: v4(), + amountMoney: { + amount: BigInt(1000), + currency: "CAD", + }, + // appFeeMoney: { + // amount: BigInt(10), + // currency: "CAD", + // }, + autocomplete: true, + externalDetails: { type: "OTHER", source: `Coinos ${v4()}` }, + locationId: locs.locations[0].id, + referenceId: v4(), + note: `Brief description ${v4()}`, }); - l("Square sync complete", { orderId, paymentId, invoice: inv.id, user: user.id, locationId }); -} + console.log(pay); +}; -- cgit v1.2.3 From b2f88134a3828d2df7271c7d0ae4219e5e6f7d56 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 8 Sep 2025 09:26:47 -0700 Subject: square payments --- lib/payments.ts | 2 +- lib/square.ts | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'lib/square.ts') diff --git a/lib/payments.ts b/lib/payments.ts index bd045f98..be801622 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -322,7 +322,7 @@ export const completePayment = async (inv, p, user) => { nwcNotify(p); notify(p, user, withdrawal); - if (user.square) squarePayment(user); + squarePayment(p, user); l(username, "received", p.type, p.amount); callWebhook(inv, p); diff --git a/lib/square.ts b/lib/square.ts index 3fbc8774..f491effe 100644 --- a/lib/square.ts +++ b/lib/square.ts @@ -1,12 +1,14 @@ -import { g, s } from "$lib/db"; import config from "$config"; +import { g, s } from "$lib/db"; +import { fiat } from "$lib/utils"; import { SquareClient } from "square"; import { v4 } from "uuid"; const { appId, environment } = config.square; -export const squarePayment = async (user) => { +export const squarePayment = async (p, user) => { let square = await g(`${user.id}:square`); + if (!square) return; let client = new SquareClient({ environment, @@ -29,27 +31,25 @@ export const squarePayment = async (user) => { environment, }); - // const payments = await authedClient.payments.list({}); - // console.log(payments); const locs = await client.locations.list({}); - const pay = await client.payments.create({ + await client.payments.create({ sourceId: "EXTERNAL", idempotencyKey: v4(), amountMoney: { - amount: BigInt(1000), - currency: "CAD", + amount: BigInt(parseFloat(fiat(p.amount, p.rate).toFixed(2)) * 100), + currency: p.currency, }, - // appFeeMoney: { - // amount: BigInt(10), - // currency: "CAD", - // }, + tipMoney: p.tip + ? { + amount: BigInt(parseFloat(fiat(p.tip, p.rate).toFixed(2)) * 100), + currency: p.currency, + } + : undefined, autocomplete: true, - externalDetails: { type: "OTHER", source: `Coinos ${v4()}` }, + externalDetails: { type: "OTHER", source: `Coinos payment ${p.id}` }, locationId: locs.locations[0].id, referenceId: v4(), - note: `Brief description ${v4()}`, + note: `Coinos payment ${p.id}`, }); - - console.log(pay); }; -- cgit v1.2.3