Skip to content

Commit

Permalink
feat(core): add payment status by hash query/subscription
Browse files Browse the repository at this point in the history
  • Loading branch information
dolcalmi committed Jan 25, 2024
1 parent 1c35e30 commit a3cb01e
Show file tree
Hide file tree
Showing 19 changed files with 398 additions and 32 deletions.
79 changes: 72 additions & 7 deletions bats/core/api/public-ln-receive.bats
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ usd_amount=50
[[ "$error_msg" == "Account does not exist for username idontexist" ]] || exit 1
}

@test "public-ln-receive: receive via invoice - can receive on btc invoice, with subscription" {
@test "public-ln-receive: receive via invoice - can receive on btc invoice, with subscription by payment request" {
token_name="$ALICE"
btc_wallet_name="$token_name.btc_wallet_id"

Expand All @@ -125,7 +125,10 @@ usd_amount=50
payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

# Setup subscription
payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Setup subscriptions
variables=$(
jq -n \
--arg payment_request "$payment_request" \
Expand All @@ -134,13 +137,59 @@ usd_amount=50
subscribe_to 'anon' 'ln-invoice-payment-status-sub' "$variables"
sleep 3
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*PENDING" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*$payment_hash" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*$payment_request" "$SUBSCRIBER_LOG_FILE"

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \

# Check for settled with subscription
# Check for settled with subscriptions
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*PAID" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*$payment_hash" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatus.*$payment_request" "$SUBSCRIBER_LOG_FILE"
stop_subscriber
}

@test "public-ln-receive: receive via invoice - can receive on btc invoice, with subscription by payment hash" {
token_name="$ALICE"
btc_wallet_name="$token_name.btc_wallet_id"

variables=$(
jq -n \
--arg wallet_id "$(read_value $btc_wallet_name)" \
--arg amount "$btc_amount" \
'{input: {recipientWalletId: $wallet_id, amount: $amount}}'
)
exec_graphql 'anon' 'ln-invoice-create-on-behalf-of-recipient' "$variables"
invoice="$(graphql_output '.data.lnInvoiceCreateOnBehalfOfRecipient.invoice')"

payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Setup subscription
variables=$(
jq -n \
--arg payment_hash "$payment_hash" \
'{input: {paymentHash: $payment_hash}}'
)
subscribe_to 'anon' 'ln-invoice-payment-status-by-hash-sub' "$variables"
sleep 3
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*PENDING" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*$payment_hash" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*$payment_request" "$SUBSCRIBER_LOG_FILE"

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \

# Check for settled with subscription
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*PAID" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*$payment_hash" "$SUBSCRIBER_LOG_FILE"
retry 10 1 grep "Data.*lnInvoicePaymentStatusByHash.*$payment_request" "$SUBSCRIBER_LOG_FILE"
stop_subscriber
}

Expand All @@ -160,12 +209,16 @@ usd_amount=50
payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \

# Check for settled with query
retry 15 1 check_ln_payment_settled "$payment_request"
retry 15 1 check_ln_payment_settled "$payment_request" "$payment_hash"
retry 15 1 check_ln_payment_settled_by_hash "$payment_request" "$payment_hash"
}

@test "public-ln-receive: receive via invoice - can receive on usd invoice, sats denominated" {
Expand All @@ -184,12 +237,16 @@ usd_amount=50
payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \

# Check for settled with query
retry 15 1 check_ln_payment_settled "$payment_request"
retry 15 1 check_ln_payment_settled "$payment_request" "$payment_hash"
retry 15 1 check_ln_payment_settled_by_hash "$payment_request" "$payment_hash"
}

@test "public-ln-receive: receive via invoice - can receive on btc amountless invoice" {
Expand All @@ -207,13 +264,17 @@ usd_amount=50
payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \
--amt "$btc_amount"

# Check for settled with query
retry 15 1 check_ln_payment_settled "$payment_request"
retry 15 1 check_ln_payment_settled "$payment_request" "$payment_hash"
retry 15 1 check_ln_payment_settled_by_hash "$payment_request" "$payment_hash"
}

@test "public-ln-receive: receive via invoice - can receive on usd amountless invoice" {
Expand All @@ -231,13 +292,17 @@ usd_amount=50
payment_request="$(echo $invoice | jq -r '.paymentRequest')"
[[ "${payment_request}" != "null" ]] || exit 1

payment_hash="$(echo $invoice | jq -r '.paymentHash')"
[[ "${payment_hash}" != "null" ]] || exit 1

# Receive payment
lnd_outside_cli payinvoice -f \
--pay_req "$payment_request" \
--amt "$btc_amount"

# Check for settled with query
retry 15 1 check_ln_payment_settled "$payment_request"
retry 15 1 check_ln_payment_settled "$payment_request" "$payment_hash"
retry 15 1 check_ln_payment_settled_by_hash "$payment_request" "$payment_hash"
}

@test "public-ln-receive: fail to create invoice - invalid wallet-id" {
Expand Down
10 changes: 10 additions & 0 deletions bats/gql/ln-invoice-payment-status-by-hash-sub.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
subscription lnInvoicePaymentStatusByHashSubscription($input: LnInvoicePaymentStatusByHashInput!) {
lnInvoicePaymentStatusByHash(input: $input) {
errors {
message
}
status
paymentHash
paymentRequest
}
}
7 changes: 7 additions & 0 deletions bats/gql/ln-invoice-payment-status-by-hash.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query LnInvoicePaymentStatusByHashQuery($input: LnInvoicePaymentStatusByHashInput!) {
lnInvoicePaymentStatusByHash(input: $input) {
status
paymentHash
paymentRequest
}
}
2 changes: 2 additions & 0 deletions bats/gql/ln-invoice-payment-status-sub.gql
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ subscription lnInvoicePaymentStatusSubscription($input: LnInvoicePaymentStatusIn
message
}
status
paymentHash
paymentRequest
}
}
2 changes: 2 additions & 0 deletions bats/gql/ln-invoice-payment-status.gql
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
query LnInvoicePaymentStatusQuery($input: LnInvoicePaymentStatusInput!) {
lnInvoicePaymentStatus(input: $input) {
status
paymentHash
paymentRequest
}
}
23 changes: 23 additions & 0 deletions bats/helpers/ln.bash
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ check_for_ln_update() {

check_ln_payment_settled() {
local payment_request=$1
local payment_hash=$2

variables=$(
jq -n \
Expand All @@ -104,6 +105,28 @@ check_ln_payment_settled() {
)
exec_graphql 'anon' 'ln-invoice-payment-status' "$variables"
payment_status="$(graphql_output '.data.lnInvoicePaymentStatus.status')"
payment_request_resp="$(graphql_output '.data.lnInvoicePaymentStatus.paymentRequest')"
payment_hash_resp="$(graphql_output '.data.lnInvoicePaymentStatus.paymentHash')"
[[ "${payment_hash}" = "${payment_hash_resp}" ]] || exit 1
[[ "${payment_request}" = "${payment_request_resp}" ]] || exit 1
[[ "${payment_status}" = "PAID" ]]
}

check_ln_payment_settled_by_hash() {
local payment_request=$1
local payment_hash=$2

variables=$(
jq -n \
--arg payment_hash "$payment_hash" \
'{"input": {"paymentHash": $payment_hash}}'
)
exec_graphql 'anon' 'ln-invoice-payment-status-by-hash' "$variables"
payment_status="$(graphql_output '.data.lnInvoicePaymentStatusByHash.status')"
payment_request_resp="$(graphql_output '.data.lnInvoicePaymentStatusByHash.paymentRequest')"
payment_hash_resp="$(graphql_output '.data.lnInvoicePaymentStatusByHash.paymentHash')"
[[ "${payment_hash}" = "${payment_hash_resp}" ]] || exit 1
[[ "${payment_request}" = "${payment_request_resp}" ]] || exit 1
[[ "${payment_status}" = "PAID" ]]
}

Expand Down
16 changes: 14 additions & 2 deletions core/api/dev/apollo-federation/supergraph.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,12 @@ input LnInvoicePaymentInput
walletId: WalletId!
}

input LnInvoicePaymentStatusByHashInput
@join__type(graph: PUBLIC)
{
paymentHash: PaymentHash!
}

input LnInvoicePaymentStatusInput
@join__type(graph: PUBLIC)
{
Expand All @@ -821,6 +827,8 @@ type LnInvoicePaymentStatusPayload
@join__type(graph: PUBLIC)
{
errors: [Error!]!
paymentHash: PaymentHash
paymentRequest: LnPaymentRequest
status: InvoicePaymentStatus
}

Expand Down Expand Up @@ -1028,7 +1036,7 @@ type MapMarker
@join__type(graph: PUBLIC)
{
mapInfo: MapInfo!
username: Username
username: Username!
}

"""Text field in a lightning payment transaction"""
Expand Down Expand Up @@ -1507,10 +1515,11 @@ type Query
{
accountDefaultWallet(username: Username!, walletCurrency: WalletCurrency): PublicWallet! @join__field(graph: PUBLIC)
btcPriceList(range: PriceGraphRange!): [PricePoint] @join__field(graph: PUBLIC)
businessMapMarkers: [MapMarker] @join__field(graph: PUBLIC)
businessMapMarkers: [MapMarker!]! @join__field(graph: PUBLIC)
currencyList: [Currency!]! @join__field(graph: PUBLIC)
globals: Globals @join__field(graph: PUBLIC)
lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload! @join__field(graph: PUBLIC)
lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatusPayload! @join__field(graph: PUBLIC)
me: User @join__field(graph: PUBLIC)
mobileVersions: [MobileVersions] @join__field(graph: PUBLIC)
onChainTxFee(address: OnChainAddress!, amount: SatAmount!, speed: PayoutSpeed! = FAST, walletId: WalletId!): OnChainTxFee! @join__field(graph: PUBLIC)
Expand Down Expand Up @@ -1654,6 +1663,7 @@ type Subscription
@join__type(graph: PUBLIC)
{
lnInvoicePaymentStatus(input: LnInvoicePaymentStatusInput!): LnInvoicePaymentStatusPayload!
lnInvoicePaymentStatusByHash(input: LnInvoicePaymentStatusByHashInput!): LnInvoicePaymentStatusPayload!
myUpdates: MyUpdatesPayload!
price(input: PriceInput!): PricePayload!

Expand Down Expand Up @@ -2023,6 +2033,8 @@ enum UserNotificationCategory
{
CIRCLES @join__enumValue(graph: NOTIFICATIONS)
PAYMENTS @join__enumValue(graph: NOTIFICATIONS)
BALANCE @join__enumValue(graph: NOTIFICATIONS)
ADMIN_NOTIFICATION @join__enumValue(graph: NOTIFICATIONS)
}

enum UserNotificationChannel
Expand Down
16 changes: 15 additions & 1 deletion core/api/src/app/lightning/payment-status-checker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getInvoiceRequestByHash } from "./get-payment-request"

import { RepositoryError } from "@/domain/errors"
import { decodeInvoice } from "@/domain/bitcoin/lightning"
import { LedgerService } from "@/services/ledger"
Expand All @@ -6,9 +8,10 @@ export const PaymentStatusChecker = async (uncheckedPaymentRequest: string) => {
const decodedInvoice = decodeInvoice(uncheckedPaymentRequest)
if (decodedInvoice instanceof Error) return decodedInvoice

const { paymentHash, expiresAt, isExpired } = decodedInvoice
const { paymentRequest, paymentHash, expiresAt, isExpired } = decodedInvoice

return {
paymentRequest,
paymentHash,
expiresAt,
isExpired,
Expand All @@ -20,3 +23,14 @@ export const PaymentStatusChecker = async (uncheckedPaymentRequest: string) => {
},
}
}

export const PaymentStatusCheckerByHash = async ({
paymentHash,
}: {
paymentHash: PaymentHash
}) => {
const paymentRequest = await getInvoiceRequestByHash({ paymentHash })
if (paymentRequest instanceof Error) return paymentRequest

return PaymentStatusChecker(paymentRequest)
}
2 changes: 2 additions & 0 deletions core/api/src/graphql/public/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import BusinessMapMarkersQuery from "@/graphql/public/root/query/business-map-ma
import AccountDefaultWalletQuery from "@/graphql/public/root/query/account-default-wallet"
import AccountDefaultWalletIdQuery from "@/graphql/public/root/query/account-default-wallet-id"
import LnInvoicePaymentStatusQuery from "@/graphql/public/root/query/ln-invoice-payment-status"
import LnInvoicePaymentStatusByHashQuery from "@/graphql/public/root/query/ln-invoice-payment-status-by-hash"

export const queryFields = {
unauthed: {
Expand All @@ -27,6 +28,7 @@ export const queryFields = {
realtimePrice: RealtimePriceQuery,
btcPriceList: BtcPriceListQuery,
lnInvoicePaymentStatus: LnInvoicePaymentStatusQuery,
lnInvoicePaymentStatusByHash: LnInvoicePaymentStatusByHashQuery,
},
authed: {
atAccountLevel: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Lightning } from "@/app"
import { WalletInvoiceStatus } from "@/domain/wallet-invoices"

import { GT } from "@/graphql/index"
import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map"
import LnInvoicePaymentStatusPayload from "@/graphql/public/types/payload/ln-invoice-payment-status"
import LnInvoicePaymentStatusByHashInput from "@/graphql/public/types/object/ln-invoice-payment-status-by-hash-input"

const LnInvoicePaymentStatusByHashQuery = GT.Field({
type: GT.NonNull(LnInvoicePaymentStatusPayload),
args: {
input: { type: GT.NonNull(LnInvoicePaymentStatusByHashInput) },
},
resolve: async (_, args) => {
const { paymentHash } = args.input
if (paymentHash instanceof Error) {
return { errors: [{ message: paymentHash.message }] }
}

const paymentStatusChecker = await Lightning.PaymentStatusCheckerByHash({
paymentHash,
})
if (paymentStatusChecker instanceof Error) {
return { errors: [mapAndParseErrorForGqlResponse(paymentStatusChecker)] }
}

const paid = await paymentStatusChecker.invoiceIsPaid()
if (paid instanceof Error) {
return { errors: [mapAndParseErrorForGqlResponse(paid)] }
}

const { paymentRequest, isExpired } = paymentStatusChecker

if (paid) {
return { errors: [], paymentHash, paymentRequest, status: WalletInvoiceStatus.Paid }
}

const status = isExpired ? WalletInvoiceStatus.Expired : WalletInvoiceStatus.Pending
return { errors: [], paymentHash, paymentRequest, status }
},
})

export default LnInvoicePaymentStatusByHashQuery
Loading

0 comments on commit a3cb01e

Please sign in to comment.