| 41 | } |
| 42 | |
| 43 | export class LNbitsPaymentsProcessor implements IPaymentsProcessor { |
| 44 | public constructor( |
| 45 | private httpClient: AxiosInstance, |
| 46 | private settings: Factory<Settings>, |
| 47 | ) {} |
| 48 | |
| 49 | public async getInvoice(invoiceId: string): Promise<GetInvoiceResponse> { |
| 50 | logger('get invoice: %s', invoiceId) |
| 51 | try { |
| 52 | const response = await this.httpClient.get(`/api/v1/payments/${invoiceId}`, { |
| 53 | maxRedirects: 1, |
| 54 | }) |
| 55 | const invoice = new LNbitsInvoice() |
| 56 | const data = response.data |
| 57 | invoice.id = data.details.payment_hash |
| 58 | invoice.pubkey = data.details.extra.internalId |
| 59 | invoice.bolt11 = data.details.bolt11 |
| 60 | invoice.amountRequested = BigInt(Math.floor(data.details.amount / 1000)) |
| 61 | if (data.paid) { |
| 62 | invoice.amountPaid = BigInt(Math.floor(data.details.amount / 1000)) |
| 63 | } |
| 64 | invoice.unit = InvoiceUnit.SATS |
| 65 | invoice.expiresAt = new Date(data.details.expiry * 1000) |
| 66 | if (data.paid) { |
| 67 | invoice.status = InvoiceStatus.COMPLETED |
| 68 | } else if (isExpiredInvoice(invoice)) { |
| 69 | invoice.status = InvoiceStatus.EXPIRED |
| 70 | } else { |
| 71 | invoice.status = InvoiceStatus.PENDING |
| 72 | } |
| 73 | invoice.description = data.details.memo |
| 74 | invoice.confirmedAt = data.paid ? new Date(data.details.time * 1000) : null |
| 75 | invoice.createdAt = new Date(data.details.time * 1000) |
| 76 | invoice.updatedAt = new Date() |
| 77 | return invoice |
| 78 | } catch (error) { |
| 79 | logger.error(`Unable to get invoice ${invoiceId}. Reason:`, error) |
| 80 | |
| 81 | throw error |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | public async createInvoice(request: CreateInvoiceRequest): Promise<CreateInvoiceResponse> { |
| 86 | logger('create invoice: %o', request) |
| 87 | const { amount: amountMsats, description, requestId: internalId } = request |
| 88 | |
| 89 | const callbackURL = new URL(this.settings().paymentsProcessors?.lnbits?.callbackBaseURL) |
| 90 | const hmacExpiry = (Date.now() + 1 * 24 * 60 * 60 * 1000).toString() |
| 91 | callbackURL.searchParams.set( |
| 92 | 'hmac', |
| 93 | hmacExpiry + ':' + hmacSha256(deriveFromSecret('lnbits-callback-hmac-key'), hmacExpiry).toString('hex'), |
| 94 | ) |
| 95 | |
| 96 | const body = { |
| 97 | amount: Number(amountMsats / 1000n), |
| 98 | memo: description, |
| 99 | extra: { |
| 100 | internalId, |
nothing calls this directly
no outgoing calls
no test coverage detected