From 17ae7c87ba43c3b353f17dcd24e469b040843edd Mon Sep 17 00:00:00 2001 From: x1m3 Date: Fri, 25 Oct 2024 18:00:04 +0200 Subject: [PATCH 01/11] feat: PaymentRequest and Payment Messages --- protocol/payment.go | 394 +++++++++++++++++++++++++++++++++ protocol/payment_test.go | 465 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 859 insertions(+) create mode 100644 protocol/payment.go create mode 100644 protocol/payment_test.go diff --git a/protocol/payment.go b/protocol/payment.go new file mode 100644 index 0000000..c069ed3 --- /dev/null +++ b/protocol/payment.go @@ -0,0 +1,394 @@ +package protocol + +import ( + "encoding/json" + "fmt" + + "github.com/pkg/errors" + + "github.com/iden3/iden3comm/v2" +) + +const ( + // PaymentRequestMessageType is a Iden3PaymentMessage payment type + PaymentRequestMessageType iden3comm.ProtocolMessage = iden3comm.DidCommProtocol + "credentials/0.1/payment-request" + + // PaymentMessageType is a Iden3PaymentMessage payment type + PaymentMessageType iden3comm.ProtocolMessage = iden3comm.DidCommProtocol + "credentials/0.1/payment" + + // Iden3PaymentRequestCryptoV1Type is a Iden3PaymentRequestCryptoV1 payment type + Iden3PaymentRequestCryptoV1Type = "Iden3PaymentRequestCryptoV1" + + // Iden3PaymentRailsRequestV1Type is a Iden3PaymentRailsRequestV1 payment type + Iden3PaymentRailsRequestV1Type = "Iden3PaymentRailsRequestV1" + + // Iden3PaymentCryptoV1Type is a Iden3PaymentCryptoV1 payment type + Iden3PaymentCryptoV1Type = "Iden3PaymentCryptoV1" + + // Iden3PaymentRailsV1Type is a Iden3PaymentRailsV1 payment type + Iden3PaymentRailsV1Type = "Iden3PaymentRailsV1" +) + +// PaymentRequestMessage represents Iden3message for payment request. +type PaymentRequestMessage struct { + ID string `json:"id"` + Typ iden3comm.MediaType `json:"typ,omitempty"` + Type iden3comm.ProtocolMessage `json:"type"` + ThreadID string `json:"thid,omitempty"` + + Body PaymentRequestMessageBody `json:"body,omitempty"` + + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` +} + +// PaymentRequestMessageBody represents the body of the PaymentRequestMessage. +type PaymentRequestMessageBody struct { + Agent string `json:"agent"` + Payments []PaymentRequestInfo `json:"payments"` +} + +// PaymentRequestInfo represents the payments request information. +type PaymentRequestInfo struct { + Type string `json:"type,omitempty"` + Credentials []PaymentRequestInfoCredentials `json:"credentials"` + Description string `json:"description"` + Data PaymentRequestInfoData `json:"data"` +} + +// PaymentRequestInfoData is a union type for field Data in PaymentRequestInfo. +// Only one of the fields can be set at a time. +type PaymentRequestInfoData struct { + crypto []Iden3PaymentRequestCryptoV1 + rails []Iden3PaymentRailsRequestV1 +} + +// NewPaymentRequestInfoDataCrypto creates a new PaymentRequestInfoData with Iden3PaymentRequestCryptoV1 data. +func NewPaymentRequestInfoDataCrypto(data Iden3PaymentRequestCryptoV1) PaymentRequestInfoData { + return PaymentRequestInfoData{ + rails: nil, + crypto: []Iden3PaymentRequestCryptoV1{data}, + } +} + +// NewPaymentRequestInfoDataRails creates a new PaymentRequestInfoData with Iden3PaymentRailsRequestV1 data. +func NewPaymentRequestInfoDataRails(data Iden3PaymentRailsRequestV1) PaymentRequestInfoData { + return PaymentRequestInfoData{ + rails: []Iden3PaymentRailsRequestV1{data}, + crypto: nil, + } +} + +// Type returns the type of the data in the union. You can use Data() to get the data. +func (p *PaymentRequestInfoData) Type() string { + if len(p.crypto) != 0 { + return Iden3PaymentRequestCryptoV1Type + } + if len(p.rails) != 0 { + return Iden3PaymentRailsRequestV1Type + } + return "" +} + +// Data returns the data in the union. You can use Type() to determine the type of the data. +func (p *PaymentRequestInfoData) Data() interface{} { + if len(p.crypto) != 0 { + return p.crypto + } + if len(p.rails) != 0 { + return p.rails + } + return nil +} + +// MarshalJSON marshals the PaymentRequestInfoData into JSON. +func (p PaymentRequestInfoData) MarshalJSON() ([]byte, error) { + if len(p.crypto) != 0 { + return json.Marshal(p.crypto[0]) + } + if len(p.rails) != 0 { + return json.Marshal(p.rails) + } + return nil, errors.New("failed to marshal not initialized PaymentRequestInfoData") +} + +// UnmarshalJSON unmarshal the PaymentRequestInfoData from JSON. +func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { + var crypto Iden3PaymentRequestCryptoV1 + var cryptoCol []Iden3PaymentRequestCryptoV1 + var rails Iden3PaymentRailsRequestV1 + var railsCol []Iden3PaymentRailsRequestV1 + + if err := json.Unmarshal(data, &crypto); err == nil { + if crypto.Type == Iden3PaymentRequestCryptoV1Type { + p.crypto = append(p.crypto, crypto) + return nil + } + } + if err := json.Unmarshal(data, &cryptoCol); err == nil { + if len(cryptoCol) != 0 { + if cryptoCol[0].Type == Iden3PaymentRequestCryptoV1Type { + p.crypto = append(p.crypto, cryptoCol...) + return nil + } + } + } + if err := json.Unmarshal(data, &rails); err == nil { + if rails.Type == Iden3PaymentRailsRequestV1Type { + p.rails = append(p.rails, rails) + return nil + } + } + if err := json.Unmarshal(data, &railsCol); err == nil { + if len(railsCol) != 0 { + if railsCol[0].Type == Iden3PaymentRailsRequestV1Type { + p.rails = append(p.rails, railsCol...) + return nil + } + } + } + return errors.Errorf("failed to unmarshal PaymentRequestInfoData: %s", string(data)) +} + +// Iden3PaymentRequestCryptoV1 represents the Iden3PaymentRequestCryptoV1 payment request data. +type Iden3PaymentRequestCryptoV1 struct { + Type string `json:"type"` + ID string `json:"id"` + Context string `json:"@context,omitempty"` + ChainID string `json:"chainId"` + Address string `json:"address"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Expiration string `json:"expiration,omitempty"` +} + +// Iden3PaymentRailsRequestV1 represents the Iden3PaymentRailsRequestV1 payment request data. +type Iden3PaymentRailsRequestV1 struct { + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof EthereumEip712Signature2021Col `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` +} + +// EthereumEip712Signature2021Col is a list of EthereumEip712Signature2021. +type EthereumEip712Signature2021Col []EthereumEip712Signature2021 + +// UnmarshalJSON unmarshal the PaymentRequestInfoData from JSON. +func (p *EthereumEip712Signature2021Col) UnmarshalJSON(data []byte) error { + var col []EthereumEip712Signature2021 + if err := json.Unmarshal(data, &col); err != nil { + var single EthereumEip712Signature2021 + if err := json.Unmarshal(data, &single); err != nil { + return fmt.Errorf("failed to unmarshal EthereumEip712Signature2021Col: %w", err) + } + col = append(col, single) + } + *p = col + return nil +} + +// EthereumEip712Signature2021 represents the Ethereum EIP712 signature. +type EthereumEip712Signature2021 struct { + Type string `json:"type"` + ProofPurpose string `json:"proofPurpose"` + ProofValue string `json:"proofValue"` + VerificationMethod string `json:"verificationMethod"` + Created string `json:"created"` + Eip712 Eip712Data `json:"eip712"` +} + +// Eip712Data represents the EIP712 data. +type Eip712Data struct { + Types string `json:"types"` + PrimaryType string `json:"primaryType"` + Domain Eip712Domain `json:"domain"` +} + +// Eip712Domain represents the EIP712 domain. +type Eip712Domain struct { + Name string `json:"name"` + Version string `json:"version"` + ChainId string `json:"chainId"` + VerifyingContract string `json:"verifyingContract"` + Salt string `json:"salt"` +} + +// PaymentRequestInfoCredentials represents the payment request credentials. +type PaymentRequestInfoCredentials struct { + Context string `json:"context,omitempty"` + Type string `json:"type,omitempty"` +} + +// PaymentMessage represents Iden3message for payment. +type PaymentMessage struct { + ID string `json:"id"` + Typ iden3comm.MediaType `json:"typ,omitempty"` + Type iden3comm.ProtocolMessage `json:"type"` + ThreadID string `json:"thid,omitempty"` + + Body PaymentMessageBody `json:"body,omitempty"` + + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` +} + +// PaymentMessageBody represents the body of the PaymentMessage. +type PaymentMessageBody struct { + Payments []Payment `json:"payments"` +} + +// Payment is a union type for field Payments in PaymentMessageBody. +// Only one of the fields can be set at a time. +type Payment struct { + crypto *Iden3PaymentCryptoV1 + rails *Iden3PaymentRailsV1 +} + +// Type returns the type of the data in the union. You can use Data() to get the data. +func (p *Payment) Type() string { + if p.crypto != nil { + return Iden3PaymentCryptoV1Type + } + if p.rails != nil { + return Iden3PaymentRailsV1Type + } + return "" +} + +// Data returns the data in the union. You can use Type() to determine the type of the data. +func (p *Payment) Data() interface{} { + if p.crypto != nil { + return p.crypto + } + if p.rails != nil { + return p.rails + } + return nil +} + +// UnmarshalJSON unmarshal the Payment from JSON. +func (p *Payment) UnmarshalJSON(bytes []byte) error { + var crypto Iden3PaymentCryptoV1 + var rails Iden3PaymentRailsV1 + if json.Unmarshal(bytes, &crypto) == nil { + if crypto.Type == Iden3PaymentCryptoV1Type { + p.crypto = &crypto + return nil + } + } + if json.Unmarshal(bytes, &rails) == nil { + if rails.Type == Iden3PaymentRailsV1Type { + p.rails = &rails + return nil + } + } + return errors.Errorf("failed to unmarshal PaymentRequestInfoData: %s", string(bytes)) +} + +// MarshalJSON marshals the Payment into JSON. +func (p Payment) MarshalJSON() ([]byte, error) { + if p.crypto != nil { + return json.Marshal(p.crypto) + } + if p.rails != nil { + return json.Marshal(p.rails) + } + return nil, errors.New("failed to marshal not initialized Payment") +} + +// Iden3PaymentCryptoV1 represents the Iden3PaymentCryptoV1 payment data. +type Iden3PaymentCryptoV1 struct { + ID string `json:"id"` + Type string `json:"type"` + Context PaymentContext `json:"@context,omitempty"` + PaymentData struct { + TxID string `json:"txId"` + } `json:"paymentData"` +} + +// Iden3PaymentRailsV1 represents the Iden3PaymentRailsV1 payment data. +type Iden3PaymentRailsV1 struct { + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context,omitempty"` + PaymentData struct { + TxID string `json:"txId"` + ChainID string `json:"chainId"` + } `json:"paymentData"` +} + +// PaymentContext represents the payment context. +type PaymentContext struct { + str *string + strCol []string + itemCol []interface{} +} + +// NewPaymentContextString creates a new PaymentContext with a string. +func NewPaymentContextString(str string) PaymentContext { + return PaymentContext{str: &str} +} + +// NewPaymentContextStringCol creates a new PaymentContext with a string collection. +func NewPaymentContextStringCol(strCol []string) PaymentContext { + return PaymentContext{strCol: strCol} +} + +// NewPaymentContextItemCol creates a new PaymentContext with an interface{} collection. +func NewPaymentContextItemCol(itemCol []interface{}) PaymentContext { + return PaymentContext{itemCol: itemCol} +} + +// MarshalJSON marshals the PaymentContext into JSON. +func (p PaymentContext) MarshalJSON() ([]byte, error) { + if p.str != nil { + return json.Marshal(p.str) + } + if len(p.strCol) != 0 { + return json.Marshal(p.strCol) + } + if len(p.itemCol) != 0 { + return json.Marshal(p.itemCol) + } + return nil, errors.New("failed to marshal not initialized PaymentContext") +} + +// UnmarshalJSON unmarshal the PaymentContext from JSON. +func (p *PaymentContext) UnmarshalJSON(data []byte) error { + var str string + var strCol []string + var itemCol []interface{} + + if err := json.Unmarshal(data, &str); err == nil { + p.str = &str + return nil + } + if err := json.Unmarshal(data, &strCol); err == nil { + p.strCol = strCol + return nil + } + if err := json.Unmarshal(data, &itemCol); err == nil { + p.itemCol = itemCol + return nil + } + return errors.Errorf("failed to unmarshal PaymentContext: %s", string(data)) +} + +// Data returns the data in the union. +func (p PaymentContext) Data() interface{} { + if p.str != nil { + return p.str + } + if len(p.strCol) != 0 { + return p.strCol + } + if len(p.itemCol) != 0 { + return p.itemCol + } + return nil +} diff --git a/protocol/payment_test.go b/protocol/payment_test.go new file mode 100644 index 0000000..45e860b --- /dev/null +++ b/protocol/payment_test.go @@ -0,0 +1,465 @@ +package protocol + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { + const paymentRequestTypeIden3PaymentRequestCryptoV1 = ` + { + "id": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "thid": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment-request", + "body": { + "agent": "", + "payments": [ + { + "credentials": [ + { + "type": "AML", + "context": "" + } + ], + "data": { + "type":"Iden3PaymentRequestCryptoV1", + "amount":"10", + "id": "ox", + "chainId": "80002", + "address": "0xpay1", + "currency": "ETH", + "expiration": "" + }, + "description":"you can pass the verification on our KYC provider by following the next link" + } + ] + }, + "to": "did:polygonid:polygon:mumbai:2qJUZDSCFtpR8QvHyBC4eFm6ab9sJo5rqPbcaeyGC4", + "from": "did:iden3:polygon:mumbai:x3HstHLj2rTp6HHXk2WczYP7w3rpCsRbwCMeaQ2H2" +} +` + + const paymentRequestTypeIden3PaymentRailsRequestV1 = ` +{ + "id": "b79b037c-0a6f-4031-a343-46e52e5f606b", + "thid": "b79b037c-0a6f-4031-a343-46e52e5f606b", + "from": "did:iden3:polygon:amoy:xCRp75DgAdS63W65fmXHz6p9DwdonuRU9e46DifhX", + "to": "did:iden3:polygon:amoy:x7Z95VkUuyo6mqraJw2VGwCfqTzdqhM1RVjRHzcpK", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment-request", + "body": { + "agent": "", + "payments": [ + { + "data": [ + { + "type": "Iden3PaymentRailsRequestV1", + "@context": "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "30001", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xd881e175b548b940406e8ed97e0fe58134ac93e381cb53c35f940040b1d890540cc9f4c0f3bf42b35d3dc72d29b203af5cbae968fafc1342332f63c3581a8e691b", + "verificationMethod": "did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.964Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "80002", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + }, + { + "type": "Iden3PaymentRailsRequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "60002", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xd3606f34447c437ef7c13a1e407ad75597ee0229590de286e5775d1f1335ff93601d7a6398d69a4a3e2ddb4da5e30e61ef388d2ad6e0b50041731eaf3afe41ad1b", + "verificationMethod": "did:pkh:eip155:1101:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.967Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "1101", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + }, + { + "type": "Iden3PaymentRailsRequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "90003", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0x1a4e1c250eb0654b1d2dbd5a6b65ffd1483d37fa9c0ef2ef5bf5b9f52d129ccf0ce43beaad86b0d4ade03f33c8b1825a38ba6417576b79f50485380d1dfdad661b", + "verificationMethod": "did:pkh:eip155:59141:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.970Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "59141", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + } + ], + "credentials": [ + { + "type": "AML", + "context": "http://test.com" + } + ], + "description": "Iden3PaymentRailsRequestV1 payment-request integration test" + } + ] + } +} +` + + for _, tc := range []struct { + desc string + payload []byte + expectedPayload []byte + }{ + { + desc: "PaymentRequestMessage of type PaymentRequestCryptoV1", + payload: []byte(paymentRequestTypeIden3PaymentRequestCryptoV1), + expectedPayload: []byte(paymentRequestTypeIden3PaymentRequestCryptoV1), + }, + { + desc: "PaymentRequestMessage of type Iden3PaymentRailsRequestV1", + payload: []byte(paymentRequestTypeIden3PaymentRailsRequestV1), + expectedPayload: []byte(paymentRequestTypeIden3PaymentRailsRequestV1), + }, + } { + + t.Run(tc.desc, func(t *testing.T) { + var msg PaymentRequestMessage + err := json.Unmarshal(tc.payload, &msg) + require.NoError(t, err) + payload, err := json.Marshal(msg) + require.NoError(t, err) + assert.JSONEq(t, string(tc.expectedPayload), string(payload)) + + }) + } +} + +func TestEthereumEip712Signature2021Col(t *testing.T) { + const eip712Signature2021InList = ` + [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0x1a4e1c250eb0654b1d2dbd5a6b65ffd1483d37fa9c0ef2ef5bf5b9f52d129ccf0ce43beaad86b0d4ade03f33c8b1825a38ba6417576b79f50485380d1dfdad661b", + "verificationMethod": "did:pkh:eip155:59141:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.970Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "59141", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] +` + + const eip712Signature2021Single = ` + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0x1a4e1c250eb0654b1d2dbd5a6b65ffd1483d37fa9c0ef2ef5bf5b9f52d129ccf0ce43beaad86b0d4ade03f33c8b1825a38ba6417576b79f50485380d1dfdad661b", + "verificationMethod": "did:pkh:eip155:59141:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.970Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "59141", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } +` + for _, tc := range []struct { + desc string + payload []byte + expectedPayload []byte + }{ + { + desc: "eip712Signature2021 unmarshalling from a list but marshaling to a list", + payload: []byte(eip712Signature2021InList), + expectedPayload: []byte(eip712Signature2021InList), + }, + { + desc: "eip712Signature2021 unmarshalling from an element & marshaling to a list", + payload: []byte(eip712Signature2021Single), + expectedPayload: []byte(eip712Signature2021InList), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + var msg EthereumEip712Signature2021Col + require.NoError(t, json.Unmarshal(tc.payload, &msg)) + payload, err := json.Marshal(msg) + require.NoError(t, err) + assert.JSONEq(t, string(tc.expectedPayload), string(payload)) + }) + } + +} + +func TestPaymentRequestInfoDataUnmarshalMarshall(t *testing.T) { + const paymentRequestCryptoV1InList = ` + [ + { + "type":"Iden3PaymentRequestCryptoV1", + "amount":"10", + "id": "ox", + "chainId": "80002", + "address": "0xpay1", + "currency": "ETH", + "expiration": "" + } + ] +` + const paymentRequestCryptoV1Single = ` + { + "type":"Iden3PaymentRequestCryptoV1", + "amount":"10", + "id": "ox", + "chainId": "80002", + "address": "0xpay1", + "currency": "ETH", + "expiration": "" + } + +` + + const paymentRequestRailsV1InList = ` + [ + { + "type": "Iden3PaymentRailsRequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "30001", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xd881e175b548b940406e8ed97e0fe58134ac93e381cb53c35f940040b1d890540cc9f4c0f3bf42b35d3dc72d29b203af5cbae968fafc1342332f63c3581a8e691b", + "verificationMethod": "did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.964Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "80002", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + }, + { + "type": "Iden3PaymentRailsRequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "60002", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xd3606f34447c437ef7c13a1e407ad75597ee0229590de286e5775d1f1335ff93601d7a6398d69a4a3e2ddb4da5e30e61ef388d2ad6e0b50041731eaf3afe41ad1b", + "verificationMethod": "did:pkh:eip155:1101:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.967Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "1101", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + }, + { + "type": "Iden3PaymentRailsRequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "90003", + "currency": "ETHWEI", + "expirationDate": "2024-10-14T13:22:31.956Z", + "nonce": "18", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0x1a4e1c250eb0654b1d2dbd5a6b65ffd1483d37fa9c0ef2ef5bf5b9f52d129ccf0ce43beaad86b0d4ade03f33c8b1825a38ba6417576b79f50485380d1dfdad661b", + "verificationMethod": "did:pkh:eip155:59141:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-14T12:22:31.970Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "59141", + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", + "salt": "" + } + } + } + ] + } + ] + ` + for _, tc := range []struct { + desc string + payload []byte + expectedPayload []byte + }{ + { + desc: "PaymentRequestCryptoV1 unmarshalling from an element & marshaling to a single element", + payload: []byte(paymentRequestCryptoV1InList), + expectedPayload: []byte(paymentRequestCryptoV1Single), + }, + { + desc: "PaymentRequestCryptoV1 unmarshalling from a list but marshaling to a single element", + payload: []byte(paymentRequestCryptoV1InList), + expectedPayload: []byte(paymentRequestCryptoV1Single), + }, + { + desc: "Iden3PaymentRailsRequestV1 multiple elements inside a list", + payload: []byte(paymentRequestRailsV1InList), + expectedPayload: []byte(paymentRequestRailsV1InList), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + var msg PaymentRequestInfoData + require.NoError(t, json.Unmarshal(tc.payload, &msg)) + payload, err := json.Marshal(msg) + require.NoError(t, err) + assert.JSONEq(t, string(tc.expectedPayload), string(payload)) + }) + } +} + +func TestPaymentContext(t *testing.T) { + for _, tc := range []struct { + desc string + payload []byte + expectedPayload []byte + }{ + { + desc: "A string", + payload: []byte(`"context"`), + expectedPayload: []byte(`"context"`), + }, + { + desc: "A string in a list", + payload: []byte(`["context"]`), + expectedPayload: []byte(`["context"]`), + }, + { + desc: "A list of strings", + payload: []byte(`["context1", "context2"]`), + expectedPayload: []byte(`["context1", "context2"]`), + }, + { + desc: "A list of heterogeneous objects", + payload: []byte(`[{"field":"context1"}, "context in a string"]`), + expectedPayload: []byte(`[{"field":"context1"}, "context in a string"]`), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + var msg PaymentContext + require.NoError(t, json.Unmarshal(tc.payload, &msg)) + payload, err := json.Marshal(msg) + require.NoError(t, err) + assert.JSONEq(t, string(tc.expectedPayload), string(payload)) + }) + } +} From 2e39460325078ed8fe4ba537139fe31482b21353 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Mon, 28 Oct 2024 09:03:35 +0100 Subject: [PATCH 02/11] fix: linter --- protocol/payment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index c069ed3..8d84771 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -159,7 +159,7 @@ type Iden3PaymentRequestCryptoV1 struct { Address string `json:"address"` Amount string `json:"amount"` Currency string `json:"currency"` - Expiration string `json:"expiration,omitempty"` + Expiration string `json:"expirationDate,omitempty"` } // Iden3PaymentRailsRequestV1 represents the Iden3PaymentRailsRequestV1 payment request data. @@ -213,7 +213,7 @@ type Eip712Data struct { type Eip712Domain struct { Name string `json:"name"` Version string `json:"version"` - ChainId string `json:"chainId"` + ChainID string `json:"chainId"` VerifyingContract string `json:"verifyingContract"` Salt string `json:"salt"` } From 037522287a3eec900004bece9fcdc8f99b69c9c9 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Mon, 28 Oct 2024 09:21:55 +0100 Subject: [PATCH 03/11] fix: revert unwanted change --- protocol/payment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/payment.go b/protocol/payment.go index 8d84771..d7d9d59 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -159,7 +159,7 @@ type Iden3PaymentRequestCryptoV1 struct { Address string `json:"address"` Amount string `json:"amount"` Currency string `json:"currency"` - Expiration string `json:"expirationDate,omitempty"` + Expiration string `json:"expiration,omitempty"` } // Iden3PaymentRailsRequestV1 represents the Iden3PaymentRailsRequestV1 payment request data. From 87594df55df65ad369009622c312fe172d6be36e Mon Sep 17 00:00:00 2001 From: x1m3 Date: Tue, 29 Oct 2024 10:29:45 +0100 Subject: [PATCH 04/11] fix: Change Domain name in type for PaymentRequest and Payment messages --- protocol/payment.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index d7d9d59..7f9c622 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/pkg/errors" "github.com/iden3/iden3comm/v2" @@ -11,10 +12,10 @@ import ( const ( // PaymentRequestMessageType is a Iden3PaymentMessage payment type - PaymentRequestMessageType iden3comm.ProtocolMessage = iden3comm.DidCommProtocol + "credentials/0.1/payment-request" + PaymentRequestMessageType iden3comm.ProtocolMessage = iden3comm.Iden3Protocol + "credentials/0.1/payment-request" // PaymentMessageType is a Iden3PaymentMessage payment type - PaymentMessageType iden3comm.ProtocolMessage = iden3comm.DidCommProtocol + "credentials/0.1/payment" + PaymentMessageType iden3comm.ProtocolMessage = iden3comm.Iden3Protocol + "credentials/0.1/payment" // Iden3PaymentRequestCryptoV1Type is a Iden3PaymentRequestCryptoV1 payment type Iden3PaymentRequestCryptoV1Type = "Iden3PaymentRequestCryptoV1" @@ -194,12 +195,12 @@ func (p *EthereumEip712Signature2021Col) UnmarshalJSON(data []byte) error { // EthereumEip712Signature2021 represents the Ethereum EIP712 signature. type EthereumEip712Signature2021 struct { - Type string `json:"type"` - ProofPurpose string `json:"proofPurpose"` - ProofValue string `json:"proofValue"` - VerificationMethod string `json:"verificationMethod"` - Created string `json:"created"` - Eip712 Eip712Data `json:"eip712"` + Type verifiable.ProofType `json:"type"` + ProofPurpose string `json:"proofPurpose"` + ProofValue string `json:"proofValue"` + VerificationMethod string `json:"verificationMethod"` + Created string `json:"created"` + Eip712 Eip712Data `json:"eip712"` } // Eip712Data represents the EIP712 data. From 6588a90ddfc2a242210044d2d190d9f8d481b91c Mon Sep 17 00:00:00 2001 From: x1m3 Date: Wed, 30 Oct 2024 12:58:12 +0100 Subject: [PATCH 05/11] chore: Refactor PaymentContext unmarshal --- protocol/payment.go | 43 ++++++++++++++++++++++++++-------------- protocol/payment_test.go | 5 +++++ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index 7f9c622..6b54bfa 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -120,6 +120,8 @@ func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { var rails Iden3PaymentRailsRequestV1 var railsCol []Iden3PaymentRailsRequestV1 + p.crypto, p.rails = nil, nil + if err := json.Unmarshal(data, &crypto); err == nil { if crypto.Type == Iden3PaymentRequestCryptoV1Type { p.crypto = append(p.crypto, crypto) @@ -361,23 +363,34 @@ func (p PaymentContext) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshal the PaymentContext from JSON. func (p *PaymentContext) UnmarshalJSON(data []byte) error { - var str string - var strCol []string - var itemCol []interface{} - - if err := json.Unmarshal(data, &str); err == nil { - p.str = &str - return nil + var o any + if err := json.Unmarshal(data, &o); err != nil { + return err } - if err := json.Unmarshal(data, &strCol); err == nil { - p.strCol = strCol - return nil - } - if err := json.Unmarshal(data, &itemCol); err == nil { - p.itemCol = itemCol - return nil + + switch v := o.(type) { + case string: + p.str = &v + p.strCol = nil + p.itemCol = nil + case []any: + p.str = nil + p.itemCol = nil + p.strCol = make([]string, len(v)) + for i := range v { + s, ok := v[i].(string) + if !ok { + p.strCol = nil + p.itemCol = v + break + } + p.strCol[i] = s + } + default: + return errors.Errorf("failed to unmarshal PaymentContext: %s", string(data)) } - return errors.Errorf("failed to unmarshal PaymentContext: %s", string(data)) + + return nil } // Data returns the data in the union. diff --git a/protocol/payment_test.go b/protocol/payment_test.go index 45e860b..53f47e5 100644 --- a/protocol/payment_test.go +++ b/protocol/payment_test.go @@ -453,6 +453,11 @@ func TestPaymentContext(t *testing.T) { payload: []byte(`[{"field":"context1"}, "context in a string"]`), expectedPayload: []byte(`[{"field":"context1"}, "context in a string"]`), }, + { + desc: "A list of heterogeneous objects, first is a string", + payload: []byte(`["context in a string", {"field":"context1"}]`), + expectedPayload: []byte(`["context in a string", {"field":"context1"}]`), + }, } { t.Run(tc.desc, func(t *testing.T) { var msg PaymentContext From 7e98a748cf17d053f9c7ff82cece6339603a2c00 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Thu, 31 Oct 2024 18:52:09 +0100 Subject: [PATCH 06/11] feat: Add ERC20 payment data to PaymentRequest and Payment messages chore: Refactor marshallers with clearer and more consistent performer --- protocol/payment.go | 221 ++++++++++++++++++++++++++------------- protocol/payment_test.go | 192 +++++++++++++++++++++++++++++++--- 2 files changed, 324 insertions(+), 89 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index 6b54bfa..4341e07 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -23,11 +23,17 @@ const ( // Iden3PaymentRailsRequestV1Type is a Iden3PaymentRailsRequestV1 payment type Iden3PaymentRailsRequestV1Type = "Iden3PaymentRailsRequestV1" + // Iden3PaymentRailsERC20RequestV1Type is a Iden3PaymentRequestCryptoV1 payment type + Iden3PaymentRailsERC20RequestV1Type = "Iden3PaymentRailsERC20RequestV1" + // Iden3PaymentCryptoV1Type is a Iden3PaymentCryptoV1 payment type Iden3PaymentCryptoV1Type = "Iden3PaymentCryptoV1" // Iden3PaymentRailsV1Type is a Iden3PaymentRailsV1 payment type Iden3PaymentRailsV1Type = "Iden3PaymentRailsV1" + + // Iden3PaymentRailsERC20V1Type is a Iden3PaymentRailsERC20V1 payment type + Iden3PaymentRailsERC20V1Type = "Iden3PaymentRailsERC20V1" ) // PaymentRequestMessage represents Iden3message for payment request. @@ -60,97 +66,137 @@ type PaymentRequestInfo struct { // PaymentRequestInfoData is a union type for field Data in PaymentRequestInfo. // Only one of the fields can be set at a time. type PaymentRequestInfoData struct { - crypto []Iden3PaymentRequestCryptoV1 - rails []Iden3PaymentRailsRequestV1 + dataType string + crypto []Iden3PaymentRequestCryptoV1 + rails []Iden3PaymentRailsRequestV1 + railsERC []Iden3PaymentRailsERC20RequestV1 } // NewPaymentRequestInfoDataCrypto creates a new PaymentRequestInfoData with Iden3PaymentRequestCryptoV1 data. func NewPaymentRequestInfoDataCrypto(data Iden3PaymentRequestCryptoV1) PaymentRequestInfoData { return PaymentRequestInfoData{ - rails: nil, - crypto: []Iden3PaymentRequestCryptoV1{data}, + dataType: Iden3PaymentRequestCryptoV1Type, + crypto: []Iden3PaymentRequestCryptoV1{data}, } } // NewPaymentRequestInfoDataRails creates a new PaymentRequestInfoData with Iden3PaymentRailsRequestV1 data. func NewPaymentRequestInfoDataRails(data Iden3PaymentRailsRequestV1) PaymentRequestInfoData { return PaymentRequestInfoData{ - rails: []Iden3PaymentRailsRequestV1{data}, - crypto: nil, + dataType: Iden3PaymentRailsRequestV1Type, + rails: []Iden3PaymentRailsRequestV1{data}, + } +} + +func NewPaymentRequestInfoDataRailsERC20(data Iden3PaymentRailsERC20RequestV1) PaymentRequestInfoData { + return PaymentRequestInfoData{ + dataType: Iden3PaymentRailsERC20RequestV1Type, + railsERC: []Iden3PaymentRailsERC20RequestV1{data}, } } // Type returns the type of the data in the union. You can use Data() to get the data. func (p *PaymentRequestInfoData) Type() string { - if len(p.crypto) != 0 { - return Iden3PaymentRequestCryptoV1Type - } - if len(p.rails) != 0 { - return Iden3PaymentRailsRequestV1Type - } - return "" + return p.dataType } // Data returns the data in the union. You can use Type() to determine the type of the data. func (p *PaymentRequestInfoData) Data() interface{} { - if len(p.crypto) != 0 { + switch p.dataType { + case Iden3PaymentRequestCryptoV1Type: return p.crypto - } - if len(p.rails) != 0 { + case Iden3PaymentRailsRequestV1Type: return p.rails + case Iden3PaymentRailsERC20RequestV1Type: + return p.railsERC } return nil } // MarshalJSON marshals the PaymentRequestInfoData into JSON. func (p PaymentRequestInfoData) MarshalJSON() ([]byte, error) { - if len(p.crypto) != 0 { + switch p.dataType { + case Iden3PaymentRequestCryptoV1Type: return json.Marshal(p.crypto[0]) - } - if len(p.rails) != 0 { + case Iden3PaymentRailsRequestV1Type: return json.Marshal(p.rails) + case Iden3PaymentRailsERC20RequestV1Type: + return json.Marshal(p.railsERC) } return nil, errors.New("failed to marshal not initialized PaymentRequestInfoData") } // UnmarshalJSON unmarshal the PaymentRequestInfoData from JSON. func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { - var crypto Iden3PaymentRequestCryptoV1 - var cryptoCol []Iden3PaymentRequestCryptoV1 - var rails Iden3PaymentRailsRequestV1 - var railsCol []Iden3PaymentRailsRequestV1 + var item struct { + Type string `json:"type"` + } + var collection []struct { + Type string `json:"type"` + } - p.crypto, p.rails = nil, nil + p.crypto, p.rails, p.railsERC = nil, nil, nil - if err := json.Unmarshal(data, &crypto); err == nil { - if crypto.Type == Iden3PaymentRequestCryptoV1Type { - p.crypto = append(p.crypto, crypto) + if err := json.Unmarshal(data, &item); err == nil { + p.dataType = item.Type + return p.unmarshalFromItem(item.Type, data) + } + + if err := json.Unmarshal(data, &collection); err == nil { + if len(collection) == 0 { return nil } + + p.dataType = collection[0].Type + return p.unmarshalFromCollection(p.dataType, data) } - if err := json.Unmarshal(data, &cryptoCol); err == nil { - if len(cryptoCol) != 0 { - if cryptoCol[0].Type == Iden3PaymentRequestCryptoV1Type { - p.crypto = append(p.crypto, cryptoCol...) - return nil - } + return errors.New("failed to unmarshal PaymentRequestInfoData. not a single item nor a collection") +} + +func (p *PaymentRequestInfoData) unmarshalFromItem(typ string, data []byte) error { + switch typ { + case Iden3PaymentRequestCryptoV1Type: + var o Iden3PaymentRequestCryptoV1 + if err := json.Unmarshal(data, &o); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } - } - if err := json.Unmarshal(data, &rails); err == nil { - if rails.Type == Iden3PaymentRailsRequestV1Type { - p.rails = append(p.rails, rails) - return nil + p.crypto = []Iden3PaymentRequestCryptoV1{o} + case Iden3PaymentRailsRequestV1Type: + var o Iden3PaymentRailsRequestV1 + if err := json.Unmarshal(data, &o); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) + } + p.rails = []Iden3PaymentRailsRequestV1{o} + case Iden3PaymentRailsERC20RequestV1Type: + var o Iden3PaymentRailsERC20RequestV1 + if err := json.Unmarshal(data, &o); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } + p.railsERC = []Iden3PaymentRailsERC20RequestV1{o} + default: + return errors.Errorf("unmarshalling PaymentRequestInfoData. unknown type: %s", typ) } - if err := json.Unmarshal(data, &railsCol); err == nil { - if len(railsCol) != 0 { - if railsCol[0].Type == Iden3PaymentRailsRequestV1Type { - p.rails = append(p.rails, railsCol...) - return nil - } + return nil +} + +func (p *PaymentRequestInfoData) unmarshalFromCollection(typ string, data []byte) error { + switch typ { + case Iden3PaymentRequestCryptoV1Type: + if err := json.Unmarshal(data, &p.crypto); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) + } + case Iden3PaymentRailsRequestV1Type: + if err := json.Unmarshal(data, &p.rails); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } + case Iden3PaymentRailsERC20RequestV1Type: + if err := json.Unmarshal(data, &p.railsERC); err != nil { + return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) + } + default: + return errors.Errorf("unmarshalling PaymentRequestInfoData. unknown type: %s", typ) } - return errors.Errorf("failed to unmarshal PaymentRequestInfoData: %s", string(data)) + return nil } // Iden3PaymentRequestCryptoV1 represents the Iden3PaymentRequestCryptoV1 payment request data. @@ -178,6 +224,23 @@ type Iden3PaymentRailsRequestV1 struct { Currency string `json:"currency"` } +// Iden3PaymentRailsERC20RequestV1 represents the Iden3PaymentRailsERC20RequestV1 payment request data. +type Iden3PaymentRailsERC20RequestV1 struct { + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof EthereumEip712Signature2021Col `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` + TokenAddress string `json:"tokenAddress"` + Features []PaymentFeatures `json:"features,omitempty"` +} + +type PaymentFeatures string + // EthereumEip712Signature2021Col is a list of EthereumEip712Signature2021. type EthereumEip712Signature2021Col []EthereumEip712Signature2021 @@ -218,7 +281,6 @@ type Eip712Domain struct { Version string `json:"version"` ChainID string `json:"chainId"` VerifyingContract string `json:"verifyingContract"` - Salt string `json:"salt"` } // PaymentRequestInfoCredentials represents the payment request credentials. @@ -248,58 +310,60 @@ type PaymentMessageBody struct { // Payment is a union type for field Payments in PaymentMessageBody. // Only one of the fields can be set at a time. type Payment struct { - crypto *Iden3PaymentCryptoV1 - rails *Iden3PaymentRailsV1 + dataType string + crypto *Iden3PaymentCryptoV1 + rails *Iden3PaymentRailsV1 + railsERC *Iden3PaymentRailsERC20V1 } // Type returns the type of the data in the union. You can use Data() to get the data. func (p *Payment) Type() string { - if p.crypto != nil { - return Iden3PaymentCryptoV1Type - } - if p.rails != nil { - return Iden3PaymentRailsV1Type - } - return "" + return p.dataType } // Data returns the data in the union. You can use Type() to determine the type of the data. func (p *Payment) Data() interface{} { - if p.crypto != nil { + switch p.dataType { + case Iden3PaymentCryptoV1Type: return p.crypto - } - if p.rails != nil { + case Iden3PaymentRailsV1Type: return p.rails + case Iden3PaymentRailsERC20V1Type: + return p.railsERC } return nil } // UnmarshalJSON unmarshal the Payment from JSON. func (p *Payment) UnmarshalJSON(bytes []byte) error { - var crypto Iden3PaymentCryptoV1 - var rails Iden3PaymentRailsV1 - if json.Unmarshal(bytes, &crypto) == nil { - if crypto.Type == Iden3PaymentCryptoV1Type { - p.crypto = &crypto - return nil - } + var item struct { + Type string `json:"type"` } - if json.Unmarshal(bytes, &rails) == nil { - if rails.Type == Iden3PaymentRailsV1Type { - p.rails = &rails - return nil - } + if err := json.Unmarshal(bytes, &item); err != nil { + return fmt.Errorf("failed to unmarshal Payment: %w", err) + } + + p.dataType = item.Type + switch item.Type { + case Iden3PaymentCryptoV1Type: + return json.Unmarshal(bytes, &p.crypto) + case Iden3PaymentRailsV1Type: + return json.Unmarshal(bytes, &p.rails) + case Iden3PaymentRailsERC20V1Type: + return json.Unmarshal(bytes, &p.railsERC) } return errors.Errorf("failed to unmarshal PaymentRequestInfoData: %s", string(bytes)) } // MarshalJSON marshals the Payment into JSON. func (p Payment) MarshalJSON() ([]byte, error) { - if p.crypto != nil { + switch p.dataType { + case Iden3PaymentCryptoV1Type: return json.Marshal(p.crypto) - } - if p.rails != nil { + case Iden3PaymentRailsV1Type: return json.Marshal(p.rails) + case Iden3PaymentRailsERC20V1Type: + return json.Marshal(p.railsERC) } return nil, errors.New("failed to marshal not initialized Payment") } @@ -325,6 +389,17 @@ type Iden3PaymentRailsV1 struct { } `json:"paymentData"` } +type Iden3PaymentRailsERC20V1 struct { + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context,omitempty"` + PaymentData struct { + TxID string `json:"txId"` + ChainID string `json:"chainId"` + TokenAddress string `json:"tokenAddress"` + } `json:"paymentData"` +} + // PaymentContext represents the payment context. type PaymentContext struct { str *string diff --git a/protocol/payment_test.go b/protocol/payment_test.go index 53f47e5..6db885e 100644 --- a/protocol/payment_test.go +++ b/protocol/payment_test.go @@ -79,8 +79,7 @@ func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "80002", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -112,8 +111,7 @@ func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "1101", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -145,8 +143,7 @@ func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "59141", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -164,6 +161,67 @@ func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { ] } } +` + + const paymentRequestTypeIden3PaymentRailsERC20RequestV1 = ` +{ + "id": "54782ed3-8d83-427b-856d-eac57a9aa94a", + "thid": "54782ed3-8d83-427b-856d-eac57a9aa94a", + "from": "did:iden3:polygon:amoy:xCRp75DgAdS63W65fmXHz6p9DwdonuRU9e46DifhX", + "to": "did:iden3:polygon:amoy:x7Z95VkUuyo6mqraJw2VGwCfqTzdqhM1RVjRHzcpK", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment-request", + "body": { + "agent": "agent.example.com", + "payments": [ + { + "data": [ + { + "type": "Iden3PaymentRailsERC20RequestV1", + "@context": [ + "https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20RequestV1", + "https://w3id.org/security/suites/eip712sig-2021/v1" + ], + "tokenAddress": "0x2FE40749812FAC39a0F380649eF59E01bccf3a1A", + "features": ["EIP-2612"], + "recipient": "0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a", + "amount": "40", + "currency": "ERC20Token", + "expirationDate": "2024-10-28T16:02:36.816Z", + "nonce": "3008", + "metadata": "0x", + "proof": [ + { + "type": "EthereumEip712Signature2021", + "proofPurpose": "assertionMethod", + "proofValue": "0xc3d9d6fa9aa7af03863943f7568ce61303e84221e3e29277309fd42581742024402802816cca5542620c19895331f4bdc1ea6fed0d0c6a1cf8656556d3acfde61b", + "verificationMethod": "did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId", + "created": "2024-10-28T15:02:36.946Z", + "eip712": { + "types": "https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json", + "primaryType": "Iden3PaymentRailsRequestV1", + "domain": { + "name": "MCPayment", + "version": "1.0.0", + "chainId": "80002", + "verifyingContract": "0x6f742EBA99C3043663f995a7f566e9F012C07925" + } + } + } + ] + } + ], + "credentials": [ + { + "type": "AML", + "context": "http://test.com" + } + ], + "description": "Iden3PaymentRailsRequestV1 payment-request integration test" + } + ] + } +} ` for _, tc := range []struct { @@ -181,6 +239,11 @@ func TestPaymentRequestMessagePaymentTypeUnmarshall(t *testing.T) { payload: []byte(paymentRequestTypeIden3PaymentRailsRequestV1), expectedPayload: []byte(paymentRequestTypeIden3PaymentRailsRequestV1), }, + { + desc: "PaymentRequestMessage of type Iden3PaymentRailsERC20RequestV1", + payload: []byte(paymentRequestTypeIden3PaymentRailsERC20RequestV1), + expectedPayload: []byte(paymentRequestTypeIden3PaymentRailsERC20RequestV1), + }, } { t.Run(tc.desc, func(t *testing.T) { @@ -211,8 +274,7 @@ func TestEthereumEip712Signature2021Col(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "59141", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -233,8 +295,7 @@ func TestEthereumEip712Signature2021Col(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "59141", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -321,8 +382,7 @@ func TestPaymentRequestInfoDataUnmarshalMarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "80002", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -354,8 +414,7 @@ func TestPaymentRequestInfoDataUnmarshalMarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "1101", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -387,8 +446,7 @@ func TestPaymentRequestInfoDataUnmarshalMarshall(t *testing.T) { "name": "MCPayment", "version": "1.0.0", "chainId": "59141", - "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A", - "salt": "" + "verifyingContract": "0x74Ac6aa5dDC433A654d84aFCE5D95c32df16cC0A" } } } @@ -468,3 +526,105 @@ func TestPaymentContext(t *testing.T) { }) } } + +func TestPaymentMarshalUnmarshal(t *testing.T) { + const paymentCryptoV1 = ` +{ + "id": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "thid": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment", + "body": { + "payments": [ + { + "id":"123", + "type":"Iden3PaymentCryptoV1", + "@context": "https://schema.iden3.io/core/jsonld/payment.jsonld", + "paymentData": { + "txId": "0x123" + } + } + ] + }, + "to": "did:polygonid:polygon:mumbai:2qJUZDSCFtpR8QvHyBC4eFm6ab9sJo5rqPbcaeyGC4", + "from": "did:iden3:polygon:mumbai:x3HstHLj2rTp6HHXk2WczYP7w3rpCsRbwCMeaQ2H2" +} +` + const paymentNative = ` +{ + "id": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "thid": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment", + "body": { + "payments": [ + { + "nonce":"123", + "type":"Iden3PaymentRailsV1", + "@context": "https://schema.iden3.io/core/jsonld/payment.jsonld", + "paymentData": { + "txId": "0x123", + "chainId": "123" + } + } + ] + }, + "to": "did:polygonid:polygon:mumbai:2qJUZDSCFtpR8QvHyBC4eFm6ab9sJo5rqPbcaeyGC4", + "from": "did:iden3:polygon:mumbai:x3HstHLj2rTp6HHXk2WczYP7w3rpCsRbwCMeaQ2H2" +} +` + const paymentERC20 = ` +{ + "id": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "thid": "36f9e851-d713-4b50-8f8d-8a9382f138ca", + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/0.1/payment", + "body": { + "payments": [ + { + "nonce":"123", + "type":"Iden3PaymentRailsERC20V1", + "@context": "https://schema.iden3.io/core/jsonld/payment.jsonld", + "paymentData": { + "txId": "0x123", + "chainId": "123", + "tokenAddress": "0x123" + } + } + ] + }, + "to": "did:polygonid:polygon:mumbai:2qJUZDSCFtpR8QvHyBC4eFm6ab9sJo5rqPbcaeyGC4", + "from": "did:iden3:polygon:mumbai:x3HstHLj2rTp6HHXk2WczYP7w3rpCsRbwCMeaQ2H2" +} +` + + for _, tc := range []struct { + desc string + payload []byte + expectedPayload []byte + }{ + { + desc: "Crypto payment", + payload: []byte(paymentCryptoV1), + expectedPayload: []byte(paymentCryptoV1), + }, + { + desc: "Native payment", + payload: []byte(paymentNative), + expectedPayload: []byte(paymentNative), + }, + { + desc: "ERC20 payment", + payload: []byte(paymentERC20), + expectedPayload: []byte(paymentERC20), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + var msg PaymentMessage + require.NoError(t, json.Unmarshal(tc.payload, &msg)) + payload, err := json.Marshal(msg) + require.NoError(t, err) + assert.JSONEq(t, string(tc.expectedPayload), string(payload)) + }) + } +} From 369144dfa11cb2314b18892a285743d3245feab0 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Thu, 31 Oct 2024 18:57:59 +0100 Subject: [PATCH 07/11] feat: Constructors for PaymentMessage --- protocol/payment.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/protocol/payment.go b/protocol/payment.go index 4341e07..29d48b9 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -88,6 +88,7 @@ func NewPaymentRequestInfoDataRails(data Iden3PaymentRailsRequestV1) PaymentRequ } } +// NewPaymentRequestInfoDataRailsERC20 creates a new PaymentRequestInfoData with Iden3PaymentRailsERC20RequestV1 data. func NewPaymentRequestInfoDataRailsERC20(data Iden3PaymentRailsERC20RequestV1) PaymentRequestInfoData { return PaymentRequestInfoData{ dataType: Iden3PaymentRailsERC20RequestV1Type, @@ -239,6 +240,7 @@ type Iden3PaymentRailsERC20RequestV1 struct { Features []PaymentFeatures `json:"features,omitempty"` } +// PaymentFeatures represents type Features used in ERC20 payment request. type PaymentFeatures string // EthereumEip712Signature2021Col is a list of EthereumEip712Signature2021. @@ -316,6 +318,30 @@ type Payment struct { railsERC *Iden3PaymentRailsERC20V1 } +// NewPaymentCrypto creates a new Payment with Iden3PaymentCryptoV1 data. +func NewPaymentCrypto(data Iden3PaymentCryptoV1) Payment { + return Payment{ + dataType: Iden3PaymentCryptoV1Type, + crypto: &data, + } +} + +// NewPaymentRails creates a new Payment with Iden3PaymentRailsV1 data. +func NewPaymentRails(data Iden3PaymentRailsV1) Payment { + return Payment{ + dataType: Iden3PaymentRailsV1Type, + rails: &data, + } +} + +// NEwPaymentRailsERC20 creates a new Payment with Iden3PaymentRailsERC20V1 data. +func NEwPaymentRailsERC20(data Iden3PaymentRailsERC20V1) Payment { + return Payment{ + dataType: Iden3PaymentRailsERC20V1Type, + railsERC: &data, + } +} + // Type returns the type of the data in the union. You can use Data() to get the data. func (p *Payment) Type() string { return p.dataType @@ -389,6 +415,7 @@ type Iden3PaymentRailsV1 struct { } `json:"paymentData"` } +// Iden3PaymentRailsERC20V1 represents the Iden3PaymentRailsERC20V1 payment data. type Iden3PaymentRailsERC20V1 struct { Nonce string `json:"nonce"` Type string `json:"type"` From 3e12354012a56fea1760af5b4f7492d71c830149 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Mon, 4 Nov 2024 16:57:05 +0100 Subject: [PATCH 08/11] chore: Encapsulate proof type so it will be easier to extend to other proof types. --- protocol/payment.go | 62 ++++++++++++++++++++++++---------------- protocol/payment_test.go | 2 +- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index 29d48b9..aa925af 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -34,6 +34,9 @@ const ( // Iden3PaymentRailsERC20V1Type is a Iden3PaymentRailsERC20V1 payment type Iden3PaymentRailsERC20V1Type = "Iden3PaymentRailsERC20V1" + + // Eip712SignatureProofType is a EthereumEip712Signature2021 proof type + Eip712SignatureProofType = "EthereumEip712Signature2021" ) // PaymentRequestMessage represents Iden3message for payment request. @@ -214,40 +217,43 @@ type Iden3PaymentRequestCryptoV1 struct { // Iden3PaymentRailsRequestV1 represents the Iden3PaymentRailsRequestV1 payment request data. type Iden3PaymentRailsRequestV1 struct { - Nonce string `json:"nonce"` - Type string `json:"type"` - Context PaymentContext `json:"@context"` - Recipient string `json:"recipient"` - Amount string `json:"amount"` // Not negative number - ExpirationDate string `json:"expirationDate"` - Proof EthereumEip712Signature2021Col `json:"proof"` - Metadata string `json:"metadata"` - Currency string `json:"currency"` + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof PaymentProof `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` } // Iden3PaymentRailsERC20RequestV1 represents the Iden3PaymentRailsERC20RequestV1 payment request data. type Iden3PaymentRailsERC20RequestV1 struct { - Nonce string `json:"nonce"` - Type string `json:"type"` - Context PaymentContext `json:"@context"` - Recipient string `json:"recipient"` - Amount string `json:"amount"` // Not negative number - ExpirationDate string `json:"expirationDate"` - Proof EthereumEip712Signature2021Col `json:"proof"` - Metadata string `json:"metadata"` - Currency string `json:"currency"` - TokenAddress string `json:"tokenAddress"` - Features []PaymentFeatures `json:"features,omitempty"` + Nonce string `json:"nonce"` + Type string `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof PaymentProof `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` + TokenAddress string `json:"tokenAddress"` + Features []PaymentFeatures `json:"features,omitempty"` } // PaymentFeatures represents type Features used in ERC20 payment request. type PaymentFeatures string -// EthereumEip712Signature2021Col is a list of EthereumEip712Signature2021. -type EthereumEip712Signature2021Col []EthereumEip712Signature2021 +type PaymentProof struct { + dataType string + eip712Signature []EthereumEip712Signature2021 +} // UnmarshalJSON unmarshal the PaymentRequestInfoData from JSON. -func (p *EthereumEip712Signature2021Col) UnmarshalJSON(data []byte) error { +func (p *PaymentProof) UnmarshalJSON(data []byte) error { + p.dataType = Eip712SignatureProofType var col []EthereumEip712Signature2021 if err := json.Unmarshal(data, &col); err != nil { var single EthereumEip712Signature2021 @@ -256,10 +262,18 @@ func (p *EthereumEip712Signature2021Col) UnmarshalJSON(data []byte) error { } col = append(col, single) } - *p = col + p.eip712Signature = col return nil } +// MarshalJSON marshals the PaymentProof into JSON. +func (p PaymentProof) MarshalJSON() ([]byte, error) { + if p.dataType == Eip712SignatureProofType { + return json.Marshal(p.eip712Signature) + } + return nil, errors.New("failed to marshal not initialized PaymentProof") +} + // EthereumEip712Signature2021 represents the Ethereum EIP712 signature. type EthereumEip712Signature2021 struct { Type verifiable.ProofType `json:"type"` diff --git a/protocol/payment_test.go b/protocol/payment_test.go index 6db885e..b63fe83 100644 --- a/protocol/payment_test.go +++ b/protocol/payment_test.go @@ -317,7 +317,7 @@ func TestEthereumEip712Signature2021Col(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - var msg EthereumEip712Signature2021Col + var msg PaymentProof require.NoError(t, json.Unmarshal(tc.payload, &msg)) payload, err := json.Marshal(msg) require.NoError(t, err) From 9661235f7459b4b9b563d5daa3d2c551bc78969d Mon Sep 17 00:00:00 2001 From: x1m3 Date: Mon, 4 Nov 2024 16:59:08 +0100 Subject: [PATCH 09/11] fix: linter --- protocol/payment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/payment.go b/protocol/payment.go index aa925af..b645809 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -246,6 +246,7 @@ type Iden3PaymentRailsERC20RequestV1 struct { // PaymentFeatures represents type Features used in ERC20 payment request. type PaymentFeatures string +// PaymentProof represents a payment proof. type PaymentProof struct { dataType string eip712Signature []EthereumEip712Signature2021 From 0d36ea1672791ae676931c8bdbf1ea96c7105d0d Mon Sep 17 00:00:00 2001 From: x1m3 Date: Wed, 6 Nov 2024 09:43:07 +0100 Subject: [PATCH 10/11] chore: Rename PaymentRequestInfoData.railsERC as suggested --- protocol/payment.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index b645809..ef51188 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -69,10 +69,10 @@ type PaymentRequestInfo struct { // PaymentRequestInfoData is a union type for field Data in PaymentRequestInfo. // Only one of the fields can be set at a time. type PaymentRequestInfoData struct { - dataType string - crypto []Iden3PaymentRequestCryptoV1 - rails []Iden3PaymentRailsRequestV1 - railsERC []Iden3PaymentRailsERC20RequestV1 + dataType string + crypto []Iden3PaymentRequestCryptoV1 + rails []Iden3PaymentRailsRequestV1 + railsERC20 []Iden3PaymentRailsERC20RequestV1 } // NewPaymentRequestInfoDataCrypto creates a new PaymentRequestInfoData with Iden3PaymentRequestCryptoV1 data. @@ -94,8 +94,8 @@ func NewPaymentRequestInfoDataRails(data Iden3PaymentRailsRequestV1) PaymentRequ // NewPaymentRequestInfoDataRailsERC20 creates a new PaymentRequestInfoData with Iden3PaymentRailsERC20RequestV1 data. func NewPaymentRequestInfoDataRailsERC20(data Iden3PaymentRailsERC20RequestV1) PaymentRequestInfoData { return PaymentRequestInfoData{ - dataType: Iden3PaymentRailsERC20RequestV1Type, - railsERC: []Iden3PaymentRailsERC20RequestV1{data}, + dataType: Iden3PaymentRailsERC20RequestV1Type, + railsERC20: []Iden3PaymentRailsERC20RequestV1{data}, } } @@ -112,7 +112,7 @@ func (p *PaymentRequestInfoData) Data() interface{} { case Iden3PaymentRailsRequestV1Type: return p.rails case Iden3PaymentRailsERC20RequestV1Type: - return p.railsERC + return p.railsERC20 } return nil } @@ -125,7 +125,7 @@ func (p PaymentRequestInfoData) MarshalJSON() ([]byte, error) { case Iden3PaymentRailsRequestV1Type: return json.Marshal(p.rails) case Iden3PaymentRailsERC20RequestV1Type: - return json.Marshal(p.railsERC) + return json.Marshal(p.railsERC20) } return nil, errors.New("failed to marshal not initialized PaymentRequestInfoData") } @@ -139,7 +139,7 @@ func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { Type string `json:"type"` } - p.crypto, p.rails, p.railsERC = nil, nil, nil + p.crypto, p.rails, p.railsERC20 = nil, nil, nil if err := json.Unmarshal(data, &item); err == nil { p.dataType = item.Type @@ -176,7 +176,7 @@ func (p *PaymentRequestInfoData) unmarshalFromItem(typ string, data []byte) erro if err := json.Unmarshal(data, &o); err != nil { return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } - p.railsERC = []Iden3PaymentRailsERC20RequestV1{o} + p.railsERC20 = []Iden3PaymentRailsERC20RequestV1{o} default: return errors.Errorf("unmarshalling PaymentRequestInfoData. unknown type: %s", typ) } @@ -194,7 +194,7 @@ func (p *PaymentRequestInfoData) unmarshalFromCollection(typ string, data []byte return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } case Iden3PaymentRailsERC20RequestV1Type: - if err := json.Unmarshal(data, &p.railsERC); err != nil { + if err := json.Unmarshal(data, &p.railsERC20); err != nil { return fmt.Errorf("unmarshalling PaymentRequestInfoData: %w", err) } default: From 4e320f6b1ebd870b0a2dde3574de9c034bbefd61 Mon Sep 17 00:00:00 2001 From: x1m3 Date: Wed, 6 Nov 2024 10:32:04 +0100 Subject: [PATCH 11/11] feat: Custom type for PaymentType and PaymentRequestType --- protocol/payment.go | 105 ++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/protocol/payment.go b/protocol/payment.go index ef51188..427ed1c 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -18,27 +18,36 @@ const ( PaymentMessageType iden3comm.ProtocolMessage = iden3comm.Iden3Protocol + "credentials/0.1/payment" // Iden3PaymentRequestCryptoV1Type is a Iden3PaymentRequestCryptoV1 payment type - Iden3PaymentRequestCryptoV1Type = "Iden3PaymentRequestCryptoV1" + Iden3PaymentRequestCryptoV1Type PaymentRequestType = "Iden3PaymentRequestCryptoV1" // Iden3PaymentRailsRequestV1Type is a Iden3PaymentRailsRequestV1 payment type - Iden3PaymentRailsRequestV1Type = "Iden3PaymentRailsRequestV1" + Iden3PaymentRailsRequestV1Type PaymentRequestType = "Iden3PaymentRailsRequestV1" // Iden3PaymentRailsERC20RequestV1Type is a Iden3PaymentRequestCryptoV1 payment type - Iden3PaymentRailsERC20RequestV1Type = "Iden3PaymentRailsERC20RequestV1" + Iden3PaymentRailsERC20RequestV1Type PaymentRequestType = "Iden3PaymentRailsERC20RequestV1" // Iden3PaymentCryptoV1Type is a Iden3PaymentCryptoV1 payment type - Iden3PaymentCryptoV1Type = "Iden3PaymentCryptoV1" + Iden3PaymentCryptoV1Type PaymentType = "Iden3PaymentCryptoV1" // Iden3PaymentRailsV1Type is a Iden3PaymentRailsV1 payment type - Iden3PaymentRailsV1Type = "Iden3PaymentRailsV1" + Iden3PaymentRailsV1Type PaymentType = "Iden3PaymentRailsV1" // Iden3PaymentRailsERC20V1Type is a Iden3PaymentRailsERC20V1 payment type - Iden3PaymentRailsERC20V1Type = "Iden3PaymentRailsERC20V1" + Iden3PaymentRailsERC20V1Type PaymentType = "Iden3PaymentRailsERC20V1" // Eip712SignatureProofType is a EthereumEip712Signature2021 proof type - Eip712SignatureProofType = "EthereumEip712Signature2021" + Eip712SignatureProofType ProofType = "EthereumEip712Signature2021" ) +// PaymentType is type for Payment +type PaymentType string + +// PaymentRequestType is type for Payment request +type PaymentRequestType string + +// ProofType is type for Proof +type ProofType string + // PaymentRequestMessage represents Iden3message for payment request. type PaymentRequestMessage struct { ID string `json:"id"` @@ -69,7 +78,7 @@ type PaymentRequestInfo struct { // PaymentRequestInfoData is a union type for field Data in PaymentRequestInfo. // Only one of the fields can be set at a time. type PaymentRequestInfoData struct { - dataType string + dataType PaymentRequestType crypto []Iden3PaymentRequestCryptoV1 rails []Iden3PaymentRailsRequestV1 railsERC20 []Iden3PaymentRailsERC20RequestV1 @@ -100,7 +109,7 @@ func NewPaymentRequestInfoDataRailsERC20(data Iden3PaymentRailsERC20RequestV1) P } // Type returns the type of the data in the union. You can use Data() to get the data. -func (p *PaymentRequestInfoData) Type() string { +func (p *PaymentRequestInfoData) Type() PaymentRequestType { return p.dataType } @@ -133,10 +142,10 @@ func (p PaymentRequestInfoData) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshal the PaymentRequestInfoData from JSON. func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { var item struct { - Type string `json:"type"` + Type PaymentRequestType `json:"type"` } var collection []struct { - Type string `json:"type"` + Type PaymentRequestType `json:"type"` } p.crypto, p.rails, p.railsERC20 = nil, nil, nil @@ -157,7 +166,7 @@ func (p *PaymentRequestInfoData) UnmarshalJSON(data []byte) error { return errors.New("failed to unmarshal PaymentRequestInfoData. not a single item nor a collection") } -func (p *PaymentRequestInfoData) unmarshalFromItem(typ string, data []byte) error { +func (p *PaymentRequestInfoData) unmarshalFromItem(typ PaymentRequestType, data []byte) error { switch typ { case Iden3PaymentRequestCryptoV1Type: var o Iden3PaymentRequestCryptoV1 @@ -183,7 +192,7 @@ func (p *PaymentRequestInfoData) unmarshalFromItem(typ string, data []byte) erro return nil } -func (p *PaymentRequestInfoData) unmarshalFromCollection(typ string, data []byte) error { +func (p *PaymentRequestInfoData) unmarshalFromCollection(typ PaymentRequestType, data []byte) error { switch typ { case Iden3PaymentRequestCryptoV1Type: if err := json.Unmarshal(data, &p.crypto); err != nil { @@ -205,42 +214,42 @@ func (p *PaymentRequestInfoData) unmarshalFromCollection(typ string, data []byte // Iden3PaymentRequestCryptoV1 represents the Iden3PaymentRequestCryptoV1 payment request data. type Iden3PaymentRequestCryptoV1 struct { - Type string `json:"type"` - ID string `json:"id"` - Context string `json:"@context,omitempty"` - ChainID string `json:"chainId"` - Address string `json:"address"` - Amount string `json:"amount"` - Currency string `json:"currency"` - Expiration string `json:"expiration,omitempty"` + Type PaymentRequestType `json:"type"` + ID string `json:"id"` + Context string `json:"@context,omitempty"` + ChainID string `json:"chainId"` + Address string `json:"address"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Expiration string `json:"expiration,omitempty"` } // Iden3PaymentRailsRequestV1 represents the Iden3PaymentRailsRequestV1 payment request data. type Iden3PaymentRailsRequestV1 struct { - Nonce string `json:"nonce"` - Type string `json:"type"` - Context PaymentContext `json:"@context"` - Recipient string `json:"recipient"` - Amount string `json:"amount"` // Not negative number - ExpirationDate string `json:"expirationDate"` - Proof PaymentProof `json:"proof"` - Metadata string `json:"metadata"` - Currency string `json:"currency"` + Nonce string `json:"nonce"` + Type PaymentRequestType `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof PaymentProof `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` } // Iden3PaymentRailsERC20RequestV1 represents the Iden3PaymentRailsERC20RequestV1 payment request data. type Iden3PaymentRailsERC20RequestV1 struct { - Nonce string `json:"nonce"` - Type string `json:"type"` - Context PaymentContext `json:"@context"` - Recipient string `json:"recipient"` - Amount string `json:"amount"` // Not negative number - ExpirationDate string `json:"expirationDate"` - Proof PaymentProof `json:"proof"` - Metadata string `json:"metadata"` - Currency string `json:"currency"` - TokenAddress string `json:"tokenAddress"` - Features []PaymentFeatures `json:"features,omitempty"` + Nonce string `json:"nonce"` + Type PaymentRequestType `json:"type"` + Context PaymentContext `json:"@context"` + Recipient string `json:"recipient"` + Amount string `json:"amount"` // Not negative number + ExpirationDate string `json:"expirationDate"` + Proof PaymentProof `json:"proof"` + Metadata string `json:"metadata"` + Currency string `json:"currency"` + TokenAddress string `json:"tokenAddress"` + Features []PaymentFeatures `json:"features,omitempty"` } // PaymentFeatures represents type Features used in ERC20 payment request. @@ -248,7 +257,7 @@ type PaymentFeatures string // PaymentProof represents a payment proof. type PaymentProof struct { - dataType string + dataType ProofType eip712Signature []EthereumEip712Signature2021 } @@ -327,7 +336,7 @@ type PaymentMessageBody struct { // Payment is a union type for field Payments in PaymentMessageBody. // Only one of the fields can be set at a time. type Payment struct { - dataType string + dataType PaymentType crypto *Iden3PaymentCryptoV1 rails *Iden3PaymentRailsV1 railsERC *Iden3PaymentRailsERC20V1 @@ -358,7 +367,7 @@ func NEwPaymentRailsERC20(data Iden3PaymentRailsERC20V1) Payment { } // Type returns the type of the data in the union. You can use Data() to get the data. -func (p *Payment) Type() string { +func (p *Payment) Type() PaymentType { return p.dataType } @@ -378,7 +387,7 @@ func (p *Payment) Data() interface{} { // UnmarshalJSON unmarshal the Payment from JSON. func (p *Payment) UnmarshalJSON(bytes []byte) error { var item struct { - Type string `json:"type"` + Type PaymentType `json:"type"` } if err := json.Unmarshal(bytes, &item); err != nil { return fmt.Errorf("failed to unmarshal Payment: %w", err) @@ -412,7 +421,7 @@ func (p Payment) MarshalJSON() ([]byte, error) { // Iden3PaymentCryptoV1 represents the Iden3PaymentCryptoV1 payment data. type Iden3PaymentCryptoV1 struct { ID string `json:"id"` - Type string `json:"type"` + Type PaymentType `json:"type"` Context PaymentContext `json:"@context,omitempty"` PaymentData struct { TxID string `json:"txId"` @@ -422,7 +431,7 @@ type Iden3PaymentCryptoV1 struct { // Iden3PaymentRailsV1 represents the Iden3PaymentRailsV1 payment data. type Iden3PaymentRailsV1 struct { Nonce string `json:"nonce"` - Type string `json:"type"` + Type PaymentType `json:"type"` Context PaymentContext `json:"@context,omitempty"` PaymentData struct { TxID string `json:"txId"` @@ -433,7 +442,7 @@ type Iden3PaymentRailsV1 struct { // Iden3PaymentRailsERC20V1 represents the Iden3PaymentRailsERC20V1 payment data. type Iden3PaymentRailsERC20V1 struct { Nonce string `json:"nonce"` - Type string `json:"type"` + Type PaymentType `json:"type"` Context PaymentContext `json:"@context,omitempty"` PaymentData struct { TxID string `json:"txId"`