From 299f5d86b68114d9772fcecc153342fadf22249c Mon Sep 17 00:00:00 2001 From: boufni95 Date: Fri, 6 Dec 2024 19:24:02 +0000 Subject: [PATCH] list offer ops --- proto/autogenerated/client.md | 26 +++++++ proto/autogenerated/go/http_client.go | 30 ++++++++ proto/autogenerated/go/types.go | 14 ++++ proto/autogenerated/ts/express_server.ts | 34 ++++++++ proto/autogenerated/ts/http_client.ts | 14 ++++ proto/autogenerated/ts/nostr_client.ts | 15 ++++ proto/autogenerated/ts/nostr_transport.ts | 28 +++++++ proto/autogenerated/ts/types.ts | 94 ++++++++++++++++++++++- proto/service/methods.proto | 6 ++ proto/service/structs.proto | 17 ++++ src/services/main/offerManager.ts | 21 +++-- src/services/serverMethods/index.ts | 7 ++ src/services/storage/paymentStorage.ts | 8 ++ 13 files changed, 307 insertions(+), 7 deletions(-) diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 12ec7d3c..988fe113 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -168,6 +168,11 @@ The nostr server will send back a message response, and inside the body there wi - input: [OfferId](#OfferId) - output: [OfferConfig](#OfferConfig) +- GetUserOfferInvoices + - auth type: __User__ + - input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq) + - output: [OfferInvoices](#OfferInvoices) + - GetUserOffers - auth type: __User__ - This methods has an __empty__ __request__ body @@ -585,6 +590,13 @@ The nostr server will send back a message response, and inside the body there wi - input: [OfferId](#OfferId) - output: [OfferConfig](#OfferConfig) +- GetUserOfferInvoices + - auth type: __User__ + - http method: __post__ + - http route: __/api/user/offer/get/invoices__ + - input: [GetUserOfferInvoicesReq](#GetUserOfferInvoicesReq) + - output: [OfferInvoices](#OfferInvoices) + - GetUserOffers - auth type: __User__ - http method: __get__ @@ -998,6 +1010,10 @@ The nostr server will send back a message response, and inside the body there wi ### GetProductBuyLinkResponse - __link__: _string_ +### GetUserOfferInvoicesReq + - __include_unpaid__: _boolean_ + - __offer_id__: _string_ + ### GetUserOperationsRequest - __latestIncomingInvoice__: _number_ - __latestIncomingTx__: _number_ @@ -1130,6 +1146,16 @@ The nostr server will send back a message response, and inside the body there wi ### OfferId - __offer_id__: _string_ +### OfferInvoice + - __amount__: _number_ + - __data__: MAP with key: _string_ and value: _string_ + - __invoice__: _string_ + - __offer_id__: _string_ + - __paid_at_unix__: _number_ + +### OfferInvoices + - __invoices__: ARRAY of: _[OfferInvoice](#OfferInvoice)_ + ### OpenChannel - __active__: _boolean_ - __capacity__: _number_ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index a5a0806f..8319449a 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -95,6 +95,7 @@ type Client struct { GetUsageMetrics func() (*UsageMetrics, error) GetUserInfo func() (*UserInfo, error) GetUserOffer func(req OfferId) (*OfferConfig, error) + GetUserOfferInvoices func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) GetUserOffers func() (*UserOffers, error) GetUserOperations func(req GetUserOperationsRequest) (*GetUserOperationsResponse, error) HandleLnurlAddress func(routeParams HandleLnurlAddress_RouteParams) (*LnurlPayInfoResponse, error) @@ -1110,6 +1111,35 @@ func NewClient(params ClientParams) *Client { } return &res, nil }, + GetUserOfferInvoices: func(req GetUserOfferInvoicesReq) (*OfferInvoices, error) { + auth, err := params.RetrieveUserAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/user/offer/get/invoices" + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := OfferInvoices{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, GetUserOffers: func() (*UserOffers, error) { auth, err := params.RetrieveUserAuth() if err != nil { diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 27e77fd5..28113249 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -274,6 +274,10 @@ type GetPaymentStateRequest struct { type GetProductBuyLinkResponse struct { Link string `json:"link"` } +type GetUserOfferInvoicesReq struct { + Include_unpaid bool `json:"include_unpaid"` + Offer_id string `json:"offer_id"` +} type GetUserOperationsRequest struct { Latestincominginvoice int64 `json:"latestIncomingInvoice"` Latestincomingtx int64 `json:"latestIncomingTx"` @@ -406,6 +410,16 @@ type OfferConfig struct { type OfferId struct { Offer_id string `json:"offer_id"` } +type OfferInvoice struct { + Amount int64 `json:"amount"` + Data map[string]string `json:"data"` + Invoice string `json:"invoice"` + Offer_id string `json:"offer_id"` + Paid_at_unix int64 `json:"paid_at_unix"` +} +type OfferInvoices struct { + Invoices []OfferInvoice `json:"invoices"` +} type OpenChannel struct { Active bool `json:"active"` Capacity int64 `json:"capacity"` diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index 3c418d2d..d84f9c48 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -467,6 +467,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOfferInvoices': + if (!methods.GetUserOfferInvoices) { + throw new Error('method GetUserOfferInvoices not found' ) + } else { + const error = Types.GetUserOfferInvoicesReqValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOffers': if (!methods.GetUserOffers) { throw new Error('method GetUserOffers not found' ) @@ -1135,6 +1147,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => { opts.metricsCallback([{ ...info, ...stats, ...authContext }]) } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } }) + if (!opts.allowNotImplementedMethods && !methods.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices is not implemented') + app.post('/api/user/offer/get/invoices', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetUserOfferInvoices', batch: false, nostr: false, batchSize: 0} + const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n } + let authCtx: Types.AuthContext = {} + try { + if (!methods.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices is not implemented') + const authContext = await opts.UserAuthGuard(req.headers['authorization']) + authCtx = authContext + stats.guard = process.hrtime.bigint() + const request = req.body + const error = Types.GetUserOfferInvoicesReqValidate(request) + stats.validate = process.hrtime.bigint() + if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback) + const query = req.query + const params = req.params + const response = await methods.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', ctx:authContext , req: request}) + stats.handle = process.hrtime.bigint() + res.json({status: 'OK', ...response}) + opts.metricsCallback([{ ...info, ...stats, ...authContext }]) + } catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } + }) if (!opts.allowNotImplementedMethods && !methods.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') app.get('/api/user/offers/get', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetUserOffers', batch: false, nostr: false, batchSize: 0} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 5ca384df..01e4266b 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -522,6 +522,20 @@ export default (params: ClientParams) => ({ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + const auth = await params.retrieveUserAuth() + if (auth === null) throw new Error('retrieveUserAuth() returned null') + let finalRoute = '/api/user/offer/get/invoices' + const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } }) + if (data.status === 'ERROR' && typeof data.reason === 'string') return data + if (data.status === 'OK') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.OfferInvoicesValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOffers: async (): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_client.ts b/proto/autogenerated/ts/nostr_client.ts index 5cf127d2..ff4eda05 100644 --- a/proto/autogenerated/ts/nostr_client.ts +++ b/proto/autogenerated/ts/nostr_client.ts @@ -451,6 +451,21 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ } return { status: 'ERROR', reason: 'invalid response' } }, + GetUserOfferInvoices: async (request: Types.GetUserOfferInvoicesReq): Promise => { + const auth = await params.retrieveNostrUserAuth() + if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') + const nostrRequest: NostrRequest = {} + nostrRequest.body = request + const data = await send(params.pubDestination, {rpcName:'GetUserOfferInvoices',authIdentifier:auth, ...nostrRequest }) + if (data.status === 'ERROR' && typeof data.reason === 'string') return data + if (data.status === 'OK') { + const result = data + if(!params.checkResult) return { status: 'OK', ...result } + const error = Types.OfferInvoicesValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetUserOffers: async (): Promise => { const auth = await params.retrieveNostrUserAuth() if (auth === null) throw new Error('retrieveNostrUserAuth() returned null') diff --git a/proto/autogenerated/ts/nostr_transport.ts b/proto/autogenerated/ts/nostr_transport.ts index e651e11f..99cceb6d 100644 --- a/proto/autogenerated/ts/nostr_transport.ts +++ b/proto/autogenerated/ts/nostr_transport.ts @@ -349,6 +349,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) } break + case 'GetUserOfferInvoices': + if (!methods.GetUserOfferInvoices) { + throw new Error('method not defined: GetUserOfferInvoices') + } else { + const error = Types.GetUserOfferInvoicesReqValidate(operation.req) + opStats.validate = process.hrtime.bigint() + if (error !== null) throw error + const res = await methods.GetUserOfferInvoices({...operation, ctx}); responses.push({ status: 'OK', ...res }) + opStats.handle = process.hrtime.bigint() + callsMetrics.push({ ...opInfo, ...opStats, ...ctx }) + } + break case 'GetUserOffers': if (!methods.GetUserOffers) { throw new Error('method not defined: GetUserOffers') @@ -816,6 +828,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => { opts.metricsCallback([{ ...info, ...stats, ...authContext }]) }catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } break + case 'GetUserOfferInvoices': + try { + if (!methods.GetUserOfferInvoices) throw new Error('method: GetUserOfferInvoices is not implemented') + const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier) + stats.guard = process.hrtime.bigint() + authCtx = authContext + const request = req.body + const error = Types.GetUserOfferInvoicesReqValidate(request) + stats.validate = process.hrtime.bigint() + if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback) + const response = await methods.GetUserOfferInvoices({rpcName:'GetUserOfferInvoices', ctx:authContext , req: request}) + stats.handle = process.hrtime.bigint() + res({status: 'OK', ...response}) + opts.metricsCallback([{ ...info, ...stats, ...authContext }]) + }catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e } + break case 'GetUserOffers': try { if (!methods.GetUserOffers) throw new Error('method: GetUserOffers is not implemented') diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index ef7d35cf..f7a9b021 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -34,8 +34,8 @@ export type UserContext = { app_user_id: string user_id: string } -export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input -export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output +export type UserMethodInputs = AddProduct_Input | AddUserOffer_Input | AuthorizeDebit_Input | BanDebit_Input | DecodeInvoice_Input | DeleteUserOffer_Input | EditDebit_Input | EnrollAdminToken_Input | GetDebitAuthorizations_Input | GetLNURLChannelLink_Input | GetLnurlPayLink_Input | GetLnurlWithdrawLink_Input | GetPaymentState_Input | GetUserInfo_Input | GetUserOffer_Input | GetUserOfferInvoices_Input | GetUserOffers_Input | GetUserOperations_Input | NewAddress_Input | NewInvoice_Input | NewProductInvoice_Input | PayAddress_Input | PayInvoice_Input | ResetDebit_Input | RespondToDebit_Input | UpdateCallbackUrl_Input | UpdateUserOffer_Input | UserHealth_Input +export type UserMethodOutputs = AddProduct_Output | AddUserOffer_Output | AuthorizeDebit_Output | BanDebit_Output | DecodeInvoice_Output | DeleteUserOffer_Output | EditDebit_Output | EnrollAdminToken_Output | GetDebitAuthorizations_Output | GetLNURLChannelLink_Output | GetLnurlPayLink_Output | GetLnurlWithdrawLink_Output | GetPaymentState_Output | GetUserInfo_Output | GetUserOffer_Output | GetUserOfferInvoices_Output | GetUserOffers_Output | GetUserOperations_Output | NewAddress_Output | NewInvoice_Output | NewProductInvoice_Output | PayAddress_Output | PayInvoice_Output | ResetDebit_Output | RespondToDebit_Output | UpdateCallbackUrl_Output | UpdateUserOffer_Output | UserHealth_Output export type AuthContext = AdminContext | AppContext | GuestContext | GuestWithPubContext | MetricsContext | UserContext export type AddApp_Input = {rpcName:'AddApp', req: AddAppRequest} @@ -167,6 +167,9 @@ export type GetUserInfo_Output = ResultError | ({ status: 'OK' } & UserInfo) export type GetUserOffer_Input = {rpcName:'GetUserOffer', req: OfferId} export type GetUserOffer_Output = ResultError | ({ status: 'OK' } & OfferConfig) +export type GetUserOfferInvoices_Input = {rpcName:'GetUserOfferInvoices', req: GetUserOfferInvoicesReq} +export type GetUserOfferInvoices_Output = ResultError | ({ status: 'OK' } & OfferInvoices) + export type GetUserOffers_Input = {rpcName:'GetUserOffers'} export type GetUserOffers_Output = ResultError | ({ status: 'OK' } & UserOffers) @@ -314,6 +317,7 @@ export type ServerMethods = { GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise GetUserInfo?: (req: GetUserInfo_Input & {ctx: UserContext }) => Promise GetUserOffer?: (req: GetUserOffer_Input & {ctx: UserContext }) => Promise + GetUserOfferInvoices?: (req: GetUserOfferInvoices_Input & {ctx: UserContext }) => Promise GetUserOffers?: (req: GetUserOffers_Input & {ctx: UserContext }) => Promise GetUserOperations?: (req: GetUserOperations_Input & {ctx: UserContext }) => Promise HandleLnurlAddress?: (req: HandleLnurlAddress_Input & {ctx: GuestContext }) => Promise @@ -1504,6 +1508,29 @@ export const GetProductBuyLinkResponseValidate = (o?: GetProductBuyLinkResponse, return null } +export type GetUserOfferInvoicesReq = { + include_unpaid: boolean + offer_id: string +} +export const GetUserOfferInvoicesReqOptionalFields: [] = [] +export type GetUserOfferInvoicesReqOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + include_unpaid_CustomCheck?: (v: boolean) => boolean + offer_id_CustomCheck?: (v: string) => boolean +} +export const GetUserOfferInvoicesReqValidate = (o?: GetUserOfferInvoicesReq, opts: GetUserOfferInvoicesReqOptions = {}, path: string = 'GetUserOfferInvoicesReq::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + if (typeof o.include_unpaid !== 'boolean') return new Error(`${path}.include_unpaid: is not a boolean`) + if (opts.include_unpaid_CustomCheck && !opts.include_unpaid_CustomCheck(o.include_unpaid)) return new Error(`${path}.include_unpaid: custom check failed`) + + 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`) + + return null +} + export type GetUserOperationsRequest = { latestIncomingInvoice: number latestIncomingTx: number @@ -2310,6 +2337,69 @@ export const OfferIdValidate = (o?: OfferId, opts: OfferIdOptions = {}, path: st return null } +export type OfferInvoice = { + amount: number + data: Record + invoice: string + offer_id: string + paid_at_unix: number +} +export const OfferInvoiceOptionalFields: [] = [] +export type OfferInvoiceOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + amount_CustomCheck?: (v: number) => boolean + data_CustomCheck?: (v: Record) => boolean + invoice_CustomCheck?: (v: string) => boolean + offer_id_CustomCheck?: (v: string) => boolean + paid_at_unix_CustomCheck?: (v: number) => boolean +} +export const OfferInvoiceValidate = (o?: OfferInvoice, opts: OfferInvoiceOptions = {}, path: string = 'OfferInvoice::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + if (typeof o.amount !== 'number') return new Error(`${path}.amount: is not a number`) + if (opts.amount_CustomCheck && !opts.amount_CustomCheck(o.amount)) return new Error(`${path}.amount: custom check failed`) + + if (typeof o.data !== 'object' || o.data === null) return new Error(`${path}.data: is not an object or is null`) + for (const key in o.data) { + if (typeof o.data[key] !== 'string') return new Error(`${path}.data['${key}']: is not a string`) + } + + if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) + if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) + + 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 (typeof o.paid_at_unix !== 'number') return new Error(`${path}.paid_at_unix: is not a number`) + if (opts.paid_at_unix_CustomCheck && !opts.paid_at_unix_CustomCheck(o.paid_at_unix)) return new Error(`${path}.paid_at_unix: custom check failed`) + + return null +} + +export type OfferInvoices = { + invoices: OfferInvoice[] +} +export const OfferInvoicesOptionalFields: [] = [] +export type OfferInvoicesOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + invoices_ItemOptions?: OfferInvoiceOptions + invoices_CustomCheck?: (v: OfferInvoice[]) => boolean +} +export const OfferInvoicesValidate = (o?: OfferInvoices, opts: OfferInvoicesOptions = {}, path: string = 'OfferInvoices::root.'): Error | null => { + if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message') + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + + if (!Array.isArray(o.invoices)) return new Error(`${path}.invoices: is not an array`) + for (let index = 0; index < o.invoices.length; index++) { + const invoicesErr = OfferInvoiceValidate(o.invoices[index], opts.invoices_ItemOptions, `${path}.invoices[${index}]`) + if (invoicesErr !== null) return invoicesErr + } + if (opts.invoices_CustomCheck && !opts.invoices_CustomCheck(o.invoices)) return new Error(`${path}.invoices: custom check failed`) + + return null +} + export type OpenChannel = { active: boolean capacity: number diff --git a/proto/service/methods.proto b/proto/service/methods.proto index b5a91007..4ac703ad 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -481,6 +481,12 @@ service LightningPub { option (http_route) = "/api/user/offer/get"; option (nostr) = true; } + rpc GetUserOfferInvoices(structs.GetUserOfferInvoicesReq) returns (structs.OfferInvoices){ + option (auth_type) = "User"; + option (http_method) = "post"; + option (http_route) = "/api/user/offer/get/invoices"; + option (nostr) = true; + } rpc UpdateUserOffer(structs.OfferConfig) returns (structs.Empty){ option (auth_type) = "User"; diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 955b113a..409c1b16 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -641,4 +641,21 @@ message OfferConfig { message UserOffers { repeated OfferConfig offers = 1; +} + +message GetUserOfferInvoicesReq { + string offer_id = 1; + bool include_unpaid = 2; +} + +message OfferInvoices { + repeated OfferInvoice invoices = 1; +} + +message OfferInvoice { + string invoice = 1; + string offer_id = 2; + int64 paid_at_unix = 3; + int64 amount = 4; + map data = 5; } \ No newline at end of file diff --git a/src/services/main/offerManager.ts b/src/services/main/offerManager.ts index fc31d441..e1a4a3e1 100644 --- a/src/services/main/offerManager.ts +++ b/src/services/main/offerManager.ts @@ -44,11 +44,6 @@ const mapToOfferConfig = (appUserId: string, offer: UserOffer, { pubkey, relay } export class OfferManager { - - - - - _nostrSend: NostrSend | null = null applicationManager: ApplicationManager @@ -97,6 +92,22 @@ export class OfferManager { callback_url: req.callback_url, }) } + async GetUserOfferInvoices(ctx: Types.UserContext, req: Types.GetUserOfferInvoicesReq): Promise { + const userOffer = await this.storage.offerStorage.GetUserOffer(ctx.app_user_id, req.offer_id) + if (!userOffer) { + throw new Error("Offer not found") + } + const i = await this.storage.paymentStorage.GetOfferInvoices(req.offer_id, req.include_unpaid) + return { + invoices: i.map(i => ({ + invoice: i.invoice, + offer_id: i.offer_id || "", + paid_at_unix: i.paid_at_unix, + amount: i.paid_amount, + data: i.payer_data || {} + })) + } + } async GetUserOffer(ctx: Types.UserContext, req: Types.OfferId): Promise { const app = await this.applicationManager.GetApp(ctx.app_id) diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index 7a9b0e0c..4fdb6c76 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -350,6 +350,13 @@ export default (mainHandler: Main): Types.ServerMethods => { }) if (err != null) throw new Error(err.message) return mainHandler.offerManager.GetUserOffer(ctx, req) + }, + GetUserOfferInvoices: async ({ ctx, req }) => { + const err = Types.GetUserOfferInvoicesReqValidate(req, { + offer_id_CustomCheck: id => id !== '' + }) + if (err != null) throw new Error(err.message) + return mainHandler.offerManager.GetUserOfferInvoices(ctx, req) } } } \ No newline at end of file diff --git a/src/services/storage/paymentStorage.ts b/src/services/storage/paymentStorage.ts index 051b9943..9e7755c3 100644 --- a/src/services/storage/paymentStorage.ts +++ b/src/services/storage/paymentStorage.ts @@ -454,4 +454,12 @@ export default class { async GetPendingPayments(entityManager = this.DB) { return entityManager.getRepository(UserInvoicePayment).find({ where: { paid_at_unix: 0 } }) } + + async GetOfferInvoices(offerId: string, includeUnpaid: boolean, entityManager = this.DB) { + const where: { offer_id: string, paid_at_unix?: FindOperator } = { offer_id: offerId } + if (!includeUnpaid) { + where.paid_at_unix = MoreThan(0) + } + return entityManager.getRepository(UserReceivingInvoice).find({ where }) + } } \ No newline at end of file