Skip to content

Commit

Permalink
Secure payment term support (relies on existing go-dpp changes) (bitc…
Browse files Browse the repository at this point in the history
  • Loading branch information
rt121212121 authored and AustEcon committed Oct 11, 2022
1 parent bd857da commit 288db72
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 36 deletions.
29 changes: 21 additions & 8 deletions data/noop/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package noop

import (
"context"
"github.com/libsv/go-dpp/modes/hybridmode"
"time"

"github.com/libsv/go-dpp/modes/hybridmode"

"github.com/bitcoin-sv/dpp-proxy/log"
"github.com/libsv/go-bk/envelope"
"github.com/libsv/go-bt/v2/bscript"
"github.com/libsv/go-dpp"
"github.com/libsv/go-dpp/nativetypes"
Expand Down Expand Up @@ -34,7 +36,7 @@ func (n *noop) PaymentCreate(ctx context.Context, args dpp.PaymentCreateArgs, re
func (n noop) PaymentTerms(ctx context.Context, args dpp.PaymentTermsArgs) (*dpp.PaymentTerms, error) {
return &dpp.PaymentTerms{
Network: "noop",
Version: "1.0",
Version: "1.0",
CreationTimestamp: time.Now().Unix(),
ExpirationTimestamp: time.Now().Add(time.Hour).Unix(),
Memo: "noop",
Expand All @@ -52,24 +54,35 @@ func (n noop) PaymentTerms(ctx context.Context, args dpp.PaymentTermsArgs) (*dpp
"choiceID0": {
"transactions": {
hybridmode.TransactionTerms{
Outputs: hybridmode.Outputs{ NativeOutputs: []nativetypes.NativeOutput{
Outputs: hybridmode.Outputs{NativeOutputs: []nativetypes.NativeOutput{
{
Amount: 1000,
Amount: 1000,
LockingScript: func() *bscript.Script {
ls, _ := bscript.NewFromHexString(
"76a91493d0d43918a5df78f08cfe22a4e022846b6736c288ac")
return ls
}(),
Description: "noop description",
Description: "noop description",
},
} },
Inputs: hybridmode.Inputs{},
}},
Inputs: hybridmode.Inputs{},
Policies: &hybridmode.Policies{},
},
},
},

},
},
}, nil
}

func (n noop) PaymentTermsSecure(ctx context.Context, args dpp.PaymentTermsArgs) (*envelope.JSONEnvelope, error) {
var signature string = "3044022004cf2c5711f34f0de11fd316074c44ce0f63a525840aae0cf61d9dee04b317b102201a56049354449ddce3d8b059403b2d866662b6d1f9d0064365d420406d8d992d"
var publicKey string = "03d546057437f3279f66d6ae91a03ffe1120ef3a79b8f186d9b6a8f1e0582ccf78"
return &envelope.JSONEnvelope{
Payload: "{\"network\":\"mainnet\",\"creationTimestamp\":{},\"expirationTimestamp\":{},\"url\":\"https://localhost:3443/api/v1/payment/123456\",\"memo\":\"string\",\"beneficiary\":{\"name\":\"beneficiary 1\",\"avatar\":\"http://url.com\",\"extensions\":{\"email\":\"[email protected]\",\"address\":\"1 the street, the town, B1 1AA\",\"additionalProp1\":{}}},\"outputs\":[{\"amount\":100000,\"script\":\"76a91455b61be43392125d127f1780fb038437cd67ef9c88ac\",\"description\":\"paymentReference 123456\"}],\"fees\":{\"data\":{\"satoshis\":0,\"bytes\":0},\"standard\":{\"satoshis\":0,\"bytes\":0}},\"ancestry\":{\"format\":\"binary\",\"minDepth\":0}}",
Signature: &signature,
PublicKey: &publicKey,
Encoding: "UTF-8",
MimeType: "application/json",
}, nil
}
13 changes: 9 additions & 4 deletions data/payd/payd.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,21 @@ func (p *payd) PaymentTerms(ctx context.Context, args dpp.PaymentTermsArgs) (*dp
return &resp, nil
}

// PaymentTerms will fetch a payment request message from payd for a given messages.
func (p *payd) PaymentTermsSecure(ctx context.Context, args dpp.PaymentTermsArgs) (*envelope.JSONEnvelope, error) {
return nil, errors.New("PayD unsupported for payment terms (secure)")
}

// PaymentCreate will post a request to payd to validate and add the txos to the wallet.
//
// If invalid a non 204 status code is returned.
func (p *payd) PaymentCreate(ctx context.Context, args dpp.PaymentCreateArgs, req dpp.Payment) (*dpp.PaymentACK, error) {
paymentReq := models.PayDPayment{
ModeID: req.ModeID,
Mode: req.Mode,
Originator: req.Originator,
ModeID: req.ModeID,
Mode: req.Mode,
Originator: req.Originator,
Transaction: req.Transaction,
Memo: req.Memo,
Memo: req.Memo,
}
var ack dpp.PaymentACK
if err := p.client.Do(ctx, http.MethodPost, fmt.Sprintf(urlPayments, p.baseURL(), args.PaymentID), http.StatusNoContent, paymentReq, &ack); err != nil {
Expand Down
49 changes: 42 additions & 7 deletions data/sockets/payd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import (

// Routes contain the unique keys for socket messages used in the payment protocol.
const (
RoutePayment = "payment"
RoutePaymentACK = "payment.ack"
RoutePaymentError = "payment.error"
RouteProofCreate = "proof.create"
RoutePaymentTermsCreate = "paymentterms.create"
RoutePaymentTermsResponse = "paymentterms.response"
RoutePaymentTermsError = "paymentterms.error"
RoutePayment = "payment"
RoutePaymentACK = "payment.ack"
RoutePaymentError = "payment.error"
RouteProofCreate = "proof.create"
RoutePaymentTermsCreate = "paymentterms.create"
RoutePaymentTermsResponse = "paymentterms.response"
RoutePaymentTermsError = "paymentterms.error"

appID = "dpp"
)
Expand Down Expand Up @@ -85,6 +85,41 @@ func (p *payd) PaymentTerms(ctx context.Context, args dpp.PaymentTermsArgs) (*dp
return nil, fmt.Errorf("unexpected response key '%s'", resp.Key())
}

// PaymentTerms will send a socket request to a payd client for a payment request.
// It will wait on a response before returnign the payment request.
func (p *payd) PaymentTermsSecure(ctx context.Context, args dpp.PaymentTermsArgs) (*envelope.JSONEnvelope, error) {
msg := sockets.NewMessage(RoutePaymentTermsCreate, "", args.PaymentID)
msg.AppID = appID
msg.CorrelationID = uuid.NewString()

ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

resp, err := p.s.BroadcastAwait(ctx, args.PaymentID, msg)
if err != nil {
if errors.Is(err, sockets.ErrChannelNotFound) {
return nil, errs.NewErrNotFound("N00001", "invoice not found")
}
return nil, errors.Wrap(err, "failed to broadcast message for payment terms (secure)")
}
switch resp.Key() {
case RoutePaymentTermsResponse:
var pr *envelope.JSONEnvelope
if err := resp.Bind(&pr); err != nil {
return nil, errors.Wrap(err, "failed to bind payment terms (secure) response")
}
return pr, nil
case RoutePaymentTermsError:
var clientErr server.ClientError
if err := resp.Bind(&clientErr); err != nil {
return nil, errors.Wrap(err, "failed to bind error response")
}
return nil, toLathosErr(clientErr)
}

return nil, fmt.Errorf("unexpected response key '%s'", resp.Key())
}

// PaymentCreate will send a request to payd to create and process the payment.
func (p *payd) PaymentCreate(ctx context.Context, args dpp.PaymentCreateArgs, req dpp.Payment) (*dpp.PaymentACK, error) {
msg := sockets.NewMessage(RoutePayment, "", args.PaymentID)
Expand Down
17 changes: 16 additions & 1 deletion service/paymentrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package service
import (
"context"

"github.com/libsv/go-bk/envelope"
"github.com/libsv/go-dpp"
"github.com/pkg/errors"
validator "github.com/theflyingcodr/govalidator"
)


type paymentTerms struct {
prRdr dpp.PaymentTermsReader
}
Expand Down Expand Up @@ -40,3 +40,18 @@ func (p *paymentTerms) PaymentTerms(ctx context.Context, args dpp.PaymentTermsAr

return pReq, nil
}

// PaymentTerms handles setting up a new PaymentTerms response and will validate that we have a paymentID.
func (p *paymentTerms) PaymentTermsSecure(ctx context.Context, args dpp.PaymentTermsArgs) (*envelope.JSONEnvelope, error) {
if err := validator.New().
Validate("paymentID", validator.NotEmpty(args.PaymentID)); err.Err() != nil {
return nil, err
}

pReq, err := p.prRdr.PaymentTermsSecure(ctx, args)
if err != nil {
return nil, errors.Wrapf(err, "failed to get payment request for paymentID %s", args.PaymentID)
}

return pReq, nil
}
14 changes: 14 additions & 0 deletions service/paymentrequest_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/url"

"github.com/bitcoin-sv/dpp-proxy/config"
"github.com/libsv/go-bk/envelope"
"github.com/libsv/go-dpp"
"github.com/pkg/errors"
validator "github.com/theflyingcodr/govalidator"
Expand Down Expand Up @@ -59,3 +60,16 @@ func (p *paymentTermsProxy) PaymentTerms(ctx context.Context, args dpp.PaymentTe

return resp, nil
}

// PaymentTerms will call to the data layer to return a full payment request.
func (p *paymentTermsProxy) PaymentTermsSecure(ctx context.Context, args dpp.PaymentTermsArgs) (*envelope.JSONEnvelope, error) {
if err := validator.New().
Validate("paymentID", validator.NotEmpty(args.PaymentID)); err.Err() != nil {
return nil, err
}
resp, err := p.preqRdr.PaymentTermsSecure(ctx, args)
if err != nil {
return nil, errors.Wrapf(err, "failed to read payment request for paymentID %s", args.PaymentID)
}
return resp, nil
}
25 changes: 25 additions & 0 deletions transports/http/payment_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func NewPaymentTermsHandler(svc dpp.PaymentTermsService) *PaymentTermsHandler {
// RegisterRoutes will setup all routes with an echo group.
func (h *PaymentTermsHandler) RegisterRoutes(g *echo.Group) {
g.GET(RouteV1PaymentTerms, h.buildPaymentTerms)
g.GET(RouteV1PaymentTermsSecure, h.buildPaymentTermsSecure)
}

// buildPaymentTerms will setup and return a new payment request.
Expand All @@ -50,3 +51,27 @@ func (h *PaymentTermsHandler) buildPaymentTerms(e echo.Context) error {
}
return e.JSON(http.StatusOK, resp)
}

// buildPaymentTermsSecure will setup and return a new payment request.
// @Summary Request to pay an invoice and receive back outputs to use when constructing the payment transaction
// @Description Creates a payment request based on a payment id (the identifier for an invoice).
// @Tags Payment
// @Accept json
// @Produce json
// @Param paymentID path string true "Payment ID"
// @Success 201 {object} envelope.JSONEnvelope "contains the signed PaymentTerms"
// @Failure 404 {object} server.ClientError "returned if the paymentID has not been found"
// @Failure 400 {object} server.ClientError "returned if the user input is invalid, usually an issue with the paymentID"
// @Failure 500 {string} string "returned if there is an unexpected internal error"
// @Router /api/v1/payment/sec/{paymentID} [GET].
func (h *PaymentTermsHandler) buildPaymentTermsSecure(e echo.Context) error {
var args dpp.PaymentTermsArgs
if err := e.Bind(&args); err != nil {
return errors.Wrap(err, "failed to bind request")
}
resp, err := h.svc.PaymentTermsSecure(e.Request().Context(), args)
if err != nil {
return errors.WithStack(err)
}
return e.JSON(http.StatusOK, resp)
}
7 changes: 4 additions & 3 deletions transports/http/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package http

// Routes used in the http handlers.
const (
RouteV1PaymentTerms = "api/v1/payment/:paymentID"
RouteV1Payment = "api/v1/payment/:paymentID"
RouteV1Proofs = "api/v1/proofs/:txid"
RouteV1PaymentTerms = "api/v1/payment/:paymentID"
RouteV1PaymentTermsSecure = "api/v1/payment/sec/:paymentID"
RouteV1Payment = "api/v1/payment/:paymentID"
RouteV1Proofs = "api/v1/proofs/:txid"
)
76 changes: 64 additions & 12 deletions vendor/github.com/libsv/go-dpp/mocks/payment_request_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion vendor/github.com/libsv/go-dpp/payment_terms.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 288db72

Please sign in to comment.