rfc 6570 callback templates and callback bearer tokens
This commit is contained in:
parent
144de45075
commit
8005b2a3ff
16 changed files with 222 additions and 84 deletions
|
|
@ -31,13 +31,14 @@ import { DebitToPub1727105758354 } from './build/src/services/storage/migrations
|
|||
import { UserCbUrl1727112281043 } from './build/src/services/storage/migrations/1727112281043-user_cb_url.js'
|
||||
import { UserOffer1733502626042 } from './build/src/services/storage/migrations/1733502626042-user_offer.js'
|
||||
import { ManagementGrant1751307732346 } from './build/src/services/storage/migrations/1751307732346-management_grant.js'
|
||||
import { InvoiceCallbackUrls1752425992291 } from './build/src/services/storage/migrations/1752425992291-invoice_callback_urls.js'
|
||||
export default new DataSource({
|
||||
type: "sqlite",
|
||||
database: "db.sqlite",
|
||||
// logging: true,
|
||||
migrations: [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189, CreateInviteTokenTable1721751414878,
|
||||
PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264, DebitToPub1727105758354, UserCbUrl1727112281043,
|
||||
UserOffer1733502626042, ManagementGrant1751307732346],
|
||||
UserOffer1733502626042, ManagementGrant1751307732346, InvoiceCallbackUrls1752425992291],
|
||||
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment,
|
||||
UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment, LspOrder, LndNodeInfo, TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant],
|
||||
// synchronize: true,
|
||||
|
|
|
|||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -44,6 +44,7 @@
|
|||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "0.3.15",
|
||||
"typescript": "^5.5.4",
|
||||
"uri-template": "^2.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"websocket": "^1.0.35",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
|
|
@ -4744,6 +4745,12 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/pct-encode": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pct-encode/-/pct-encode-1.0.3.tgz",
|
||||
"integrity": "sha512-+ojEvSHApoLWF2YYxwnOM4N9DPn5e5fG+j0YJ9drKNaYtrZYOq5M9ESOaBYqOHCXOAALODJJ4wkqHAXEuLpwMw==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
|
@ -6486,6 +6493,15 @@
|
|||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-template": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uri-template/-/uri-template-2.0.0.tgz",
|
||||
"integrity": "sha512-r/i44nPoo0ktEZDjx+hxp9PSjQuBBfsd6RgCRuuMqCP0FZEp+YE0SpihThI4UGc5ePqQEFsdyZc7UVlowp+LLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pct-encode": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@
|
|||
"ts-proto": "^1.131.2",
|
||||
"typeorm": "0.3.15",
|
||||
"typescript": "^5.5.4",
|
||||
"uri-template": "^2.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"websocket": "^1.0.35",
|
||||
"websocket-polyfill": "^0.0.3",
|
||||
|
|
|
|||
|
|
@ -1021,6 +1021,8 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __payer_data__: _[PayerData](#PayerData)_ *this field is optional
|
||||
- __payer_identifier__: _string_
|
||||
- __receiver_identifier__: _string_
|
||||
- __rejectUnauthorized__: _boolean_ *this field is optional
|
||||
- __token__: _string_ *this field is optional
|
||||
|
||||
### AddAppUserRequest
|
||||
- __balance__: _number_
|
||||
|
|
@ -1383,12 +1385,16 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
|
||||
### OfferConfig
|
||||
- __callback_url__: _string_
|
||||
- __createdAtUnix__: _number_
|
||||
- __default_offer__: _boolean_
|
||||
- __expected_data__: MAP with key: _string_ and value: _[OfferDataType](#OfferDataType)_
|
||||
- __label__: _string_
|
||||
- __noffer__: _string_
|
||||
- __offer_id__: _string_
|
||||
- __payer_data__: ARRAY of: _string_
|
||||
- __price_sats__: _number_
|
||||
- __rejectUnauthorized__: _boolean_
|
||||
- __token__: _string_
|
||||
- __updatedAtUnix__: _number_
|
||||
|
||||
### OfferId
|
||||
- __offer_id__: _string_
|
||||
|
|
@ -1631,9 +1637,6 @@ The nostr server will send back a message response, and inside the body there wi
|
|||
- __MONTH__
|
||||
- __WEEK__
|
||||
|
||||
### OfferDataType
|
||||
- __DATA_STRING__
|
||||
|
||||
### OperationType
|
||||
- __CHAIN_OP__
|
||||
- __INVOICE_OP__
|
||||
|
|
|
|||
|
|
@ -65,12 +65,6 @@ const (
|
|||
WEEK IntervalType = "WEEK"
|
||||
)
|
||||
|
||||
type OfferDataType string
|
||||
|
||||
const (
|
||||
DATA_STRING OfferDataType = "DATA_STRING"
|
||||
)
|
||||
|
||||
type OperationType string
|
||||
|
||||
const (
|
||||
|
|
@ -112,6 +106,8 @@ type AddAppUserInvoiceRequest struct {
|
|||
Payer_data *PayerData `json:"payer_data"`
|
||||
Payer_identifier string `json:"payer_identifier"`
|
||||
Receiver_identifier string `json:"receiver_identifier"`
|
||||
Rejectunauthorized bool `json:"rejectUnauthorized"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
type AddAppUserRequest struct {
|
||||
Balance int64 `json:"balance"`
|
||||
|
|
@ -473,13 +469,17 @@ type NewInvoiceResponse struct {
|
|||
Invoice string `json:"invoice"`
|
||||
}
|
||||
type OfferConfig struct {
|
||||
Callback_url string `json:"callback_url"`
|
||||
Default_offer bool `json:"default_offer"`
|
||||
Expected_data map[string]OfferDataType `json:"expected_data"`
|
||||
Label string `json:"label"`
|
||||
Noffer string `json:"noffer"`
|
||||
Offer_id string `json:"offer_id"`
|
||||
Price_sats int64 `json:"price_sats"`
|
||||
Callback_url string `json:"callback_url"`
|
||||
Createdatunix int64 `json:"createdAtUnix"`
|
||||
Default_offer bool `json:"default_offer"`
|
||||
Label string `json:"label"`
|
||||
Noffer string `json:"noffer"`
|
||||
Offer_id string `json:"offer_id"`
|
||||
Payer_data []string `json:"payer_data"`
|
||||
Price_sats int64 `json:"price_sats"`
|
||||
Rejectunauthorized bool `json:"rejectUnauthorized"`
|
||||
Token string `json:"token"`
|
||||
Updatedatunix int64 `json:"updatedAtUnix"`
|
||||
}
|
||||
type OfferId struct {
|
||||
Offer_id string `json:"offer_id"`
|
||||
|
|
|
|||
|
|
@ -429,13 +429,6 @@ export const enumCheckIntervalType = (e?: IntervalType): boolean => {
|
|||
for (const v in IntervalType) if (e === v) return true
|
||||
return false
|
||||
}
|
||||
export enum OfferDataType {
|
||||
DATA_STRING = 'DATA_STRING',
|
||||
}
|
||||
export const enumCheckOfferDataType = (e?: OfferDataType): boolean => {
|
||||
for (const v in OfferDataType) if (e === v) return true
|
||||
return false
|
||||
}
|
||||
export enum OperationType {
|
||||
CHAIN_OP = 'CHAIN_OP',
|
||||
INVOICE_OP = 'INVOICE_OP',
|
||||
|
|
@ -528,9 +521,11 @@ export type AddAppUserInvoiceRequest = {
|
|||
payer_data?: PayerData
|
||||
payer_identifier: string
|
||||
receiver_identifier: string
|
||||
rejectUnauthorized?: boolean
|
||||
token?: string
|
||||
}
|
||||
export type AddAppUserInvoiceRequestOptionalField = 'offer_string' | 'payer_data'
|
||||
export const AddAppUserInvoiceRequestOptionalFields: AddAppUserInvoiceRequestOptionalField[] = ['offer_string', 'payer_data']
|
||||
export type AddAppUserInvoiceRequestOptionalField = 'offer_string' | 'payer_data' | 'rejectUnauthorized' | 'token'
|
||||
export const AddAppUserInvoiceRequestOptionalFields: AddAppUserInvoiceRequestOptionalField[] = ['offer_string', 'payer_data', 'rejectUnauthorized', 'token']
|
||||
export type AddAppUserInvoiceRequestOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: AddAppUserInvoiceRequestOptionalField[]
|
||||
http_callback_url_CustomCheck?: (v: string) => boolean
|
||||
|
|
@ -539,6 +534,8 @@ export type AddAppUserInvoiceRequestOptions = OptionsBaseMessage & {
|
|||
payer_data_Options?: PayerDataOptions
|
||||
payer_identifier_CustomCheck?: (v: string) => boolean
|
||||
receiver_identifier_CustomCheck?: (v: string) => boolean
|
||||
rejectUnauthorized_CustomCheck?: (v?: boolean) => boolean
|
||||
token_CustomCheck?: (v?: string) => boolean
|
||||
}
|
||||
export const AddAppUserInvoiceRequestValidate = (o?: AddAppUserInvoiceRequest, opts: AddAppUserInvoiceRequestOptions = {}, path: string = 'AddAppUserInvoiceRequest::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
|
|
@ -566,6 +563,12 @@ export const AddAppUserInvoiceRequestValidate = (o?: AddAppUserInvoiceRequest, o
|
|||
if (typeof o.receiver_identifier !== 'string') return new Error(`${path}.receiver_identifier: is not a string`)
|
||||
if (opts.receiver_identifier_CustomCheck && !opts.receiver_identifier_CustomCheck(o.receiver_identifier)) return new Error(`${path}.receiver_identifier: custom check failed`)
|
||||
|
||||
if ((o.rejectUnauthorized || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('rejectUnauthorized')) && typeof o.rejectUnauthorized !== 'boolean') return new Error(`${path}.rejectUnauthorized: is not a boolean`)
|
||||
if (opts.rejectUnauthorized_CustomCheck && !opts.rejectUnauthorized_CustomCheck(o.rejectUnauthorized)) return new Error(`${path}.rejectUnauthorized: custom check failed`)
|
||||
|
||||
if ((o.token || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('token')) && typeof o.token !== 'string') return new Error(`${path}.token: is not a string`)
|
||||
if (opts.token_CustomCheck && !opts.token_CustomCheck(o.token)) return new Error(`${path}.token: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -2754,23 +2757,31 @@ export const NewInvoiceResponseValidate = (o?: NewInvoiceResponse, opts: NewInvo
|
|||
|
||||
export type OfferConfig = {
|
||||
callback_url: string
|
||||
createdAtUnix: number
|
||||
default_offer: boolean
|
||||
expected_data: Record<string, OfferDataType>
|
||||
label: string
|
||||
noffer: string
|
||||
offer_id: string
|
||||
payer_data: string[]
|
||||
price_sats: number
|
||||
rejectUnauthorized: boolean
|
||||
token: string
|
||||
updatedAtUnix: number
|
||||
}
|
||||
export const OfferConfigOptionalFields: [] = []
|
||||
export type OfferConfigOptions = OptionsBaseMessage & {
|
||||
checkOptionalsAreSet?: []
|
||||
callback_url_CustomCheck?: (v: string) => boolean
|
||||
createdAtUnix_CustomCheck?: (v: number) => boolean
|
||||
default_offer_CustomCheck?: (v: boolean) => boolean
|
||||
expected_data_CustomCheck?: (v: Record<string, OfferDataType>) => boolean
|
||||
label_CustomCheck?: (v: string) => boolean
|
||||
noffer_CustomCheck?: (v: string) => boolean
|
||||
offer_id_CustomCheck?: (v: string) => boolean
|
||||
payer_data_CustomCheck?: (v: string[]) => boolean
|
||||
price_sats_CustomCheck?: (v: number) => boolean
|
||||
rejectUnauthorized_CustomCheck?: (v: boolean) => boolean
|
||||
token_CustomCheck?: (v: string) => boolean
|
||||
updatedAtUnix_CustomCheck?: (v: number) => boolean
|
||||
}
|
||||
export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions = {}, path: string = 'OfferConfig::root.'): Error | null => {
|
||||
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
|
||||
|
|
@ -2779,14 +2790,12 @@ export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions =
|
|||
if (typeof o.callback_url !== 'string') return new Error(`${path}.callback_url: is not a string`)
|
||||
if (opts.callback_url_CustomCheck && !opts.callback_url_CustomCheck(o.callback_url)) return new Error(`${path}.callback_url: custom check failed`)
|
||||
|
||||
if (typeof o.createdAtUnix !== 'number') return new Error(`${path}.createdAtUnix: is not a number`)
|
||||
if (opts.createdAtUnix_CustomCheck && !opts.createdAtUnix_CustomCheck(o.createdAtUnix)) return new Error(`${path}.createdAtUnix: custom check failed`)
|
||||
|
||||
if (typeof o.default_offer !== 'boolean') return new Error(`${path}.default_offer: is not a boolean`)
|
||||
if (opts.default_offer_CustomCheck && !opts.default_offer_CustomCheck(o.default_offer)) return new Error(`${path}.default_offer: custom check failed`)
|
||||
|
||||
if (typeof o.expected_data !== 'object' || o.expected_data === null) return new Error(`${path}.expected_data: is not an object or is null`)
|
||||
for (const key in o.expected_data) {
|
||||
if (!enumCheckOfferDataType(o.expected_data[key])) return new Error(`${path}.expected_data['${key}']: is not a OfferDataType`)
|
||||
}
|
||||
|
||||
if (typeof o.label !== 'string') return new Error(`${path}.label: is not a string`)
|
||||
if (opts.label_CustomCheck && !opts.label_CustomCheck(o.label)) return new Error(`${path}.label: custom check failed`)
|
||||
|
||||
|
|
@ -2796,9 +2805,24 @@ export const OfferConfigValidate = (o?: OfferConfig, opts: OfferConfigOptions =
|
|||
if (typeof o.offer_id !== 'string') return new Error(`${path}.offer_id: is not a string`)
|
||||
if (opts.offer_id_CustomCheck && !opts.offer_id_CustomCheck(o.offer_id)) return new Error(`${path}.offer_id: custom check failed`)
|
||||
|
||||
if (!Array.isArray(o.payer_data)) return new Error(`${path}.payer_data: is not an array`)
|
||||
for (let index = 0; index < o.payer_data.length; index++) {
|
||||
if (typeof o.payer_data[index] !== 'string') return new Error(`${path}.payer_data[${index}]: is not a string`)
|
||||
}
|
||||
if (opts.payer_data_CustomCheck && !opts.payer_data_CustomCheck(o.payer_data)) return new Error(`${path}.payer_data: custom check failed`)
|
||||
|
||||
if (typeof o.price_sats !== 'number') return new Error(`${path}.price_sats: is not a number`)
|
||||
if (opts.price_sats_CustomCheck && !opts.price_sats_CustomCheck(o.price_sats)) return new Error(`${path}.price_sats: custom check failed`)
|
||||
|
||||
if (typeof o.rejectUnauthorized !== 'boolean') return new Error(`${path}.rejectUnauthorized: is not a boolean`)
|
||||
if (opts.rejectUnauthorized_CustomCheck && !opts.rejectUnauthorized_CustomCheck(o.rejectUnauthorized)) return new Error(`${path}.rejectUnauthorized: custom check failed`)
|
||||
|
||||
if (typeof o.token !== 'string') return new Error(`${path}.token: is not a string`)
|
||||
if (opts.token_CustomCheck && !opts.token_CustomCheck(o.token)) return new Error(`${path}.token: custom check failed`)
|
||||
|
||||
if (typeof o.updatedAtUnix !== 'number') return new Error(`${path}.updatedAtUnix: is not a number`)
|
||||
if (opts.updatedAtUnix_CustomCheck && !opts.updatedAtUnix_CustomCheck(o.updatedAtUnix)) return new Error(`${path}.updatedAtUnix: custom check failed`)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -377,6 +377,9 @@ message AddAppUserInvoiceRequest {
|
|||
NewInvoiceRequest invoice_req = 4;
|
||||
optional PayerData payer_data = 5;
|
||||
optional string offer_string = 6;
|
||||
optional bool rejectUnauthorized = 7;
|
||||
optional string token = 8;
|
||||
|
||||
}
|
||||
|
||||
message GetAppUserRequest {
|
||||
|
|
@ -753,9 +756,6 @@ message DebitResponse {
|
|||
}
|
||||
}
|
||||
|
||||
enum OfferDataType {
|
||||
DATA_STRING = 0;
|
||||
}
|
||||
|
||||
message OfferId {
|
||||
string offer_id = 1;
|
||||
|
|
@ -766,9 +766,13 @@ message OfferConfig {
|
|||
string label = 2;
|
||||
int64 price_sats = 3;
|
||||
string callback_url = 4;
|
||||
map<string, OfferDataType> expected_data = 5;
|
||||
repeated string payer_data = 5;
|
||||
string noffer = 6;
|
||||
bool default_offer = 7;
|
||||
string token = 8;
|
||||
bool rejectUnauthorized = 9;
|
||||
int64 createdAtUnix = 10;
|
||||
int64 updatedAtUnix = 11;
|
||||
}
|
||||
|
||||
message UserOffers {
|
||||
|
|
|
|||
|
|
@ -193,7 +193,8 @@ export default class {
|
|||
}
|
||||
const opts: InboundOptionals = {
|
||||
callbackUrl: cbUrl, expiry: defaultInvoiceExpiry, expectedPayer: payer.user, linkedApplication: app, zapInfo,
|
||||
offerId: req.offer_string, payerData: req.payer_data?.data
|
||||
offerId: req.offer_string, payerData: req.payer_data?.data, rejectUnauthorized: req.rejectUnauthorized,
|
||||
token: req.token
|
||||
}
|
||||
const appUserInvoice = await this.paymentManager.NewInvoice(receiver.user.user_id, req.invoice_req, opts)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ import { Unlocker } from "./unlocker.js"
|
|||
import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
|
||||
import { DebitManager } from "./debitManager.js"
|
||||
import { OfferManager } from "./offerManager.js"
|
||||
import { parse } from "uri-template"
|
||||
import webRTC from "../webRTC/index.js"
|
||||
import { ManagementManager } from "./managementManager.js"
|
||||
import { Agent } from "https"
|
||||
|
||||
type UserOperationsSub = {
|
||||
id: string
|
||||
|
|
@ -265,7 +267,7 @@ export default class {
|
|||
if (fee > 0) {
|
||||
await this.storage.userStorage.IncrementUserBalance(userInvoice.linkedApplication.owner.user_id, fee, 'fees', tx)
|
||||
}
|
||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl, { invoice: paymentRequest, amount, other: userInvoice.payer_data })
|
||||
await this.triggerPaidCallback(log, userInvoice.callbackUrl, { invoice: paymentRequest, amount, payerData: userInvoice.payer_data, token: userInvoice.bearer_token, rejectUnauthorized: userInvoice.rejectUnauthorized })
|
||||
const operationId = `${Types.UserOperationType.INCOMING_INVOICE}-${userInvoice.serial_id}`
|
||||
const op = { amount, paidAtUnix: Date.now() / 1000, inbound: true, type: Types.UserOperationType.INCOMING_INVOICE, identifier: userInvoice.invoice, operationId, network_fee: 0, service_fee: fee, confirmed: true, tx_hash: "", internal }
|
||||
this.sendOperationToNostr(userInvoice.linkedApplication, userInvoice.user.user_id, op)
|
||||
|
|
@ -283,21 +285,67 @@ export default class {
|
|||
})
|
||||
}
|
||||
|
||||
async triggerPaidCallback(log: PubLogger, url: string, { invoice, amount, other }: { invoice: string, amount: number, other?: Record<string, string> }) {
|
||||
async triggerPaidCallback(log: PubLogger, url: string,
|
||||
{ invoice, amount, payerData, token, rejectUnauthorized }:
|
||||
{ invoice: string,
|
||||
amount: number,
|
||||
payerData?: Record<string, string>,
|
||||
token?: string,
|
||||
rejectUnauthorized?: boolean
|
||||
}
|
||||
) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
let finalUrl = url.replace(`%[invoice]`, invoice).replace(`%[amount]`, amount.toString())
|
||||
if (other) {
|
||||
for (const [key, value] of Object.entries(other)) {
|
||||
finalUrl = finalUrl.replace(`%[${key}]`, value)
|
||||
}
|
||||
let finalUrl = "";
|
||||
const payerDataToExpand = {
|
||||
amount,
|
||||
invoice,
|
||||
...(payerData !== undefined ? payerData : {})
|
||||
};
|
||||
try {
|
||||
const parsed = parse(url);
|
||||
finalUrl = parsed.expand(payerDataToExpand)
|
||||
} catch (err: any) {
|
||||
log(ERROR, "error expanding callback url template for invoice", err?.message || "");
|
||||
return;
|
||||
}
|
||||
const symbol = finalUrl.includes('?') ? "&" : "?"
|
||||
finalUrl = finalUrl + symbol + "ok=true"
|
||||
|
||||
/*
|
||||
* Construct URL to find protocol.
|
||||
* If it's https we then use an agent
|
||||
* with the passed rejectUnauthorized
|
||||
* value.
|
||||
* If it's http we don't use an agent.
|
||||
* If it's neither we log error and
|
||||
* return.
|
||||
*/
|
||||
let parsedUrl: URL | null = null;
|
||||
let agent: Agent | undefined;
|
||||
try {
|
||||
parsedUrl = new URL(finalUrl);
|
||||
} catch (err: any) {
|
||||
log(ERROR, "error parsing callback url for invoice", err?.message || "");
|
||||
return;
|
||||
}
|
||||
if (parsedUrl.protocol === "https:") {
|
||||
agent = new Agent({
|
||||
rejectUnauthorized
|
||||
})
|
||||
} else if (parsedUrl.protocol === "http:") {
|
||||
agent = undefined
|
||||
} else {
|
||||
log(ERROR, "callback url's protocol is neither http or https");
|
||||
return;
|
||||
}
|
||||
const headers = {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {})
|
||||
}
|
||||
try {
|
||||
const symbol = finalUrl.includes('?') ? "&" : "?"
|
||||
finalUrl = finalUrl + symbol + "ok=true"
|
||||
log("sending paid callback to", finalUrl)
|
||||
await fetch(finalUrl)
|
||||
await fetch(finalUrl, { agent, headers })
|
||||
} catch (err: any) {
|
||||
log(ERROR, "error sending paid callback for invoice", err.message || "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export class ManagementManager {
|
|||
label: offer.label,
|
||||
price_sats: offer.price_sats,
|
||||
callback_url: offer.callback_url,
|
||||
payer_data: Object.keys(offer.expected_data || {}),
|
||||
payer_data: offer.payer_data || [],
|
||||
noffer: nofferEncode(pointer),
|
||||
}
|
||||
}
|
||||
|
|
@ -229,15 +229,11 @@ export class ManagementManager {
|
|||
if (validateResult.state !== 'success') {
|
||||
return validateResult
|
||||
}
|
||||
const dataMap: Record<string, Types.OfferDataType> = {}
|
||||
nmanageReq.offer.fields.payer_data.forEach(data => {
|
||||
dataMap[data] = Types.OfferDataType.DATA_STRING
|
||||
})
|
||||
const offer = await this.storage.offerStorage.AddUserOffer(appUserId, {
|
||||
label: nmanageReq.offer.fields.label,
|
||||
callback_url: nmanageReq.offer.fields.callback_url,
|
||||
price_sats: nmanageReq.offer.fields.price_sats,
|
||||
expected_data: dataMap,
|
||||
payer_data: nmanageReq.offer.fields.payer_data,
|
||||
management_pubkey: requestorPub,
|
||||
})
|
||||
return { state: 'success', result: offer }
|
||||
|
|
@ -288,18 +284,16 @@ export class ManagementManager {
|
|||
if (validateResult.state !== 'success') {
|
||||
return validateResult
|
||||
}
|
||||
const dataMap: Record<string, Types.OfferDataType> = {}
|
||||
for (const data of nmanageReq.offer.fields.payer_data || []) {
|
||||
if (typeof data !== 'string') {
|
||||
return { state: 'error', err: { res: 'GFY', code: 5, error: 'Invalid Field/Value', field: 'payer_data' } }
|
||||
}
|
||||
dataMap[data] = Types.OfferDataType.DATA_STRING
|
||||
}
|
||||
await this.storage.offerStorage.UpdateUserOffer(offer.result.app_user_id, nmanageReq.offer.id, {
|
||||
label: nmanageReq.offer.fields.label,
|
||||
callback_url: nmanageReq.offer.fields.callback_url,
|
||||
price_sats: nmanageReq.offer.fields.price_sats,
|
||||
expected_data: dataMap,
|
||||
payer_data: nmanageReq.offer.fields.payer_data,
|
||||
})
|
||||
const updatedOffer = await this.storage.offerStorage.GetOffer(nmanageReq.offer.id)
|
||||
if (!updatedOffer) {
|
||||
|
|
|
|||
|
|
@ -12,15 +12,6 @@ import { NofferData, OfferPriceType, nofferEncode } from '@shocknet/clink-sdk';
|
|||
import { MainSettings } from "./settings.js";
|
||||
|
||||
const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }: { pubkey: string, relay: string }): Types.OfferConfig => {
|
||||
if (offer.expected_data) {
|
||||
const keys = Object.keys(offer.expected_data)
|
||||
for (const key of keys) {
|
||||
const v = offer.expected_data[key] as Types.OfferDataType
|
||||
if (!Types.OfferDataType[v]) {
|
||||
offer.expected_data[key] = Types.OfferDataType.DATA_STRING
|
||||
}
|
||||
}
|
||||
}
|
||||
const offerStr = offer.offer_id
|
||||
const priceType: OfferPriceType = offer.price_sats === 0 ? OfferPriceType.Spontaneous : OfferPriceType.Fixed
|
||||
const noffer = nofferEncode({ pubkey, offer: offerStr, priceType, relay, price: offer.price_sats || undefined })
|
||||
|
|
@ -28,10 +19,14 @@ const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay }
|
|||
label: offer.label,
|
||||
price_sats: offer.price_sats,
|
||||
callback_url: offer.callback_url,
|
||||
expected_data: (offer.expected_data || {}) as Record<string, Types.OfferDataType>,
|
||||
payer_data: offer.payer_data || [],
|
||||
offer_id: offer.offer_id,
|
||||
noffer: noffer,
|
||||
default_offer: appUserId === offer.app_user_id
|
||||
default_offer: appUserId === offer.app_user_id,
|
||||
createdAtUnix: offer.created_at.getTime(),
|
||||
updatedAtUnix: offer.updated_at.getTime(),
|
||||
token: offer.bearer_token,
|
||||
rejectUnauthorized: offer.rejectUnauthorized
|
||||
}
|
||||
}
|
||||
export class OfferManager {
|
||||
|
|
@ -66,7 +61,7 @@ export class OfferManager {
|
|||
|
||||
async AddUserOffer(ctx: Types.UserContext, req: Types.OfferConfig): Promise<Types.OfferId> {
|
||||
const newOffer = await this.storage.offerStorage.AddUserOffer(ctx.app_user_id, {
|
||||
expected_data: req.expected_data,
|
||||
payer_data: req.payer_data,
|
||||
label: req.label,
|
||||
price_sats: req.price_sats,
|
||||
callback_url: req.callback_url,
|
||||
|
|
@ -82,7 +77,7 @@ export class OfferManager {
|
|||
|
||||
async UpdateUserOffer(ctx: Types.UserContext, req: Types.OfferConfig) {
|
||||
await this.storage.offerStorage.UpdateUserOffer(ctx.app_user_id, req.offer_id, {
|
||||
expected_data: req.expected_data,
|
||||
payer_data: req.payer_data,
|
||||
label: req.label,
|
||||
price_sats: req.price_sats,
|
||||
callback_url: req.callback_url,
|
||||
|
|
@ -139,12 +134,8 @@ export class OfferManager {
|
|||
}
|
||||
|
||||
ValidateExpectedData(userOffer: UserOffer, payerData: any): { passed: false, validated: undefined } | { passed: true, validated: Record<string, string> } {
|
||||
const expected = userOffer.expected_data
|
||||
if (!expected) {
|
||||
return { passed: true, validated: {} }
|
||||
}
|
||||
const expectedKeys = Object.keys(expected)
|
||||
if (expectedKeys.length === 0) {
|
||||
const expectedKeys = userOffer.payer_data
|
||||
if (!expectedKeys || expectedKeys.length === 0) {
|
||||
return { passed: true, validated: {} }
|
||||
}
|
||||
if (typeof payerData !== 'object' || payerData === null) {
|
||||
|
|
@ -193,11 +184,11 @@ export class OfferManager {
|
|||
return this.HandleDefaultUserOffer(offerReq, appId, remote)
|
||||
}
|
||||
if (userOffer.app_user_id === userOffer.offer_id) {
|
||||
if (userOffer.price_sats !== 0 || userOffer.expected_data) {
|
||||
if (userOffer.price_sats !== 0 || userOffer.payer_data) {
|
||||
this.logger("default offer has custom price or expected data, resetting")
|
||||
await this.storage.offerStorage.UpdateUserOffer(userOffer.app_user_id, userOffer.offer_id, { price_sats: 0, expected_data: null })
|
||||
await this.storage.offerStorage.UpdateUserOffer(userOffer.app_user_id, userOffer.offer_id, { price_sats: 0, payer_data: null })
|
||||
userOffer.price_sats = 0
|
||||
userOffer.expected_data = null
|
||||
userOffer.payer_data = null
|
||||
}
|
||||
}
|
||||
let amt = userOffer.price_sats
|
||||
|
|
@ -216,7 +207,9 @@ export class OfferManager {
|
|||
http_callback_url: userOffer.callback_url, payer_identifier: userOffer.app_user_id, receiver_identifier: userOffer.app_user_id,
|
||||
invoice_req: { amountSats: amt, memo: userOffer.label, zap: offerReq.zap },
|
||||
payer_data: validated ? { data: validated } : undefined,
|
||||
offer_string: offer
|
||||
offer_string: offer,
|
||||
rejectUnauthorized: userOffer.rejectUnauthorized,
|
||||
token: userOffer.bearer_token
|
||||
})
|
||||
return { success: true, invoice: res.invoice }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,14 @@ export class UserOffer {
|
|||
type: 'simple-json',
|
||||
default: null
|
||||
})
|
||||
expected_data: Record<string, string> | null
|
||||
payer_data: string[] | null
|
||||
|
||||
@Column({ default: "" })
|
||||
bearer_token: string
|
||||
|
||||
@Column({ default: true })
|
||||
rejectUnauthorized: boolean
|
||||
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date
|
||||
|
|
|
|||
|
|
@ -64,6 +64,13 @@ export class UserReceivingInvoice {
|
|||
})
|
||||
payer_data?: Record<string, string>
|
||||
|
||||
|
||||
@Column({ default: true })
|
||||
rejectUnauthorized: boolean
|
||||
|
||||
@Column({ default: "" })
|
||||
bearer_token: string
|
||||
|
||||
@Column({ default: "" })
|
||||
offer_id?: string
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InvoiceCallbackUrls1752425992291 implements MigrationInterface {
|
||||
name = 'InvoiceCallbackUrls1752425992291'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "payer_data" text, "offer_id" varchar NOT NULL DEFAULT (''), "rejectUnauthorized" boolean NOT NULL DEFAULT (1), "bearer_token" varchar NOT NULL DEFAULT (''), CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider", "payer_data", "offer_id") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider", "payer_data", "offer_id" FROM "user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "user_receiving_invoice"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_receiving_invoice" RENAME TO "user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
|
||||
|
||||
await queryRunner.query(`CREATE TABLE "temporary_user_offer" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "offer_id" varchar NOT NULL, "label" varchar NOT NULL, "price_sats" integer NOT NULL DEFAULT (0), "callback_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "management_pubkey" varchar NOT NULL DEFAULT (''), "payer_data" text, "bearer_token" varchar NOT NULL DEFAULT (''), "rejectUnauthorized" boolean NOT NULL DEFAULT (1), CONSTRAINT "UQ_478f72095abd8a516d3a309a5c5" UNIQUE ("offer_id"))`);
|
||||
await queryRunner.query(`INSERT INTO "temporary_user_offer"("serial_id", "app_user_id", "offer_id", "label", "price_sats", "callback_url", "created_at", "updated_at", "management_pubkey") SELECT "serial_id", "app_user_id", "offer_id", "label", "price_sats", "callback_url", "created_at", "updated_at", "management_pubkey" FROM "user_offer"`);
|
||||
await queryRunner.query(`DROP TABLE "user_offer"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_user_offer" RENAME TO "user_offer"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_a131e6b58f084f1340538681b5"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_receiving_invoice" RENAME TO "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_receiving_invoice" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "invoice" varchar NOT NULL, "expires_at_unix" integer NOT NULL, "paid_at_unix" integer NOT NULL DEFAULT (0), "internal" boolean NOT NULL DEFAULT (0), "paidByLnd" boolean NOT NULL DEFAULT (0), "callbackUrl" varchar NOT NULL DEFAULT (''), "paid_amount" integer NOT NULL DEFAULT (0), "service_fee" integer NOT NULL DEFAULT (0), "zap_info" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "userSerialId" integer, "productProductId" varchar, "payerSerialId" integer, "linkedApplicationSerialId" integer, "liquidityProvider" varchar, "payer_data" text, "offer_id" varchar NOT NULL DEFAULT (''), CONSTRAINT "FK_714a8b7d4f89f8a802ca181b789" FOREIGN KEY ("linkedApplicationSerialId") REFERENCES "application" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_d4bb1e4c60e8a869f1f43ca2e31" FOREIGN KEY ("payerSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5263bde2a519db9ea608b702ec8" FOREIGN KEY ("productProductId") REFERENCES "product" ("product_id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_2c0dfb3483f3e5e7e3cdd5dc71f" FOREIGN KEY ("userSerialId") REFERENCES "user" ("serial_id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
|
||||
await queryRunner.query(`INSERT INTO "user_receiving_invoice"("serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider", "payer_data", "offer_id") SELECT "serial_id", "invoice", "expires_at_unix", "paid_at_unix", "internal", "paidByLnd", "callbackUrl", "paid_amount", "service_fee", "zap_info", "created_at", "updated_at", "userSerialId", "productProductId", "payerSerialId", "linkedApplicationSerialId", "liquidityProvider", "payer_data", "offer_id" FROM "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_receiving_invoice"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a131e6b58f084f1340538681b5" ON "user_receiving_invoice" ("invoice") `);
|
||||
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "user_offer" RENAME TO "temporary_user_offer"`);
|
||||
await queryRunner.query(`CREATE TABLE "user_offer" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_user_id" varchar NOT NULL, "offer_id" varchar NOT NULL, "label" varchar NOT NULL, "price_sats" integer NOT NULL DEFAULT (0), "callback_url" varchar NOT NULL DEFAULT (''), "expected_data" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "management_pubkey" varchar NOT NULL DEFAULT (''), CONSTRAINT "UQ_478f72095abd8a516d3a309a5c5" UNIQUE ("offer_id"))`);
|
||||
await queryRunner.query(`INSERT INTO "user_offer"("serial_id", "app_user_id", "offer_id", "label", "price_sats", "callback_url", "created_at", "updated_at", "management_pubkey") SELECT "serial_id", "app_user_id", "offer_id", "label", "price_sats", "callback_url", "created_at", "updated_at", "management_pubkey" FROM "temporary_user_offer"`);
|
||||
await queryRunner.query(`DROP TABLE "temporary_user_offer"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,9 +19,10 @@ import { RootOpsTime1745428134124 } from './1745428134124-root_ops_time.js'
|
|||
import { ChannelEvents1750777346411 } from './1750777346411-channel_events.js'
|
||||
import { ManagementGrant1751307732346 } from './1751307732346-management_grant.js'
|
||||
import { ManagementGrantBanned1751989251513 } from './1751989251513-management_grant_banned.js'
|
||||
import { InvoiceCallbackUrls1752425992291 } from './1752425992291-invoice_callback_urls.js'
|
||||
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
|
||||
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
|
||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513]
|
||||
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513, InvoiceCallbackUrls1752425992291]
|
||||
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825, RootOps1732566440447, RootOpsTime1745428134124, ChannelEvents1750777346411]
|
||||
/* export const TypeOrmMigrationRunner = async (log: PubLogger, storageManager: Storage, settings: DbSettings, arg: string | undefined): Promise<boolean> => {
|
||||
await connectAndMigrate(log, storageManager, allMigrations, allMetricsMigrations)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { Application } from './entity/Application.js';
|
|||
import TransactionsQueue from "./db/transactionsQueue.js";
|
||||
import { LoggedEvent } from './eventsLog.js';
|
||||
import { StorageInterface } from './db/storageInterface.js';
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo, offerId?: string, payerData?: Record<string, string> }
|
||||
export type InboundOptionals = { product?: Product, callbackUrl?: string, expiry: number, expectedPayer?: User, linkedApplication?: Application, zapInfo?: ZapInfo, offerId?: string, payerData?: Record<string, string> , rejectUnauthorized?: boolean, token?: string}
|
||||
export const defaultInvoiceExpiry = 60 * 60
|
||||
export default class {
|
||||
dbs: StorageInterface
|
||||
|
|
@ -102,6 +102,8 @@ export default class {
|
|||
liquidityProvider: providerDestination,
|
||||
offer_id: options.offerId,
|
||||
payer_data: options.payerData,
|
||||
rejectUnauthorized: options.rejectUnauthorized,
|
||||
bearer_token: options.token
|
||||
}, txId)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue