17
17
package fr .acinq .eclair .payment .receive
18
18
19
19
import akka .actor .Actor .Receive
20
+ import akka .actor .typed .Behavior
21
+ import akka .actor .typed .scaladsl .Behaviors
22
+ import akka .actor .typed .scaladsl .adapter .ClassicActorContextOps
20
23
import akka .actor .{ActorContext , ActorRef , PoisonPill , Status }
21
24
import akka .event .{DiagnosticLoggingAdapter , LoggingAdapter }
22
25
import fr .acinq .bitcoin .{ByteVector32 , Crypto }
@@ -28,6 +31,7 @@ import fr.acinq.eclair.payment.{IncomingPacket, PaymentReceived, PaymentRequest}
28
31
import fr .acinq .eclair .wire .protocol ._
29
32
import fr .acinq .eclair .{Features , Logs , MilliSatoshi , NodeParams , randomBytes32 }
30
33
34
+ import java .util .UUID
31
35
import scala .util .{Failure , Success , Try }
32
36
33
37
/**
@@ -52,26 +56,9 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
52
56
def postFulfill (paymentReceived : PaymentReceived )(implicit log : LoggingAdapter ): Unit = ()
53
57
54
58
override def handle (implicit ctx : ActorContext , log : DiagnosticLoggingAdapter ): Receive = {
55
- case ReceivePayment (amount_opt, desc, expirySeconds_opt, extraHops, fallbackAddress_opt, paymentPreimage_opt, paymentType) =>
56
- Try {
57
- val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32())
58
- val paymentHash = Crypto .sha256(paymentPreimage)
59
- val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
60
- val features = {
61
- val f1 = Seq (Features .PaymentSecret .mandatory, Features .VariableLengthOnion .mandatory)
62
- val allowMultiPart = nodeParams.features.hasFeature(Features .BasicMultiPartPayment )
63
- val f2 = if (allowMultiPart) Seq (Features .BasicMultiPartPayment .optional) else Nil
64
- val f3 = if (nodeParams.enableTrampolinePayment) Seq (Features .TrampolinePayment .optional) else Nil
65
- PaymentRequest .PaymentRequestFeatures (f1 ++ f2 ++ f3 : _* )
66
- }
67
- val paymentRequest = PaymentRequest (nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, nodeParams.minFinalExpiryDelta, fallbackAddress_opt, expirySeconds = Some (expirySeconds), extraHops = extraHops, features = features)
68
- log.debug(" generated payment request={} from amount={}" , PaymentRequest .write(paymentRequest), amount_opt)
69
- db.addIncomingPayment(paymentRequest, paymentPreimage, paymentType)
70
- paymentRequest
71
- } match {
72
- case Success (paymentRequest) => ctx.sender ! paymentRequest
73
- case Failure (exception) => ctx.sender ! Status .Failure (exception)
74
- }
59
+ case receivePayment : ReceivePayment =>
60
+ val child = ctx.spawn(CreateInvoiceActor (nodeParams), name = UUID .randomUUID().toString)
61
+ child ! CreateInvoiceActor .CreatePaymentRequest (ctx.sender(), receivePayment)
75
62
76
63
case p : IncomingPacket .FinalPacket if doHandle(p.add.paymentHash) =>
77
64
Logs .withMdc(log)(Logs .mdc(paymentHash_opt = Some (p.add.paymentHash))) {
@@ -188,21 +175,59 @@ object MultiPartHandler {
188
175
/**
189
176
* Use this message to create a Bolt 11 invoice to receive a payment.
190
177
*
191
- * @param amount_opt amount to receive in milli-satoshis.
192
- * @param description payment description.
193
- * @param expirySeconds_opt number of seconds before the invoice expires (relative to the invoice creation time).
194
- * @param extraHops routing hints to help the payer.
195
- * @param fallbackAddress fallback Bitcoin address.
196
- * @param paymentPreimage payment preimage.
178
+ * @param amount_opt amount to receive in milli-satoshis.
179
+ * @param description payment description.
180
+ * @param expirySeconds_opt number of seconds before the invoice expires (relative to the invoice creation time).
181
+ * @param extraHops routing hints to help the payer.
182
+ * @param fallbackAddress_opt fallback Bitcoin address.
183
+ * @param paymentPreimage_opt payment preimage.
197
184
*/
198
185
case class ReceivePayment (amount_opt : Option [MilliSatoshi ],
199
186
description : String ,
200
187
expirySeconds_opt : Option [Long ] = None ,
201
188
extraHops : List [List [ExtraHop ]] = Nil ,
202
- fallbackAddress : Option [String ] = None ,
203
- paymentPreimage : Option [ByteVector32 ] = None ,
189
+ fallbackAddress_opt : Option [String ] = None ,
190
+ paymentPreimage_opt : Option [ByteVector32 ] = None ,
204
191
paymentType : String = PaymentType .Standard )
205
192
193
+
194
+ object CreateInvoiceActor {
195
+
196
+ // @formatter:off
197
+ sealed trait Command
198
+ case class CreatePaymentRequest (replyTo : ActorRef , receivePayment : ReceivePayment ) extends Command
199
+ // @formatter:on
200
+
201
+ def apply (nodeParams : NodeParams ): Behavior [Command ] = {
202
+ Behaviors .setup { context =>
203
+ Behaviors .receiveMessage {
204
+ case CreatePaymentRequest (replyTo, receivePayment) =>
205
+ Try {
206
+ import receivePayment ._
207
+ val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32())
208
+ val paymentHash = Crypto .sha256(paymentPreimage)
209
+ val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
210
+ val features = {
211
+ val f1 = Seq (Features .PaymentSecret .mandatory, Features .VariableLengthOnion .mandatory)
212
+ val allowMultiPart = nodeParams.features.hasFeature(Features .BasicMultiPartPayment )
213
+ val f2 = if (allowMultiPart) Seq (Features .BasicMultiPartPayment .optional) else Nil
214
+ val f3 = if (nodeParams.enableTrampolinePayment) Seq (Features .TrampolinePayment .optional) else Nil
215
+ PaymentRequest .PaymentRequestFeatures (f1 ++ f2 ++ f3 : _* )
216
+ }
217
+ val paymentRequest = PaymentRequest (nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, description, nodeParams.minFinalExpiryDelta, fallbackAddress_opt, expirySeconds = Some (expirySeconds), extraHops = extraHops, features = features)
218
+ context.log.debug(" generated payment request={} from amount={}" , PaymentRequest .write(paymentRequest), amount_opt)
219
+ nodeParams.db.payments.addIncomingPayment(paymentRequest, paymentPreimage, paymentType)
220
+ paymentRequest
221
+ } match {
222
+ case Success (paymentRequest) => replyTo ! paymentRequest
223
+ case Failure (exception) => replyTo ! Status .Failure (exception)
224
+ }
225
+ Behaviors .stopped
226
+ }
227
+ }
228
+ }
229
+ }
230
+
206
231
private def validatePaymentStatus (payment : IncomingPacket .FinalPacket , record : IncomingPayment )(implicit log : LoggingAdapter ): Boolean = {
207
232
if (record.status.isInstanceOf [IncomingPaymentStatus .Received ]) {
208
233
log.warning(" ignoring incoming payment for which has already been paid" )
0 commit comments