├── .github ├── FUNDING.yml ├── dependabot.yml ├── hooks │ └── pre-commit └── workflows │ ├── codeql-analysis.yml │ └── go.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── amount ├── amount.go └── amount_test.go ├── config.json.dist ├── config ├── config.go ├── config_test.go ├── server.go └── stripe.go ├── controller └── rest │ └── intent │ ├── cancel │ ├── restintentcancel.go │ └── restintentcancel_integration_test.go │ ├── capture │ ├── restintentcapture.go │ └── restintentcapture_integration_test.go │ ├── confirm │ ├── restintentconfirm.go │ └── restintentconfirm_integration_test.go │ ├── create │ ├── restintentcreate.go │ └── restintentcreate_integration_test.go │ └── get │ ├── restintentget.go │ └── restintentget_integration_test.go ├── currency ├── currency.go └── currency_test.go ├── customer ├── customer.go ├── customer_test.go ├── customerstripe.go └── customerstripe_integration_test.go ├── error ├── rest.go ├── stripe.go └── stripe_test.go ├── go.mod ├── go.sum ├── main.go └── payment ├── intent ├── cancel │ ├── intentcancel.go │ └── intentcancel_integration_test.go ├── capture │ ├── intentcapture.go │ └── intentcapture_integration_test.go ├── confirm │ ├── intentconfirm.go │ └── intentconfirm_integration_test.go ├── create │ ├── intentcreate.go │ └── intentcreate_integration_test.go ├── get │ ├── intentget.go │ └── intentget_integration_test.go ├── intent.go ├── status.go ├── stripeconv.go └── stripeconv_test.go └── source ├── paymentsource.go └── paymentsource_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lelledaniele 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | day: "saturday" 13 | assignees: 14 | - "lelledaniele" 15 | 16 | - package-ecosystem: "gomod" 17 | directory: "/" 18 | schedule: 19 | interval: "weekly" 20 | day: "saturday" 21 | assignees: 22 | - "lelledaniele" 23 | -------------------------------------------------------------------------------- /.github/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go fmt ./... 4 | golint . 5 | go test ./... -tags=unit -failfast 6 | # shellcheck disable=SC2181 7 | if [ $? != 0 ] 8 | then 9 | echo "Committed code break unit tests" 10 | exit 1 11 | fi 12 | 13 | go test ./... -tags=stripe -failfast -config=ABS_PATH/config.json 14 | # shellcheck disable=SC2181 15 | if [ $? != 0 ] 16 | then 17 | echo "Committed code break Stripe tests" 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 2 * * 1' 7 | 8 | jobs: 9 | CodeQL-Build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | with: 17 | # We must fetch at least the immediate parents so that if this is 18 | # a pull request then we can checkout the head. 19 | fetch-depth: 2 20 | 21 | # If this run was triggered by a pull request event, then checkout 22 | # the head of the pull request instead of the merge commit. 23 | - run: git checkout HEAD^2 24 | if: ${{ github.event_name == 'pull_request' }} 25 | 26 | # Initializes the CodeQL tools for scanning. 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v1 29 | # Override language selection by uncommenting this and choosing your languages 30 | # with: 31 | # languages: go, javascript, csharp, python, cpp, java 32 | 33 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 34 | # If this step fails, then you should remove it and run the build manually (see below) 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v1 37 | 38 | # ℹ️ Command-line programs to run using the OS shell. 39 | # 📚 https://git.io/JvXDl 40 | 41 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 42 | # and modify them (or add more) to build your code if your project 43 | # uses a compiled language 44 | 45 | #- run: | 46 | # make bootstrap 47 | # make release 48 | 49 | - name: Perform CodeQL Analysis 50 | uses: github/codeql-action/analyze@v1 51 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.14 11 | uses: actions/setup-go@v4.0.1 12 | with: 13 | go-version: 1.14 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Swagger - Installation 20 | run: go get -u github.com/swaggo/swag/cmd/swag 21 | 22 | - name: Swagger - Init 23 | run: ~/go/bin/swag init 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Config 34 | run: | 35 | cp config.json.dist config.json 36 | sed -i 's/pk_xxx/${{ secrets.STRIPE_PK }}/g; s/sk_xxx/${{ secrets.STRIPE_SK }}/g' config.json 37 | 38 | - name: Tests - Unit 39 | run: go test ./... -failfast -tags=unit 40 | 41 | - name: Tests - Stripe 42 | run: go test ./... -failfast -tags=stripe -config=/home/runner/work/upaygo/upaygo/config.json 43 | 44 | - name: Build 45 | run: go build -v . 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /docs 4 | 5 | /upaygo.iml 6 | /config.json 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at lelle.daniele@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Daniele Rostellato 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Go Tests](https://github.com/lelledaniele/upaygo/workflows/Go/badge.svg) 2 | ![Code scanning - action](https://github.com/lelledaniele/upaygo/workflows/Code%20scanning%20-%20action/badge.svg) 3 | 4 | [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/bf7491736c431cd822f6) 5 | 6 | # uPay in Golang 7 | 8 | Payment Gateway Microservice in Golang 9 | 10 | ## PSD2 SCA 11 | 12 | **EU SCA law will be on duty after 14th September 2019** 13 | 14 | ### Updates 15 | 16 | - 13/08/2019 - For **UK cards** the [SCA implementation deadline is March 2021](https://www.fca.org.uk/news/press-releases/fca-agrees-plan-phased-implementation-strong-customer-authentication) 17 | 18 | ## Feature 19 | 20 | - SCA ready with [Stripe Payment Intents](https://stripe.com/docs/payments/payment-intents) 21 | - Off-session intents 22 | - Separation of auth and capture 23 | - New intent 24 | - Confirm intent 25 | - Capture/Delete intent 26 | - No database infrastructure needed 27 | - Stripe API keys configuration per currency 28 | 29 | ## Installation 30 | 31 | ```bash 32 | cp config.json.dist config.json 33 | vi config.json # Add your config values 34 | 35 | # API doc 36 | swag init 37 | 38 | # If you want to contribute 39 | cp .github/hooks/pre-commit .git/hooks/pre-commit 40 | # Open and change absolute config path 41 | 42 | go run main.go -config=config.json 43 | ``` 44 | 45 | ## How to use 46 | 47 | *Example of a checkout web page* 48 | 49 | 1) User to insert card information with Stripe Elements 50 | 2) Create an intent with JS SDK 51 | 3) Confirm the intent 52 | 4) Does the intent requires 3D Secure (intent status and next_action param) 53 | 1) No, Point 5) 54 | 2) Yes, Stripe Elements will open the a 3D Secure popup 55 | 5) Do you after checkout domain logic 56 | 6) Any error during your checkout process? 57 | 1) Yes, cancel the intent 58 | 2) No, capture the intent 59 | 60 | ## Tests 61 | 62 | ```bash 63 | go test ./... -failfast -tags=unit 64 | go test ./... -failfast -tags=stripe -config=ABS_PATH/config.json 65 | ``` 66 | 67 | ### APIs 68 | 69 | - Swagger */swagger/index.html* 70 | 71 | ## TODO 72 | 73 | See [*projects* section](https://github.com/lelledaniele/upaygo/projects) 74 | -------------------------------------------------------------------------------- /amount/amount.go: -------------------------------------------------------------------------------- 1 | package appamount 2 | 3 | import ( 4 | appcurrency "github.com/lelledaniele/upaygo/currency" 5 | ) 6 | 7 | type Amount interface { 8 | GetAmount() int 9 | GetCurrency() appcurrency.Currency 10 | 11 | Equal(amount Amount) bool 12 | } 13 | 14 | type a struct { 15 | A int `json:"amount"` 16 | C appcurrency.Currency `json:"currency"` 17 | } 18 | 19 | // GetAmount exposes a.A value 20 | func (a *a) GetAmount() int { 21 | return a.A 22 | } 23 | 24 | // GetCurrency exposes a.C value 25 | func (a *a) GetCurrency() appcurrency.Currency { 26 | return a.C 27 | } 28 | 29 | // Equal check if a is equal of b 30 | func (a *a) Equal(b Amount) bool { 31 | return a.GetAmount() == b.GetAmount() && 32 | a.GetCurrency() != nil && 33 | a.GetCurrency().Equal(b.GetCurrency()) 34 | } 35 | 36 | // New returns a new instance of a 37 | func New(v int, cs string) (Amount, error) { 38 | c, e := appcurrency.New(cs) 39 | if e != nil { 40 | return nil, e 41 | } 42 | 43 | return &a{v, c}, nil 44 | } 45 | -------------------------------------------------------------------------------- /amount/amount_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package appamount_test 4 | 5 | import ( 6 | "testing" 7 | 8 | appamount "github.com/lelledaniele/upaygo/amount" 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | ) 11 | 12 | func TestNew(t *testing.T) { 13 | a := 4099 14 | c, _ := appcurrency.New("EUR") 15 | got, e := appamount.New(a, c.GetISO4217()) 16 | if e != nil { 17 | t.Errorf("error during the amount creation: %v", e) 18 | } 19 | 20 | if got.GetCurrency().GetISO4217() != c.GetISO4217() { 21 | t.Errorf("error during the amount creation, currency value incorrect, got: %v want: %v", got.GetCurrency(), c) 22 | } 23 | 24 | if got.GetAmount() != a { 25 | t.Errorf("error during the amount creation, amount value incorrect, got: %v want: %v", got.GetAmount(), a) 26 | } 27 | } 28 | 29 | func TestNewWrongCurrency(t *testing.T) { 30 | _, e := appamount.New(10, "I AM A WRONG CURRENCY") 31 | if e == nil { 32 | t.Error("new amount with wrong currency created") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /config.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "stripe": { 3 | "api_keys": { 4 | "EUR": { 5 | "pk_key": "pk_xxx", 6 | "sk_key": "sk_xxx" 7 | }, 8 | "CAD": { 9 | "pk_key": "pk_xxx", 10 | "sk_key": "sk_xxx" 11 | }, 12 | "default": { 13 | "pk_key": "pk_xxx", 14 | "sk_key": "sk_xxx" 15 | } 16 | } 17 | }, 18 | "server": { 19 | "protocol": "http://", 20 | "domain": "localhost", 21 | "port": "8080" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package appconfig 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | ) 9 | 10 | var s config 11 | 12 | // ImportConfig reads the config json reader and save the content in s global var 13 | func ImportConfig(r io.Reader) error { 14 | b, e := ioutil.ReadAll(r) 15 | if e != nil { 16 | return fmt.Errorf("Impossible to read configuration: %v\n", e) 17 | } 18 | 19 | s = config{} // Reset existing configs 20 | e = json.Unmarshal(b, &s) 21 | if e != nil { 22 | return fmt.Errorf("Impossible to unmarshal configuration: %v\n", e) 23 | } 24 | 25 | return nil 26 | } 27 | 28 | // public properties needed for json.Unmarshal 29 | type config struct { 30 | Stripe apiKeys `json:"stripe"` 31 | Server server `json:"server"` 32 | } 33 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package appconfig_test 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | 9 | appconfig "github.com/lelledaniele/upaygo/config" 10 | ) 11 | 12 | const ( 13 | confStripeWithDefault = ` 14 | { 15 | "stripe": { 16 | "api_keys": { 17 | "EUR": { 18 | "pk_key": "pk_EUR", 19 | "sk_key": "sk_EUR" 20 | }, 21 | "default": { 22 | "pk_key": "pk_DEFAULT", 23 | "sk_key": "sk_DEFAULT" 24 | } 25 | } 26 | } 27 | } 28 | ` 29 | confStripeWithoutDefault = ` 30 | { 31 | "stripe": { 32 | "api_keys": { 33 | "EUR": { 34 | "pk_key": "pk_EUR", 35 | "sk_key": "sk_EUR" 36 | } 37 | } 38 | } 39 | } 40 | ` 41 | confServer = ` 42 | { 43 | "server": { 44 | "protocol": "https://", 45 | "domain": "localhost", 46 | "port": "8080" 47 | } 48 | } 49 | ` 50 | ) 51 | 52 | func TestDefaultStripeAPIConfig(t *testing.T) { 53 | e := appconfig.ImportConfig(strings.NewReader(confStripeWithDefault)) 54 | if e != nil { 55 | t.Errorf("error during the config import: %v", e) 56 | } 57 | 58 | got, e := appconfig.GetStripeAPIConfigByCurrency("NOT_FOUND_CURRENCY") 59 | if e != nil { 60 | t.Errorf("error during the retrieve of Stripe API config by inexistent currency: %v", e) 61 | } 62 | 63 | if got.GetPK() != "pk_DEFAULT" || got.GetSK() != "sk_DEFAULT" { 64 | t.Errorf("Stripe API config for an inexistent currency does not return the default value") 65 | } 66 | } 67 | 68 | func TestWithoutDefaultStripeAPIConfig(t *testing.T) { 69 | e := appconfig.ImportConfig(strings.NewReader(confStripeWithoutDefault)) 70 | if e != nil { 71 | t.Errorf("error during the config import: %v", e) 72 | } 73 | 74 | _, e = appconfig.GetStripeAPIConfigByCurrency("NOT_FOUND_CURRENCY") 75 | if e == nil { 76 | t.Error("configuration without Stripe default API keys must return an error") 77 | } 78 | } 79 | 80 | func TestCurrencyStripeAPIConfig(t *testing.T) { 81 | e := appconfig.ImportConfig(strings.NewReader(confStripeWithDefault)) 82 | if e != nil { 83 | t.Errorf("error during the config import: %v", e) 84 | } 85 | 86 | got, e := appconfig.GetStripeAPIConfigByCurrency("EUR") 87 | if e != nil { 88 | t.Errorf("error during the retrieve of Stripe API config by EUR currency: %v", e) 89 | } 90 | 91 | if got.GetPK() != "pk_EUR" || got.GetSK() != "sk_EUR" { 92 | t.Errorf("incorrect Stripe API config for EUR currency, got %v and %v, want %v %v", got.GetPK(), got.GetSK(), "pk_EUR", "sk_EUR") 93 | } 94 | } 95 | 96 | func TestLowercaseCurrencyStripeAPIConfig(t *testing.T) { 97 | e := appconfig.ImportConfig(strings.NewReader(confStripeWithDefault)) 98 | if e != nil { 99 | t.Errorf("error during the config import: %v", e) 100 | } 101 | 102 | got, e := appconfig.GetStripeAPIConfigByCurrency("eur") 103 | if e != nil { 104 | t.Errorf("error during the retrieve of Stripe API config by eur currency: %v", e) 105 | } 106 | 107 | if got.GetPK() != "pk_EUR" || got.GetSK() != "sk_EUR" { 108 | t.Errorf("incorrect Stripe API config for eur currency, got %v and %v, want %v %v", got.GetPK(), got.GetSK(), "pk_EUR", "sk_EUR") 109 | } 110 | } 111 | 112 | func TestServerConfig(t *testing.T) { 113 | e := appconfig.ImportConfig(strings.NewReader(confServer)) 114 | if e != nil { 115 | t.Errorf("error during the config import: %v", e) 116 | } 117 | 118 | got := appconfig.GetServerConfig() 119 | 120 | if got.GetPort() != "8080" { 121 | t.Errorf("incorrect server config PORT, got: %v want: %v", got.GetPort(), "8080") 122 | } 123 | 124 | if got.GetDomain() != "localhost" { 125 | t.Errorf("incorrect server config domain, got: %v want: %v", got.GetDomain(), "localhost") 126 | } 127 | 128 | if got.GetProtocol() != "https://" { 129 | t.Errorf("incorrect server config protocol, got: %v want: %v", got.GetProtocol(), "https://") 130 | } 131 | 132 | if got.GetURI() != "https://localhost:8080" { 133 | t.Errorf("incorrect server config URI, got: %v want: %v", got.GetURI(), "https://localhost:8080") 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /config/server.go: -------------------------------------------------------------------------------- 1 | package appconfig 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // ServerConfig exposes the server public method without exposing properties 8 | type ServerConfig interface { 9 | GetProtocol() string 10 | GetDomain() string 11 | GetPort() string 12 | GetURI() string 13 | } 14 | 15 | // GetServerConfig returns the server config 16 | func GetServerConfig() ServerConfig { 17 | return &s.Server 18 | } 19 | 20 | // public properties needed for json.Unmarshal 21 | type server struct { 22 | Protocol string `json:"protocol"` 23 | Domain string `json:"domain"` 24 | Port string `json:"port"` 25 | } 26 | 27 | // GetProtocol exposes server.Protocol 28 | func (s *server) GetProtocol() string { 29 | return s.Protocol 30 | } 31 | 32 | // GetDomain exposes server.Domain 33 | func (s *server) GetDomain() string { 34 | return s.Domain 35 | } 36 | 37 | // GetPort exposes server.Port 38 | func (s *server) GetPort() string { 39 | return s.Port 40 | } 41 | 42 | // GetURI returns server URL by combination of Protocol, Domain and Port 43 | func (s *server) GetURI() string { 44 | var b strings.Builder 45 | b.WriteString(s.Protocol) 46 | b.WriteString(s.Domain) 47 | b.WriteString(":") 48 | b.WriteString(s.Port) 49 | 50 | return b.String() 51 | } 52 | -------------------------------------------------------------------------------- /config/stripe.go: -------------------------------------------------------------------------------- 1 | package appconfig 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // GetStripeAPIConfigByCurrency returns the API keys by c currency 9 | // returns default API keys if c not in b 10 | func GetStripeAPIConfigByCurrency(c string) (APIConfig, error) { 11 | r, f := s.Stripe.Keys[strings.ToUpper(c)] 12 | if f { 13 | return &r, nil 14 | } 15 | 16 | r, f = s.Stripe.Keys["default"] 17 | if f { 18 | return &r, nil 19 | } 20 | 21 | return nil, errors.New("default API keys not found") 22 | } 23 | 24 | // APIConfig exposes the PK and SK, without exposing the struct (currencyAPIConfig) 25 | type APIConfig interface { 26 | GetPK() string 27 | GetSK() string 28 | } 29 | 30 | // public properties needed for json.Unmarshal 31 | type apiKeys struct { 32 | Keys map[string]currencyAPIConfig `json:"api_keys"` 33 | } 34 | 35 | // public properties needed for json.Unmarshal 36 | type currencyAPIConfig struct { 37 | PK string `json:"pk_key"` 38 | SK string `json:"sk_key"` 39 | } 40 | 41 | // GetSK exposes currencyAPIConfig.PK 42 | func (s *currencyAPIConfig) GetPK() string { 43 | return s.PK 44 | } 45 | 46 | // GetSK exposes currencyAPIConfig.SK 47 | func (s *currencyAPIConfig) GetSK() string { 48 | return s.SK 49 | } 50 | -------------------------------------------------------------------------------- /controller/rest/intent/cancel/restintentcancel.go: -------------------------------------------------------------------------------- 1 | package apprestintentcancel 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | apperror "github.com/lelledaniele/upaygo/error" 11 | apppaymentintentcancel "github.com/lelledaniele/upaygo/payment/intent/cancel" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | const ( 17 | URL = "/payment_intents/{id}/cancel" 18 | Method = http.MethodPost 19 | 20 | responseTye = "application/json" 21 | 22 | errorParamPathMissing = "missing URL in-path mandatory parameters to cancel a payment intent" 23 | errorParsingParam = "error during the payload parsing: '%v'" 24 | errorParamPayloadMissing = "missing payload mandatory parameters to cancel a payment intent" 25 | errorAmountCreation = "error during the intent amount creation: '%v'" 26 | errorIntentCancel = "error during the intent cancel: '%v'" 27 | errorIntentEncoding = "error during the intent encoding: '%v'" 28 | ) 29 | 30 | // @Summary Cancel an intent 31 | // @Description Cancel an confirmed intent 32 | // @Tags Intent 33 | // @Accept x-www-form-urlencoded 34 | // @Produce json 35 | // @Param id path string true "Intent's ID" 36 | // @Param currency formData string true "Intent's currency" 37 | // @Success 200 {interface} apppaymentintent.Intent 38 | // @Failure 400 {object} apperror.RESTError 39 | // @Failure 405 {object} apperror.RESTError 40 | // @Failure 500 {object} apperror.RESTError 41 | // @Router /payment_intents/{id}/cancel [post] 42 | func Handler(w http.ResponseWriter, r *http.Request) { 43 | w.Header().Set("Content-Type", responseTye) 44 | 45 | ID, cur, e := getParams(r) 46 | if e != nil { 47 | w.WriteHeader(http.StatusBadRequest) 48 | 49 | e := apperror.RESTError{ 50 | M: e.Error(), 51 | } 52 | _ = json.NewEncoder(w).Encode(e) 53 | 54 | return 55 | } 56 | 57 | appintent, e := apppaymentintentcancel.Cancel(ID, cur) 58 | if e != nil { 59 | w.WriteHeader(http.StatusInternalServerError) 60 | 61 | e := apperror.RESTError{ 62 | M: fmt.Sprintf(errorIntentCancel, e), 63 | } 64 | _ = json.NewEncoder(w).Encode(e) 65 | 66 | return 67 | } 68 | 69 | e = json.NewEncoder(w).Encode(appintent) 70 | if e != nil { 71 | w.WriteHeader(http.StatusInternalServerError) 72 | 73 | e := apperror.RESTError{ 74 | M: fmt.Sprintf(errorIntentEncoding, e), 75 | } 76 | _ = json.NewEncoder(w).Encode(e) 77 | 78 | return 79 | } 80 | } 81 | 82 | // Get and transform the payload params into domain structs 83 | func getParams(r *http.Request) (string, appcurrency.Currency, error) { 84 | vars := mux.Vars(r) 85 | ID, _ := vars["id"] 86 | 87 | e := r.ParseForm() 88 | if e != nil { 89 | return "", nil, fmt.Errorf(errorParsingParam, e.Error()) 90 | } 91 | 92 | p := r.Form 93 | if p.Get("currency") == "" { 94 | return "", nil, errors.New(errorParamPayloadMissing) 95 | } 96 | 97 | cur, e := appcurrency.New(p.Get("currency")) 98 | if e != nil { 99 | return "", nil, fmt.Errorf(errorAmountCreation, e.Error()) 100 | } 101 | 102 | return ID, cur, nil 103 | } 104 | -------------------------------------------------------------------------------- /controller/rest/intent/cancel/restintentcancel_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apprestintentcancel_test 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "strings" 14 | "testing" 15 | 16 | appconfig "github.com/lelledaniele/upaygo/config" 17 | apprestintentcancel "github.com/lelledaniele/upaygo/controller/rest/intent/cancel" 18 | appcurrency "github.com/lelledaniele/upaygo/currency" 19 | 20 | "github.com/gorilla/mux" 21 | "github.com/stripe/stripe-go" 22 | "github.com/stripe/stripe-go/paymentintent" 23 | ) 24 | 25 | const ( 26 | errorRestCreateIntent = "cancel intent controller failed: %v" 27 | ) 28 | 29 | type responseIntent struct { 30 | IntentGatewayReference string `json:"gateway_reference"` 31 | Status responseStatus `json:"status"` 32 | } 33 | 34 | type responseStatus struct { 35 | R string `json:"gateway_reference"` 36 | } 37 | 38 | func TestMain(m *testing.M) { 39 | var fcp string 40 | 41 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 42 | flag.Parse() 43 | 44 | if fcp == "" { 45 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 46 | os.Exit(1) 47 | } 48 | 49 | fc, e := os.Open(fcp) 50 | if e != nil { 51 | fmt.Printf("Impossible to get configuration file: %v\n", e) 52 | os.Exit(1) 53 | } 54 | defer fc.Close() 55 | 56 | e = appconfig.ImportConfig(fc) 57 | if e != nil { 58 | fmt.Printf("Error durring file config import: %v", e) 59 | os.Exit(1) 60 | } 61 | 62 | os.Exit(m.Run()) 63 | } 64 | 65 | func createTestIntent() (string, error) { 66 | cur, _ := appcurrency.New("EUR") 67 | am := int64(4567) 68 | pip := &stripe.PaymentIntentParams{ 69 | Amount: stripe.Int64(am), 70 | Currency: stripe.String(cur.GetISO4217()), 71 | PaymentMethod: stripe.String("pm_card_visa"), 72 | SetupFutureUsage: stripe.String("off_session"), 73 | ConfirmationMethod: stripe.String("automatic"), 74 | Confirm: stripe.Bool(true), 75 | CaptureMethod: stripe.String("manual"), 76 | } 77 | 78 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 79 | stripe.Key = sck.GetSK() 80 | 81 | intent, e := paymentintent.New(pip) 82 | if e != nil { 83 | return "", fmt.Errorf("impossible to create a new payment intent for testing: %v", e) 84 | } 85 | 86 | return intent.ID, e 87 | } 88 | 89 | // Test a create intent request 90 | func Test(t *testing.T) { 91 | intentID, e := createTestIntent() 92 | if e != nil { 93 | t.Error(e.Error()) 94 | } 95 | 96 | w := httptest.NewRecorder() 97 | req := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("currency=EUR")) 98 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 99 | req = mux.SetURLVars(req, map[string]string{"id": intentID}) 100 | 101 | apprestintentcancel.Handler(w, req) 102 | 103 | res := w.Result() 104 | resBody, e := ioutil.ReadAll(res.Body) 105 | if e != nil { 106 | t.Errorf(errorRestCreateIntent, e) 107 | } 108 | defer res.Body.Close() 109 | 110 | var resI responseIntent 111 | e = json.Unmarshal(resBody, &resI) 112 | if e != nil { 113 | t.Errorf(errorRestCreateIntent, e) 114 | } 115 | 116 | if resI.IntentGatewayReference == "" { 117 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 118 | } 119 | 120 | if resI.Status.R != "canceled" { 121 | t.Errorf(errorRestCreateIntent, "the body response does not have the status 'canceled'") 122 | } 123 | 124 | _, _ = paymentintent.Cancel(intentID, nil) 125 | } 126 | -------------------------------------------------------------------------------- /controller/rest/intent/capture/restintentcapture.go: -------------------------------------------------------------------------------- 1 | package apprestintentcapture 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | apperror "github.com/lelledaniele/upaygo/error" 11 | apppaymentintentcapture "github.com/lelledaniele/upaygo/payment/intent/capture" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | const ( 17 | URL = "/payment_intents/{id}/capture" 18 | Method = http.MethodPost 19 | 20 | responseTye = "application/json" 21 | 22 | errorParamPathMissing = "missing URL in-path mandatory parameters to capture a payment intent" 23 | errorParsingParam = "error during the payload parsing: '%v'" 24 | errorParamPayloadMissing = "missing payload mandatory parameters to capture a payment intent" 25 | errorAmountCreation = "error during the intent amount creation: '%v'" 26 | errorIntentCapture = "error during the intent capture: '%v'" 27 | errorIntentEncoding = "error during the intent encoding: '%v'" 28 | ) 29 | 30 | // @Summary Capture an intent 31 | // @Description Capture an confirmed intent 32 | // @Tags Intent 33 | // @Accept x-www-form-urlencoded 34 | // @Produce json 35 | // @Param id path string true "Intent's ID" 36 | // @Param currency formData string true "Intent's currency" 37 | // @Success 200 {interface} apppaymentintent.Intent 38 | // @Failure 400 {object} apperror.RESTError 39 | // @Failure 405 {object} apperror.RESTError 40 | // @Failure 500 {object} apperror.RESTError 41 | // @Router /payment_intents/{id}/capture [post] 42 | func Handler(w http.ResponseWriter, r *http.Request) { 43 | w.Header().Set("Content-Type", responseTye) 44 | 45 | ID, cur, e := getParams(r) 46 | if e != nil { 47 | w.WriteHeader(http.StatusBadRequest) 48 | 49 | e := apperror.RESTError{ 50 | M: e.Error(), 51 | } 52 | _ = json.NewEncoder(w).Encode(e) 53 | 54 | return 55 | } 56 | 57 | appintent, e := apppaymentintentcapture.Capture(ID, cur) 58 | if e != nil { 59 | w.WriteHeader(http.StatusInternalServerError) 60 | 61 | e := apperror.RESTError{ 62 | M: fmt.Sprintf(errorIntentCapture, e), 63 | } 64 | _ = json.NewEncoder(w).Encode(e) 65 | 66 | return 67 | } 68 | 69 | e = json.NewEncoder(w).Encode(appintent) 70 | if e != nil { 71 | w.WriteHeader(http.StatusInternalServerError) 72 | 73 | e := apperror.RESTError{ 74 | M: fmt.Sprintf(errorIntentEncoding, e), 75 | } 76 | _ = json.NewEncoder(w).Encode(e) 77 | 78 | return 79 | } 80 | } 81 | 82 | // Get and transform the payload params into domain structs 83 | func getParams(r *http.Request) (string, appcurrency.Currency, error) { 84 | vars := mux.Vars(r) 85 | ID, _ := vars["id"] 86 | 87 | e := r.ParseForm() 88 | if e != nil { 89 | return "", nil, fmt.Errorf(errorParsingParam, e.Error()) 90 | } 91 | 92 | p := r.Form 93 | if p.Get("currency") == "" { 94 | return "", nil, errors.New(errorParamPayloadMissing) 95 | } 96 | 97 | cur, e := appcurrency.New(p.Get("currency")) 98 | if e != nil { 99 | return "", nil, fmt.Errorf(errorAmountCreation, e.Error()) 100 | } 101 | 102 | return ID, cur, nil 103 | } 104 | -------------------------------------------------------------------------------- /controller/rest/intent/capture/restintentcapture_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apprestintentcapture_test 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "strings" 14 | "testing" 15 | 16 | appconfig "github.com/lelledaniele/upaygo/config" 17 | apprestintentcapture "github.com/lelledaniele/upaygo/controller/rest/intent/capture" 18 | appcurrency "github.com/lelledaniele/upaygo/currency" 19 | 20 | "github.com/gorilla/mux" 21 | "github.com/stripe/stripe-go" 22 | "github.com/stripe/stripe-go/paymentintent" 23 | ) 24 | 25 | const ( 26 | errorRestCreateIntent = "capture intent controller failed: %v" 27 | ) 28 | 29 | type responseIntent struct { 30 | IntentGatewayReference string `json:"gateway_reference"` 31 | Status responseStatus `json:"status"` 32 | } 33 | 34 | type responseStatus struct { 35 | R string `json:"gateway_reference"` 36 | } 37 | 38 | func TestMain(m *testing.M) { 39 | var fcp string 40 | 41 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 42 | flag.Parse() 43 | 44 | if fcp == "" { 45 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 46 | os.Exit(1) 47 | } 48 | 49 | fc, e := os.Open(fcp) 50 | if e != nil { 51 | fmt.Printf("Impossible to get configuration file: %v\n", e) 52 | os.Exit(1) 53 | } 54 | defer fc.Close() 55 | 56 | e = appconfig.ImportConfig(fc) 57 | if e != nil { 58 | fmt.Printf("Error durring file config import: %v", e) 59 | os.Exit(1) 60 | } 61 | 62 | os.Exit(m.Run()) 63 | } 64 | 65 | func createTestIntent() (string, error) { 66 | cur, _ := appcurrency.New("EUR") 67 | am := int64(1179) 68 | pip := &stripe.PaymentIntentParams{ 69 | Amount: stripe.Int64(am), 70 | Currency: stripe.String(cur.GetISO4217()), 71 | PaymentMethod: stripe.String("pm_card_visa"), 72 | SetupFutureUsage: stripe.String("off_session"), 73 | ConfirmationMethod: stripe.String("automatic"), 74 | Confirm: stripe.Bool(true), 75 | CaptureMethod: stripe.String("manual"), 76 | } 77 | 78 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 79 | stripe.Key = sck.GetSK() 80 | 81 | intent, e := paymentintent.New(pip) 82 | if e != nil { 83 | return "", fmt.Errorf("impossible to create a new payment intent for testing: %v", e) 84 | } 85 | 86 | return intent.ID, e 87 | } 88 | 89 | // Test a create intent request 90 | func Test(t *testing.T) { 91 | intentID, e := createTestIntent() 92 | if e != nil { 93 | t.Error(e.Error()) 94 | } 95 | 96 | w := httptest.NewRecorder() 97 | req := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("currency=EUR")) 98 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 99 | req = mux.SetURLVars(req, map[string]string{"id": intentID}) 100 | 101 | apprestintentcapture.Handler(w, req) 102 | 103 | res := w.Result() 104 | resBody, e := ioutil.ReadAll(res.Body) 105 | if e != nil { 106 | t.Errorf(errorRestCreateIntent, e) 107 | } 108 | defer res.Body.Close() 109 | 110 | var resI responseIntent 111 | e = json.Unmarshal(resBody, &resI) 112 | if e != nil { 113 | t.Errorf(errorRestCreateIntent, e) 114 | } 115 | 116 | if resI.IntentGatewayReference == "" { 117 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 118 | } 119 | 120 | if resI.Status.R != "succeeded" { 121 | t.Errorf(errorRestCreateIntent, "the body response does not have the status 'succeeded'") 122 | } 123 | 124 | _, _ = paymentintent.Cancel(intentID, nil) 125 | } 126 | -------------------------------------------------------------------------------- /controller/rest/intent/confirm/restintentconfirm.go: -------------------------------------------------------------------------------- 1 | package apprestintentconfirm 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | apperror "github.com/lelledaniele/upaygo/error" 11 | apppaymentintentconfirm "github.com/lelledaniele/upaygo/payment/intent/confirm" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | const ( 17 | URL = "/payment_intents/{id}/confirm" 18 | Method = http.MethodPost 19 | 20 | responseTye = "application/json" 21 | 22 | errorParamPathMissing = "missing URL in-path mandatory parameters to confirm a payment intent" 23 | errorParsingParam = "error during the payload parsing: '%v'" 24 | errorParamPayloadMissing = "missing payload mandatory parameters to confirm a payment intent" 25 | errorAmountCreation = "error during the intent amount creation: '%v'" 26 | errorIntentConfirmation = "error during the intent confirmation: '%v'" 27 | errorIntentEncoding = "error during the intent encoding: '%v'" 28 | ) 29 | 30 | // @Summary Confirm an intent 31 | // @Description Confirm an unconfirmed intent 32 | // @Tags Intent 33 | // @Accept x-www-form-urlencoded 34 | // @Produce json 35 | // @Param id path string true "Intent's ID" 36 | // @Param currency formData string true "Intent's currency" 37 | // @Success 200 {interface} apppaymentintent.Intent 38 | // @Failure 400 {object} apperror.RESTError 39 | // @Failure 405 {object} apperror.RESTError 40 | // @Failure 500 {object} apperror.RESTError 41 | // @Router /payment_intents/{id}/confirm [post] 42 | func Handler(w http.ResponseWriter, r *http.Request) { 43 | w.Header().Set("Content-Type", responseTye) 44 | 45 | ID, cur, e := getParams(r) 46 | if e != nil { 47 | w.WriteHeader(http.StatusBadRequest) 48 | 49 | e := apperror.RESTError{ 50 | M: e.Error(), 51 | } 52 | _ = json.NewEncoder(w).Encode(e) 53 | 54 | return 55 | } 56 | 57 | appintent, e := apppaymentintentconfirm.Confirm(ID, cur) 58 | if e != nil { 59 | w.WriteHeader(http.StatusInternalServerError) 60 | 61 | e := apperror.RESTError{ 62 | M: fmt.Sprintf(errorIntentConfirmation, e), 63 | } 64 | _ = json.NewEncoder(w).Encode(e) 65 | 66 | return 67 | } 68 | 69 | e = json.NewEncoder(w).Encode(appintent) 70 | if e != nil { 71 | w.WriteHeader(http.StatusInternalServerError) 72 | 73 | e := apperror.RESTError{ 74 | M: fmt.Sprintf(errorIntentEncoding, e), 75 | } 76 | _ = json.NewEncoder(w).Encode(e) 77 | 78 | return 79 | } 80 | } 81 | 82 | // Get and transform the payload params into domain structs 83 | func getParams(r *http.Request) (string, appcurrency.Currency, error) { 84 | vars := mux.Vars(r) 85 | ID, _ := vars["id"] 86 | 87 | e := r.ParseForm() 88 | if e != nil { 89 | return "", nil, fmt.Errorf(errorParsingParam, e.Error()) 90 | } 91 | 92 | p := r.Form 93 | if p.Get("currency") == "" { 94 | return "", nil, errors.New(errorParamPayloadMissing) 95 | } 96 | 97 | cur, e := appcurrency.New(p.Get("currency")) 98 | if e != nil { 99 | return "", nil, fmt.Errorf(errorAmountCreation, e.Error()) 100 | } 101 | 102 | return ID, cur, nil 103 | } 104 | -------------------------------------------------------------------------------- /controller/rest/intent/confirm/restintentconfirm_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apprestintentconfirm_test 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "strings" 14 | "testing" 15 | 16 | appconfig "github.com/lelledaniele/upaygo/config" 17 | apprestintentconfirm "github.com/lelledaniele/upaygo/controller/rest/intent/confirm" 18 | appcurrency "github.com/lelledaniele/upaygo/currency" 19 | 20 | "github.com/gorilla/mux" 21 | "github.com/stripe/stripe-go" 22 | "github.com/stripe/stripe-go/paymentintent" 23 | ) 24 | 25 | const ( 26 | errorRestCreateIntent = "confirm intent controller failed: %v" 27 | ) 28 | 29 | type responseIntent struct { 30 | IntentGatewayReference string `json:"gateway_reference"` 31 | Status responseStatus `json:"status"` 32 | } 33 | 34 | type responseStatus struct { 35 | R string `json:"gateway_reference"` 36 | } 37 | 38 | func TestMain(m *testing.M) { 39 | var fcp string 40 | 41 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 42 | flag.Parse() 43 | 44 | if fcp == "" { 45 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 46 | os.Exit(1) 47 | } 48 | 49 | fc, e := os.Open(fcp) 50 | if e != nil { 51 | fmt.Printf("Impossible to get configuration file: %v\n", e) 52 | os.Exit(1) 53 | } 54 | defer fc.Close() 55 | 56 | e = appconfig.ImportConfig(fc) 57 | if e != nil { 58 | fmt.Printf("Error durring file config import: %v", e) 59 | os.Exit(1) 60 | } 61 | 62 | os.Exit(m.Run()) 63 | } 64 | 65 | func createTestIntent() (string, error) { 66 | cur, _ := appcurrency.New("EUR") 67 | am := int64(4567) 68 | pip := &stripe.PaymentIntentParams{ 69 | Amount: stripe.Int64(am), 70 | Currency: stripe.String(cur.GetISO4217()), 71 | PaymentMethod: stripe.String("pm_card_visa"), 72 | SetupFutureUsage: stripe.String("off_session"), 73 | ConfirmationMethod: stripe.String("manual"), 74 | CaptureMethod: stripe.String("manual"), 75 | } 76 | 77 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 78 | stripe.Key = sck.GetSK() 79 | 80 | intent, e := paymentintent.New(pip) 81 | if e != nil { 82 | return "", fmt.Errorf("impossible to create a new payment intent for testing: %v", e) 83 | } 84 | 85 | return intent.ID, e 86 | } 87 | 88 | // Test a create intent request 89 | func Test(t *testing.T) { 90 | intentID, e := createTestIntent() 91 | if e != nil { 92 | t.Error(e.Error()) 93 | } 94 | 95 | w := httptest.NewRecorder() 96 | req := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("currency=EUR")) 97 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 98 | req = mux.SetURLVars(req, map[string]string{"id": intentID}) 99 | 100 | apprestintentconfirm.Handler(w, req) 101 | 102 | res := w.Result() 103 | resBody, e := ioutil.ReadAll(res.Body) 104 | if e != nil { 105 | t.Errorf(errorRestCreateIntent, e) 106 | } 107 | defer res.Body.Close() 108 | 109 | var resI responseIntent 110 | e = json.Unmarshal(resBody, &resI) 111 | if e != nil { 112 | t.Errorf(errorRestCreateIntent, e) 113 | } 114 | 115 | if resI.IntentGatewayReference == "" { 116 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 117 | } 118 | 119 | if resI.Status.R != "requires_capture" { 120 | t.Errorf(errorRestCreateIntent, "the body response does not have the status as 'requires_capture'") 121 | } 122 | 123 | _, _ = paymentintent.Cancel(intentID, nil) 124 | } 125 | -------------------------------------------------------------------------------- /controller/rest/intent/create/restintentcreate.go: -------------------------------------------------------------------------------- 1 | package apprestintentcreate 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "strconv" 9 | 10 | appamount "github.com/lelledaniele/upaygo/amount" 11 | appcustomer "github.com/lelledaniele/upaygo/customer" 12 | apperror "github.com/lelledaniele/upaygo/error" 13 | appintentcreate "github.com/lelledaniele/upaygo/payment/intent/create" 14 | appsource "github.com/lelledaniele/upaygo/payment/source" 15 | ) 16 | 17 | const ( 18 | URL = "/payment_intents" 19 | Method = http.MethodPost 20 | 21 | responseTye = "application/json" 22 | 23 | errorParsingParam = "error during the payload parsing: '%v'" 24 | errorParamMissing = "missing payload mandatory parameters to create a payment intent" 25 | errorParamAmountType = "error during the amount conversion: '%v'" 26 | errorAmountCreation = "error during the intent amount creation: '%v'" 27 | errorIntentCreation = "error during the intent creation: '%v'" 28 | errorIntentEncoding = "error during the intent encoding: '%v'" 29 | ) 30 | 31 | // @Summary Create an intent 32 | // @Description Create an unconfirmed and manual intent 33 | // @Tags Intent 34 | // @Accept x-www-form-urlencoded 35 | // @Produce json 36 | // @Param currency formData string true "Intent's currency" 37 | // @Param amount formData int true "Intent's amount" 38 | // @Param payment_source formData string true "Intent's payment source" 39 | // @Param customer_reference formData string false "Intent's customer reference" 40 | // @Success 200 {interface} apppaymentintent.Intent 41 | // @Failure 400 {object} apperror.RESTError 42 | // @Failure 405 {object} apperror.RESTError 43 | // @Failure 500 {object} apperror.RESTError 44 | // @Router /payment_intents [post] 45 | func Handler(w http.ResponseWriter, r *http.Request) { 46 | w.Header().Set("Content-Type", responseTye) 47 | 48 | amount, ps, cus, e := getParams(r) 49 | if e != nil { 50 | w.WriteHeader(http.StatusBadRequest) 51 | 52 | e := apperror.RESTError{ 53 | M: e.Error(), 54 | } 55 | _ = json.NewEncoder(w).Encode(e) 56 | 57 | return 58 | } 59 | 60 | appintent, e := appintentcreate.Create(amount, ps, cus) 61 | if e != nil { 62 | w.WriteHeader(http.StatusInternalServerError) 63 | 64 | e := apperror.RESTError{ 65 | M: fmt.Sprintf(errorIntentCreation, e), 66 | } 67 | _ = json.NewEncoder(w).Encode(e) 68 | 69 | return 70 | } 71 | 72 | e = json.NewEncoder(w).Encode(appintent) 73 | if e != nil { 74 | w.WriteHeader(http.StatusInternalServerError) 75 | 76 | e := apperror.RESTError{ 77 | M: fmt.Sprintf(errorIntentEncoding, e), 78 | } 79 | _ = json.NewEncoder(w).Encode(e) 80 | 81 | return 82 | } 83 | } 84 | 85 | // Get and transform the payload params into domain structs 86 | func getParams(r *http.Request) (appamount.Amount, appsource.Source, appcustomer.Customer, error) { 87 | e := r.ParseForm() 88 | if e != nil { 89 | return nil, nil, nil, fmt.Errorf(errorParsingParam, e.Error()) 90 | } 91 | 92 | p := r.Form 93 | if p.Get("currency") == "" || p.Get("amount") == "" || p.Get("payment_source") == "" { 94 | return nil, nil, nil, errors.New(errorParamMissing) 95 | } 96 | 97 | ai, e := strconv.Atoi(p.Get("amount")) 98 | if e != nil { 99 | return nil, nil, nil, fmt.Errorf(errorParamAmountType, e.Error()) 100 | } 101 | 102 | amount, e := appamount.New(ai, p.Get("currency")) 103 | if e != nil { 104 | return nil, nil, nil, fmt.Errorf(errorAmountCreation, e.Error()) 105 | } 106 | 107 | var cus appcustomer.Customer 108 | if p.Get("customer_reference") != "" { 109 | cus = appcustomer.New(p.Get("customer_reference"), "") 110 | } 111 | 112 | ps := appsource.New(p.Get("payment_source")) 113 | 114 | return amount, ps, cus, nil 115 | } 116 | -------------------------------------------------------------------------------- /controller/rest/intent/create/restintentcreate_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apprestintentcreate_test 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "strings" 14 | "testing" 15 | 16 | "github.com/stripe/stripe-go/customer" 17 | 18 | appconfig "github.com/lelledaniele/upaygo/config" 19 | apprestintentcreate "github.com/lelledaniele/upaygo/controller/rest/intent/create" 20 | appcurrency "github.com/lelledaniele/upaygo/currency" 21 | appcustomer "github.com/lelledaniele/upaygo/customer" 22 | ) 23 | 24 | const ( 25 | errorRestCreateIntent = "create intent controller failed: %v" 26 | ) 27 | 28 | type responseIntent struct { 29 | IntentGatewayReference string `json:"gateway_reference"` 30 | Customer responseCustomer `json:"customer"` 31 | } 32 | 33 | type responseCustomer struct { 34 | R string `json:"gateway_reference"` 35 | } 36 | 37 | func TestMain(m *testing.M) { 38 | var fcp string 39 | 40 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 41 | flag.Parse() 42 | 43 | if fcp == "" { 44 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 45 | os.Exit(1) 46 | } 47 | 48 | fc, e := os.Open(fcp) 49 | if e != nil { 50 | fmt.Printf("Impossible to get configuration file: %v\n", e) 51 | os.Exit(1) 52 | } 53 | defer fc.Close() 54 | 55 | e = appconfig.ImportConfig(fc) 56 | if e != nil { 57 | fmt.Printf("Error durring file config import: %v", e) 58 | os.Exit(1) 59 | } 60 | 61 | os.Exit(m.Run()) 62 | } 63 | 64 | // Test a create intent request 65 | func Test(t *testing.T) { 66 | var resI responseIntent 67 | 68 | c, _ := appcurrency.New("EUR") 69 | cus, e := appcustomer.NewStripe("email@email.com", c) 70 | if e != nil { 71 | t.Errorf(errorRestCreateIntent, e) 72 | } 73 | 74 | a, ps, w := 7777, "pm_card_visa", httptest.NewRecorder() 75 | p := fmt.Sprintf("currency=%v&amount=%v&payment_source=%v&customer_reference=%v", c.GetISO4217(), a, ps, cus.GetGatewayReference()) 76 | req := httptest.NewRequest("POST", "http://example.com", strings.NewReader(p)) 77 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 78 | 79 | apprestintentcreate.Handler(w, req) 80 | 81 | res := w.Result() 82 | resBody, e := ioutil.ReadAll(res.Body) 83 | if e != nil { 84 | t.Errorf(errorRestCreateIntent, e) 85 | } 86 | defer res.Body.Close() 87 | 88 | e = json.Unmarshal(resBody, &resI) 89 | if e != nil { 90 | t.Errorf(errorRestCreateIntent, e) 91 | } 92 | 93 | if resI.IntentGatewayReference == "" { 94 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 95 | } 96 | 97 | if resI.Customer.R == "" { 98 | t.Errorf(errorRestCreateIntent, "the body response does not have the customer reference") 99 | } 100 | 101 | _, _ = customer.Del(cus.GetGatewayReference(), nil) 102 | } 103 | 104 | // Test a create intent request without customer 105 | func TestWithoutCustomer(t *testing.T) { 106 | var resI responseIntent 107 | 108 | c, a, ps, w := "EUR", 9999, "pm_card_visa", httptest.NewRecorder() 109 | p := fmt.Sprintf("currency=%v&amount=%v&payment_source=%v", c, a, ps) 110 | 111 | req := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader(p)) 112 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 113 | 114 | apprestintentcreate.Handler(w, req) 115 | 116 | res := w.Result() 117 | resBody, e := ioutil.ReadAll(res.Body) 118 | _ = res.Body.Close() 119 | if e != nil { 120 | t.Errorf(errorRestCreateIntent, e) 121 | } 122 | 123 | e = json.Unmarshal(resBody, &resI) 124 | if e != nil { 125 | t.Errorf(errorRestCreateIntent, e) 126 | } 127 | 128 | if resI.IntentGatewayReference == "" { 129 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /controller/rest/intent/get/restintentget.go: -------------------------------------------------------------------------------- 1 | package apprestintentget 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | apperror "github.com/lelledaniele/upaygo/error" 11 | apppaymentintentget "github.com/lelledaniele/upaygo/payment/intent/get" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | const ( 17 | URL = "/payment_intents/{id}" 18 | Method = http.MethodGet 19 | 20 | responseTye = "application/json" 21 | 22 | errorParamPathMissing = "missing URL in-path mandatory parameters to get the payment intent" 23 | errorParamQueryMissing = "error during the query parsing: '%v'" 24 | errorAmountCreation = "error during the intent amount creation: '%v'" 25 | errorIntentGet = "error during the intent getter: '%v'" 26 | errorIntentEncoding = "error during the intent encoding: '%v'" 27 | ) 28 | 29 | // @Summary Get an intent 30 | // @Description Get an existing intent 31 | // @Tags Intent 32 | // @Accept x-www-form-urlencoded 33 | // @Produce json 34 | // @Param id path string true "Intent's ID" 35 | // @Param currency formData string true "Intent's currency" 36 | // @Success 200 {interface} apppaymentintent.Intent 37 | // @Failure 400 {object} apperror.RESTError 38 | // @Failure 405 {object} apperror.RESTError 39 | // @Failure 500 {object} apperror.RESTError 40 | // @Router /payment_intents/{id} [get] 41 | func Handler(w http.ResponseWriter, r *http.Request) { 42 | w.Header().Set("Content-Type", responseTye) 43 | 44 | ID, cur, e := getParams(r) 45 | if e != nil { 46 | w.WriteHeader(http.StatusBadRequest) 47 | 48 | e := apperror.RESTError{ 49 | M: e.Error(), 50 | } 51 | _ = json.NewEncoder(w).Encode(e) 52 | 53 | return 54 | } 55 | 56 | appintent, e := apppaymentintentget.Get(ID, cur) 57 | if e != nil { 58 | w.WriteHeader(http.StatusInternalServerError) 59 | 60 | e := apperror.RESTError{ 61 | M: fmt.Sprintf(errorIntentGet, e), 62 | } 63 | _ = json.NewEncoder(w).Encode(e) 64 | 65 | return 66 | } 67 | 68 | e = json.NewEncoder(w).Encode(appintent) 69 | if e != nil { 70 | w.WriteHeader(http.StatusInternalServerError) 71 | 72 | e := apperror.RESTError{ 73 | M: fmt.Sprintf(errorIntentEncoding, e), 74 | } 75 | _ = json.NewEncoder(w).Encode(e) 76 | 77 | return 78 | } 79 | } 80 | 81 | // Get and transform the payload params into domain structs 82 | func getParams(r *http.Request) (string, appcurrency.Currency, error) { 83 | vars := mux.Vars(r) 84 | ID, _ := vars["id"] 85 | 86 | cursym := r.URL.Query().Get("currency") 87 | if cursym == "" { 88 | return "", nil, errors.New(errorParamQueryMissing) 89 | } 90 | 91 | cur, e := appcurrency.New(cursym) 92 | if e != nil { 93 | return "", nil, fmt.Errorf(errorAmountCreation, e.Error()) 94 | } 95 | 96 | return ID, cur, nil 97 | } 98 | -------------------------------------------------------------------------------- /controller/rest/intent/get/restintentget_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apprestintentget_test 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "testing" 14 | 15 | apprestintentget "github.com/lelledaniele/upaygo/controller/rest/intent/get" 16 | 17 | appconfig "github.com/lelledaniele/upaygo/config" 18 | appcurrency "github.com/lelledaniele/upaygo/currency" 19 | 20 | "github.com/gorilla/mux" 21 | "github.com/stripe/stripe-go" 22 | "github.com/stripe/stripe-go/paymentintent" 23 | ) 24 | 25 | const ( 26 | errorRestCreateIntent = "cancel intent controller failed: %v" 27 | ) 28 | 29 | type responseIntent struct { 30 | IntentGatewayReference string `json:"gateway_reference"` 31 | Status responseStatus `json:"status"` 32 | } 33 | 34 | type responseStatus struct { 35 | R string `json:"gateway_reference"` 36 | } 37 | 38 | func TestMain(m *testing.M) { 39 | var fcp string 40 | 41 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 42 | flag.Parse() 43 | 44 | if fcp == "" { 45 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 46 | os.Exit(1) 47 | } 48 | 49 | fc, e := os.Open(fcp) 50 | if e != nil { 51 | fmt.Printf("Impossible to get configuration file: %v\n", e) 52 | os.Exit(1) 53 | } 54 | defer fc.Close() 55 | 56 | e = appconfig.ImportConfig(fc) 57 | if e != nil { 58 | fmt.Printf("Error durring file config import: %v", e) 59 | os.Exit(1) 60 | } 61 | 62 | os.Exit(m.Run()) 63 | } 64 | 65 | func createTestIntent() (string, error) { 66 | cur, _ := appcurrency.New("EUR") 67 | am := int64(4567) 68 | pip := &stripe.PaymentIntentParams{ 69 | Amount: stripe.Int64(am), 70 | Currency: stripe.String(cur.GetISO4217()), 71 | PaymentMethod: stripe.String("pm_card_visa"), 72 | SetupFutureUsage: stripe.String("off_session"), 73 | ConfirmationMethod: stripe.String("automatic"), 74 | Confirm: stripe.Bool(true), 75 | CaptureMethod: stripe.String("manual"), 76 | } 77 | 78 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 79 | stripe.Key = sck.GetSK() 80 | 81 | intent, e := paymentintent.New(pip) 82 | if e != nil { 83 | return "", fmt.Errorf("impossible to create a new payment intent for testing: %v", e) 84 | } 85 | 86 | return intent.ID, e 87 | } 88 | 89 | // Test a create intent request 90 | func Test(t *testing.T) { 91 | intentID, e := createTestIntent() 92 | if e != nil { 93 | t.Error(e.Error()) 94 | } 95 | 96 | w := httptest.NewRecorder() 97 | req := httptest.NewRequest(http.MethodGet, "http://example.com", nil) 98 | req = mux.SetURLVars(req, map[string]string{"id": intentID}) 99 | 100 | q := req.URL.Query() 101 | q.Add("currency", "EUR") 102 | req.URL.RawQuery = q.Encode() 103 | 104 | apprestintentget.Handler(w, req) 105 | 106 | res := w.Result() 107 | resBody, e := ioutil.ReadAll(res.Body) 108 | if e != nil { 109 | t.Errorf(errorRestCreateIntent, e) 110 | } 111 | defer res.Body.Close() 112 | 113 | var resI responseIntent 114 | e = json.Unmarshal(resBody, &resI) 115 | if e != nil { 116 | t.Errorf(errorRestCreateIntent, e) 117 | } 118 | 119 | if resI.IntentGatewayReference == "" { 120 | t.Errorf(errorRestCreateIntent, "the body response does not have the gateway reference") 121 | } 122 | 123 | _, _ = paymentintent.Cancel(intentID, nil) 124 | } 125 | -------------------------------------------------------------------------------- /currency/currency.go: -------------------------------------------------------------------------------- 1 | package appcurrency 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Currency interface { 9 | GetISO4217() string 10 | 11 | Equal(c Currency) bool 12 | } 13 | 14 | type c struct { 15 | ISO4217 string `json:"ISO_4217"` // 3 Char symbol - ISO 4217 16 | } 17 | 18 | // GetISO4217 exposes c.ISO4217 value 19 | func (c *c) GetISO4217() string { 20 | return c.ISO4217 21 | } 22 | 23 | // Equal checks if a == b 24 | func (a *c) Equal(b Currency) bool { 25 | return a.GetISO4217() == b.GetISO4217() 26 | } 27 | 28 | // New returns a new instance of c 29 | func New(ISO4217 string) (Currency, error) { 30 | if len(ISO4217) != 3 { 31 | return nil, fmt.Errorf("'%v' is not a currency ISO 4217 format", ISO4217) 32 | } 33 | 34 | return &c{ISO4217: strings.ToUpper(ISO4217)}, nil 35 | } 36 | -------------------------------------------------------------------------------- /currency/currency_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package appcurrency_test 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | 9 | appcurrency "github.com/lelledaniele/upaygo/currency" 10 | ) 11 | 12 | func TestNew(t *testing.T) { 13 | cs := "EUR" 14 | c, e := appcurrency.New(strings.ToLower(cs)) 15 | if e != nil { 16 | t.Errorf("error during the appcurrency.New func: %v", e) 17 | } 18 | 19 | if c.GetISO4217() != cs { 20 | t.Errorf("appcurrency.New func returns a currency ISO4217 incorrect: got: %v want %v", c.GetISO4217(), cs) 21 | } 22 | } 23 | 24 | func TestNewWithWrongCurrency(t *testing.T) { 25 | _, e := appcurrency.New("I AM A WRONG CURRENCY") 26 | 27 | if e == nil { 28 | t.Error("No error during appcurrency creation with a wrong ISO4217") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /customer/customer.go: -------------------------------------------------------------------------------- 1 | package appcustomer 2 | 3 | type Customer interface { 4 | GetGatewayReference() string 5 | GetEmail() string 6 | } 7 | 8 | type c struct { 9 | R string `json:"gateway_reference"` // Gateway reference 10 | Email string `json:"email"` 11 | } 12 | 13 | // GetGatewayReference exposes c.R value 14 | func (c *c) GetGatewayReference() string { 15 | return c.R 16 | } 17 | 18 | // GetEmail exposes c.Email value 19 | func (c *c) GetEmail() string { 20 | return c.Email 21 | } 22 | 23 | // New returns a new instance of c 24 | func New(r string, email string) Customer { 25 | return &c{R: r, Email: email} 26 | } 27 | -------------------------------------------------------------------------------- /customer/customer_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package appcustomer_test 4 | 5 | import ( 6 | "testing" 7 | 8 | appcustomer "github.com/lelledaniele/upaygo/customer" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | ref, email := "GATEWAY REFERENCE", "email@email.com" 13 | got := appcustomer.New(ref, email) 14 | 15 | if got.GetGatewayReference() != ref { 16 | t.Errorf("The new customer.gateway_reference is incorrect, got: %v want %v", got.GetGatewayReference(), ref) 17 | } 18 | 19 | if got.GetEmail() != email { 20 | t.Errorf("The new customer.email is incorrect, got: %v want %v", got.GetEmail(), email) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /customer/customerstripe.go: -------------------------------------------------------------------------------- 1 | package appcustomer 2 | 3 | import ( 4 | "errors" 5 | 6 | apperror "github.com/lelledaniele/upaygo/error" 7 | 8 | "github.com/stripe/stripe-go/customer" 9 | 10 | appconfig "github.com/lelledaniele/upaygo/config" 11 | appcurrency "github.com/lelledaniele/upaygo/currency" 12 | 13 | "github.com/stripe/stripe-go" 14 | ) 15 | 16 | func NewStripe(email string, ac appcurrency.Currency) (Customer, error) { 17 | if email == "" || ac == nil { 18 | return nil, errors.New("impossible to create a Stripe customer without required parameters") 19 | } 20 | 21 | sck, e := appconfig.GetStripeAPIConfigByCurrency(ac.GetISO4217()) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | stripe.Key = sck.GetSK() 27 | 28 | params := &stripe.CustomerParams{ 29 | Email: stripe.String(email), 30 | } 31 | cus, e := customer.New(params) 32 | if e != nil { 33 | m, es := apperror.GetStripeErrorMessage(e) 34 | if es == nil { 35 | return nil, errors.New(m) 36 | } 37 | 38 | return nil, e 39 | } 40 | 41 | return &c{ 42 | R: cus.ID, 43 | Email: cus.Email, 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /customer/customerstripe_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package appcustomer_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stripe/stripe-go/customer" 12 | 13 | appcurrency "github.com/lelledaniele/upaygo/currency" 14 | 15 | appconfig "github.com/lelledaniele/upaygo/config" 16 | appcustomer "github.com/lelledaniele/upaygo/customer" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | var fcp string 21 | 22 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 23 | flag.Parse() 24 | 25 | if fcp == "" { 26 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 27 | os.Exit(1) 28 | } 29 | 30 | fc, e := os.Open(fcp) 31 | if e != nil { 32 | fmt.Printf("Impossible to get configuration file: %v\n", e) 33 | os.Exit(1) 34 | } 35 | defer fc.Close() 36 | 37 | e = appconfig.ImportConfig(fc) 38 | if e != nil { 39 | fmt.Printf("Error durring file config import: %v", e) 40 | os.Exit(1) 41 | } 42 | 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestNewStripe(t *testing.T) { 47 | email := "email@email.com" 48 | c, _ := appcurrency.New("EUR") 49 | 50 | got, e := appcustomer.NewStripe(email, c) 51 | if e != nil { 52 | t.Errorf("error during the appcustomer creation with Stripe: %v", e) 53 | } 54 | 55 | if got.GetGatewayReference() == "" { 56 | t.Errorf("The new customer.gateway_reference is empty, got: %v", got.GetGatewayReference()) 57 | } 58 | 59 | if got.GetEmail() != email { 60 | t.Errorf("The new customer.email is incorrect, got: %v want %v", got.GetEmail(), email) 61 | } 62 | 63 | _, _ = customer.Del(got.GetGatewayReference(), nil) 64 | } 65 | -------------------------------------------------------------------------------- /error/rest.go: -------------------------------------------------------------------------------- 1 | package apperror 2 | 3 | type RESTError struct { 4 | M string `json:"error"` 5 | } 6 | 7 | func (e *RESTError) Error() string { 8 | return e.M 9 | } 10 | -------------------------------------------------------------------------------- /error/stripe.go: -------------------------------------------------------------------------------- 1 | package apperror 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/stripe/stripe-go" 7 | ) 8 | 9 | // Stripe returns a built-in error 10 | // stripeError.Error() returns a JSON string with multiple keys 11 | // New intent error examples 12 | // {"code":"resource_missing","status":400,"message":"No such customer: cus_xxx","param":"customer","request_id":"req_8PAgbbyTbIufgS","type":"invalid_request_error"} 13 | // {"status":401,"message":"Invalid API Key provided: sk_xxx","type":"invalid_request_error"} 14 | 15 | // GetStripeErrorMessage extracts the message from the built-in Stripe error 16 | func GetStripeErrorMessage(e error) (string, error) { 17 | var es stripe.Error 18 | 19 | ej := json.Unmarshal([]byte(e.Error()), &es) 20 | if ej != nil { 21 | return "", ej 22 | } 23 | 24 | return es.Msg, nil 25 | } 26 | -------------------------------------------------------------------------------- /error/stripe_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package apperror_test 4 | 5 | import ( 6 | "errors" 7 | "testing" 8 | 9 | apperror "github.com/lelledaniele/upaygo/error" 10 | ) 11 | 12 | func TestGetStripeErrorMessage(t *testing.T) { 13 | es := errors.New("{\"status\":401,\"message\":\"Invalid API Key provided: sk_xxx\",\"type\":\"invalid_request_error\"}") 14 | got, e := apperror.GetStripeErrorMessage(es) 15 | if e != nil { 16 | t.Errorf("impossible to get the error message from Stripe built-in error: %v", e) 17 | } 18 | 19 | if w := "Invalid API Key provided: sk_xxx"; got != w { 20 | t.Errorf("Get the error message from Stripe built-in error is incorrect, got: %v want: %v", got, w) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lelledaniele/upaygo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 7 | github.com/go-chi/chi v4.0.2+incompatible // indirect 8 | github.com/gorilla/mux v1.8.0 9 | github.com/stripe/stripe-go v62.10.0+incompatible 10 | github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e 11 | github.com/swaggo/swag v1.6.7 // indirect 12 | golang.org/x/text v0.3.4 // indirect 13 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 3 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 4 | github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= 5 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 6 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 7 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 8 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 9 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 10 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 11 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 17 | github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= 18 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 19 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 21 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 22 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 23 | github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= 24 | github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 25 | github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= 26 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 27 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 28 | github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= 29 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 30 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 31 | github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= 32 | github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 33 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 34 | github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= 35 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 36 | github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= 37 | github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 38 | github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= 39 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 40 | github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= 41 | github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 42 | github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= 43 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 44 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 45 | github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= 46 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 47 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 48 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 49 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 50 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 51 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 52 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 53 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 54 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 55 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 56 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 57 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 58 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 59 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 60 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 61 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 62 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 63 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= 64 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 65 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 66 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= 67 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 68 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 69 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 70 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 73 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 74 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 75 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 76 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 78 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 79 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 80 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 81 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 82 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 83 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 84 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 85 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 86 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 87 | github.com/stripe/stripe-go v61.27.0+incompatible h1:p0HgsaOYus3VbiUS26GgnZ/wa3ipIFa+NKoX74mVsHA= 88 | github.com/stripe/stripe-go v61.27.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= 89 | github.com/stripe/stripe-go v62.9.0+incompatible h1:O9SBIruVc8uMdyxSVMSXiKZRujnn45bpn8eAyn8BfcQ= 90 | github.com/stripe/stripe-go v62.9.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= 91 | github.com/stripe/stripe-go v62.10.0+incompatible h1:NA0ZdyXlogeRY1YRmx3xL3/4r3vQjAU3Y175+IwFoYo= 92 | github.com/stripe/stripe-go v62.10.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= 93 | github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= 94 | github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= 95 | github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= 96 | github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e h1:m5sYJ43teIUlESuKRFQRRm7kqi6ExiYwVKfoXNuRgHU= 97 | github.com/swaggo/http-swagger v0.0.0-20190614090009-c2865af9083e/go.mod h1:eycbshptIv+tqTMlLEaGC2noPNcetbrcYEelLafrIDI= 98 | github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= 99 | github.com/swaggo/swag v1.6.2 h1:WQMAtT/FmMBb7g0rAuHDhG3vvdtHKJ3WZ+Ssb0p4Y6E= 100 | github.com/swaggo/swag v1.6.2/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo= 101 | github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s= 102 | github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc= 103 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 104 | github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= 105 | github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 106 | github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= 107 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 108 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 109 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 110 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 111 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 112 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 114 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 115 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 116 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 117 | golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 118 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 119 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 120 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 121 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 122 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 125 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 131 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 132 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 133 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 134 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 135 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 136 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 137 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 138 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 139 | golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 h1:fTfk6GjmihJbK0mSUFgPPgYpsdmApQ86Mcd4GuKax9U= 140 | golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 141 | golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 142 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 143 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b h1:s3JPx1wx+Kydg1rKI8uzmNMeYA+rr9m5kiLGVqEBufM= 144 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 145 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 146 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 147 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 148 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 149 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 150 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 151 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 152 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 153 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 154 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 155 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | appconfig "github.com/lelledaniele/upaygo/config" 11 | apprestintentcancel "github.com/lelledaniele/upaygo/controller/rest/intent/cancel" 12 | apprestintentcapture "github.com/lelledaniele/upaygo/controller/rest/intent/capture" 13 | apprestintentconfirm "github.com/lelledaniele/upaygo/controller/rest/intent/confirm" 14 | apprestintentcreate "github.com/lelledaniele/upaygo/controller/rest/intent/create" 15 | apprestintentget "github.com/lelledaniele/upaygo/controller/rest/intent/get" 16 | 17 | httpSwagger "github.com/swaggo/http-swagger" 18 | 19 | "github.com/gorilla/mux" 20 | _ "github.com/lelledaniele/upaygo/docs" 21 | ) 22 | 23 | var configFile string 24 | 25 | func init() { 26 | flag.StringVar(&configFile, "config", "", "Path for config file") 27 | flag.Parse() 28 | 29 | if configFile == "" { 30 | log.Fatal("Flag 'config' for configuration file path is required") 31 | } 32 | } 33 | 34 | // @title uPayment in GO 35 | // @version 1.0.0 36 | // @description Microservice to manage payment 37 | // @license.name MIT 38 | func main() { 39 | fc, e := os.Open(configFile) 40 | if e != nil { 41 | log.Fatal(fmt.Sprintf("Impossible to open configuration file: %v\n", e)) 42 | } 43 | defer fc.Close() 44 | 45 | e = appconfig.ImportConfig(fc) 46 | if e != nil { 47 | log.Fatal(fmt.Sprintf("Error durring file config import: %v\n", e)) 48 | } 49 | 50 | s := appconfig.GetServerConfig() 51 | 52 | r := mux.NewRouter() 53 | r.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler) 54 | r.HandleFunc(apprestintentget.URL, apprestintentget.Handler).Methods(apprestintentget.Method) 55 | r.HandleFunc(apprestintentcreate.URL, apprestintentcreate.Handler).Methods(apprestintentcreate.Method) 56 | r.HandleFunc(apprestintentconfirm.URL, apprestintentconfirm.Handler).Methods(apprestintentconfirm.Method) 57 | r.HandleFunc(apprestintentcapture.URL, apprestintentcapture.Handler).Methods(apprestintentcapture.Method) 58 | r.HandleFunc(apprestintentcancel.URL, apprestintentcancel.Handler).Methods(apprestintentcancel.Method) 59 | 60 | log.Fatal(http.ListenAndServe(":"+s.GetPort(), r)) 61 | } 62 | -------------------------------------------------------------------------------- /payment/intent/cancel/intentcancel.go: -------------------------------------------------------------------------------- 1 | package apppaymentintentcancel 2 | 3 | import ( 4 | "errors" 5 | 6 | appconfig "github.com/lelledaniele/upaygo/config" 7 | appcurrency "github.com/lelledaniele/upaygo/currency" 8 | apperror "github.com/lelledaniele/upaygo/error" 9 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 10 | 11 | "github.com/stripe/stripe-go" 12 | "github.com/stripe/stripe-go/paymentintent" 13 | ) 14 | 15 | // Cancel gets the intent id from c Stripe account and cancel it 16 | func Cancel(id string, c appcurrency.Currency) (apppaymentintent.Intent, error) { 17 | if id == "" || c == nil { 18 | return nil, errors.New("impossible to cancel the payment intent without required parameters") 19 | } 20 | 21 | sck, e := appconfig.GetStripeAPIConfigByCurrency(c.GetISO4217()) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | stripe.Key = sck.GetSK() 27 | 28 | intent, e := paymentintent.Cancel(id, nil) 29 | if e != nil { 30 | m, es := apperror.GetStripeErrorMessage(e) 31 | if es == nil { 32 | return nil, errors.New(m) 33 | } 34 | 35 | return nil, e 36 | } 37 | 38 | return apppaymentintent.FromStripeToAppIntent(*intent), nil 39 | } 40 | -------------------------------------------------------------------------------- /payment/intent/cancel/intentcancel_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apppaymentintentcancel_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | appconfig "github.com/lelledaniele/upaygo/config" 12 | appcurrency "github.com/lelledaniele/upaygo/currency" 13 | apppaymentintentcancel "github.com/lelledaniele/upaygo/payment/intent/cancel" 14 | 15 | "github.com/stripe/stripe-go" 16 | "github.com/stripe/stripe-go/paymentintent" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | var fcp string 21 | 22 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 23 | flag.Parse() 24 | 25 | if fcp == "" { 26 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 27 | os.Exit(1) 28 | } 29 | 30 | fc, e := os.Open(fcp) 31 | if e != nil { 32 | fmt.Printf("Impossible to get configuration file: %v\n", e) 33 | os.Exit(1) 34 | } 35 | defer fc.Close() 36 | 37 | e = appconfig.ImportConfig(fc) 38 | if e != nil { 39 | fmt.Printf("Error durring file config import: %v", e) 40 | os.Exit(1) 41 | } 42 | 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestCancel(t *testing.T) { 47 | cur, _ := appcurrency.New("EUR") 48 | am := int64(2088) 49 | pip := &stripe.PaymentIntentParams{ 50 | Amount: stripe.Int64(am), 51 | Currency: stripe.String(cur.GetISO4217()), 52 | ConfirmationMethod: stripe.String("automatic"), 53 | Confirm: stripe.Bool(true), 54 | CaptureMethod: stripe.String("manual"), 55 | PaymentMethod: stripe.String("pm_card_visa"), 56 | } 57 | 58 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 59 | stripe.Key = sck.GetSK() 60 | 61 | intent, e := paymentintent.New(pip) 62 | if e != nil { 63 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 64 | } 65 | 66 | appintent, e := apppaymentintentcancel.Cancel(intent.ID, cur) 67 | if e != nil { 68 | t.Errorf("impossible to cancel %v payment intent: %v", intent.ID, e) 69 | } 70 | 71 | if !appintent.IsCanceled() { 72 | t.Error("intent cancel is incorrect, got an intent that is not canceled") 73 | } 74 | } 75 | 76 | func TestCancelWithSCACard(t *testing.T) { 77 | cur, _ := appcurrency.New("EUR") 78 | am := int64(2088) 79 | pip := &stripe.PaymentIntentParams{ 80 | Amount: stripe.Int64(am), 81 | Currency: stripe.String(cur.GetISO4217()), 82 | ConfirmationMethod: stripe.String("automatic"), 83 | Confirm: stripe.Bool(true), 84 | CaptureMethod: stripe.String("manual"), 85 | PaymentMethod: stripe.String("pm_card_authenticationRequiredOnSetup"), 86 | } 87 | 88 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 89 | stripe.Key = sck.GetSK() 90 | 91 | intent, e := paymentintent.New(pip) 92 | if e != nil { 93 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 94 | } 95 | 96 | appintent, e := apppaymentintentcancel.Cancel(intent.ID, cur) 97 | if e != nil { 98 | t.Errorf("impossible to cancel %v payment intent: %v", intent.ID, e) 99 | } 100 | 101 | if !appintent.IsCanceled() { 102 | t.Error("intent cancel is incorrect, got an intent that is not canceled") 103 | } 104 | } 105 | 106 | func TestCancelNonConfirmedIntent(t *testing.T) { 107 | cur, _ := appcurrency.New("EUR") 108 | am := int64(2088) 109 | pip := &stripe.PaymentIntentParams{ 110 | Amount: stripe.Int64(am), 111 | Currency: stripe.String(cur.GetISO4217()), 112 | ConfirmationMethod: stripe.String("manual"), 113 | Confirm: stripe.Bool(false), 114 | CaptureMethod: stripe.String("manual"), 115 | PaymentMethod: stripe.String("pm_card_authenticationRequiredOnSetup"), 116 | } 117 | 118 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 119 | stripe.Key = sck.GetSK() 120 | 121 | intent, e := paymentintent.New(pip) 122 | if e != nil { 123 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 124 | } 125 | 126 | appintent, e := apppaymentintentcancel.Cancel(intent.ID, cur) 127 | if e != nil { 128 | t.Errorf("impossible to cancel %v payment intent: %v", intent.ID, e) 129 | } 130 | 131 | if !appintent.IsCanceled() { 132 | t.Error("intent cancel is incorrect, got an intent that is not canceled") 133 | } 134 | } 135 | 136 | func TestCancelWithoutID(t *testing.T) { 137 | cur, _ := appcurrency.New("EUR") 138 | _, e := apppaymentintentcancel.Cancel("", cur) 139 | if e == nil { 140 | t.Error("expecting an error if cancel an intent without ID") 141 | } 142 | } 143 | 144 | func TestCancelWithoutCurrency(t *testing.T) { 145 | _, e := apppaymentintentcancel.Cancel("in_xxx", nil) 146 | if e == nil { 147 | t.Error("expecting an error if cancel an intent without currency") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /payment/intent/capture/intentcapture.go: -------------------------------------------------------------------------------- 1 | package apppaymentintentcapture 2 | 3 | import ( 4 | "errors" 5 | 6 | appconfig "github.com/lelledaniele/upaygo/config" 7 | appcurrency "github.com/lelledaniele/upaygo/currency" 8 | apperror "github.com/lelledaniele/upaygo/error" 9 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 10 | 11 | "github.com/stripe/stripe-go" 12 | "github.com/stripe/stripe-go/paymentintent" 13 | ) 14 | 15 | // Capture gets the intent id from c Stripe account and capture it 16 | func Capture(id string, c appcurrency.Currency) (apppaymentintent.Intent, error) { 17 | if id == "" || c == nil { 18 | return nil, errors.New("impossible to capture the payment intent without required parameters") 19 | } 20 | 21 | sck, e := appconfig.GetStripeAPIConfigByCurrency(c.GetISO4217()) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | stripe.Key = sck.GetSK() 27 | 28 | intent, e := paymentintent.Capture(id, nil) 29 | if e != nil { 30 | m, es := apperror.GetStripeErrorMessage(e) 31 | if es == nil { 32 | return nil, errors.New(m) 33 | } 34 | 35 | return nil, e 36 | } 37 | 38 | return apppaymentintent.FromStripeToAppIntent(*intent), nil 39 | } 40 | -------------------------------------------------------------------------------- /payment/intent/capture/intentcapture_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apppaymentintentcapture_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | appconfig "github.com/lelledaniele/upaygo/config" 12 | appcurrency "github.com/lelledaniele/upaygo/currency" 13 | apppaymentintentcapture "github.com/lelledaniele/upaygo/payment/intent/capture" 14 | 15 | "github.com/stripe/stripe-go" 16 | "github.com/stripe/stripe-go/paymentintent" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | var fcp string 21 | 22 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 23 | flag.Parse() 24 | 25 | if fcp == "" { 26 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 27 | os.Exit(1) 28 | } 29 | 30 | fc, e := os.Open(fcp) 31 | if e != nil { 32 | fmt.Printf("Impossible to get configuration file: %v\n", e) 33 | os.Exit(1) 34 | } 35 | defer fc.Close() 36 | 37 | e = appconfig.ImportConfig(fc) 38 | if e != nil { 39 | fmt.Printf("Error durring file config import: %v", e) 40 | os.Exit(1) 41 | } 42 | 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestCapture(t *testing.T) { 47 | cur, _ := appcurrency.New("EUR") 48 | am := int64(2088) 49 | pip := &stripe.PaymentIntentParams{ 50 | Amount: stripe.Int64(am), 51 | Currency: stripe.String(cur.GetISO4217()), 52 | ConfirmationMethod: stripe.String("automatic"), 53 | Confirm: stripe.Bool(true), 54 | CaptureMethod: stripe.String("manual"), 55 | PaymentMethod: stripe.String("pm_card_visa"), 56 | } 57 | 58 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 59 | stripe.Key = sck.GetSK() 60 | 61 | intent, e := paymentintent.New(pip) 62 | if e != nil { 63 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 64 | } 65 | 66 | appintent, e := apppaymentintentcapture.Capture(intent.ID, cur) 67 | if e != nil { 68 | t.Errorf("impossible to capture %v payment intent: %v", intent.ID, e) 69 | } 70 | 71 | if !appintent.IsSucceeded() { 72 | t.Error("intent capture is incorrect, got an intent that is not succeeded") 73 | } 74 | 75 | _, _ = paymentintent.Cancel(appintent.GetGatewayReference(), nil) 76 | } 77 | 78 | func TestCaptureWithSCACard(t *testing.T) { 79 | cur, _ := appcurrency.New("EUR") 80 | am := int64(2088) 81 | pip := &stripe.PaymentIntentParams{ 82 | Amount: stripe.Int64(am), 83 | Currency: stripe.String(cur.GetISO4217()), 84 | ConfirmationMethod: stripe.String("automatic"), 85 | Confirm: stripe.Bool(true), 86 | CaptureMethod: stripe.String("manual"), 87 | PaymentMethod: stripe.String("pm_card_authenticationRequiredOnSetup"), 88 | } 89 | 90 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 91 | stripe.Key = sck.GetSK() 92 | 93 | intent, e := paymentintent.New(pip) 94 | if e != nil { 95 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 96 | } 97 | 98 | _, e = apppaymentintentcapture.Capture(intent.ID, cur) 99 | if e == nil { 100 | t.Errorf("intent %v should not be captured as it should have status requires_action", intent.ID) 101 | } 102 | 103 | _, _ = paymentintent.Cancel(intent.ID, nil) 104 | } 105 | 106 | func TestCaptureWithoutID(t *testing.T) { 107 | cur, _ := appcurrency.New("EUR") 108 | _, e := apppaymentintentcapture.Capture("", cur) 109 | if e == nil { 110 | t.Error("expecting an error if capture an intent without ID") 111 | } 112 | } 113 | 114 | func TestCaptureWithoutCurrency(t *testing.T) { 115 | _, e := apppaymentintentcapture.Capture("in_xxx", nil) 116 | if e == nil { 117 | t.Error("expecting an error if capture an intent without currency") 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /payment/intent/confirm/intentconfirm.go: -------------------------------------------------------------------------------- 1 | package apppaymentintentconfirm 2 | 3 | import ( 4 | "errors" 5 | 6 | appconfig "github.com/lelledaniele/upaygo/config" 7 | appcurrency "github.com/lelledaniele/upaygo/currency" 8 | apperror "github.com/lelledaniele/upaygo/error" 9 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 10 | 11 | "github.com/stripe/stripe-go" 12 | "github.com/stripe/stripe-go/paymentintent" 13 | ) 14 | 15 | // Confirm gets the intent id from c Stripe account and confirm it 16 | func Confirm(id string, c appcurrency.Currency) (apppaymentintent.Intent, error) { 17 | if id == "" || c == nil { 18 | return nil, errors.New("impossible to confirm the payment intent without required parameters") 19 | } 20 | 21 | sck, e := appconfig.GetStripeAPIConfigByCurrency(c.GetISO4217()) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | stripe.Key = sck.GetSK() 27 | 28 | intent, e := paymentintent.Confirm(id, nil) 29 | if e != nil { 30 | m, es := apperror.GetStripeErrorMessage(e) 31 | if es == nil { 32 | return nil, errors.New(m) 33 | } 34 | 35 | return nil, e 36 | } 37 | 38 | return apppaymentintent.FromStripeToAppIntent(*intent), nil 39 | } 40 | -------------------------------------------------------------------------------- /payment/intent/confirm/intentconfirm_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apppaymentintentconfirm_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | appconfig "github.com/lelledaniele/upaygo/config" 12 | appcurrency "github.com/lelledaniele/upaygo/currency" 13 | apppaymentintentconfirm "github.com/lelledaniele/upaygo/payment/intent/confirm" 14 | 15 | "github.com/stripe/stripe-go" 16 | "github.com/stripe/stripe-go/paymentintent" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | var fcp string 21 | 22 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 23 | flag.Parse() 24 | 25 | if fcp == "" { 26 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 27 | os.Exit(1) 28 | } 29 | 30 | fc, e := os.Open(fcp) 31 | if e != nil { 32 | fmt.Printf("Impossible to get configuration file: %v\n", e) 33 | os.Exit(1) 34 | } 35 | defer fc.Close() 36 | 37 | e = appconfig.ImportConfig(fc) 38 | if e != nil { 39 | fmt.Printf("Error durring file config import: %v", e) 40 | os.Exit(1) 41 | } 42 | 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestConfirm(t *testing.T) { 47 | cur, _ := appcurrency.New("EUR") 48 | am := int64(2088) 49 | pip := &stripe.PaymentIntentParams{ 50 | Amount: stripe.Int64(am), 51 | Currency: stripe.String(cur.GetISO4217()), 52 | ConfirmationMethod: stripe.String("manual"), 53 | PaymentMethod: stripe.String("pm_card_visa"), 54 | } 55 | 56 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 57 | stripe.Key = sck.GetSK() 58 | 59 | intent, e := paymentintent.New(pip) 60 | if e != nil { 61 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 62 | } 63 | 64 | appintent, e := apppaymentintentconfirm.Confirm(intent.ID, cur) 65 | if e != nil { 66 | t.Errorf("impossible to confirm %v payment intent: %v", intent.ID, e) 67 | } 68 | 69 | if appintent.RequiresConfirmation() { 70 | t.Error("intent confirmation is incorrect, got an intent that requires confirmation") 71 | } 72 | 73 | _, _ = paymentintent.Cancel(appintent.GetGatewayReference(), nil) 74 | } 75 | 76 | func TestConfirmWithoutID(t *testing.T) { 77 | cur, _ := appcurrency.New("EUR") 78 | _, e := apppaymentintentconfirm.Confirm("", cur) 79 | if e == nil { 80 | t.Error("expecting an error if confirm an intent without ID") 81 | } 82 | } 83 | 84 | func TestConfirmWithoutCurrency(t *testing.T) { 85 | _, e := apppaymentintentconfirm.Confirm("in_xxx", nil) 86 | if e == nil { 87 | t.Error("expecting an error if confirm an intent without currency") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /payment/intent/create/intentcreate.go: -------------------------------------------------------------------------------- 1 | package apppaymentintentcreate 2 | 3 | import ( 4 | "errors" 5 | 6 | appamount "github.com/lelledaniele/upaygo/amount" 7 | appconfig "github.com/lelledaniele/upaygo/config" 8 | appcustomer "github.com/lelledaniele/upaygo/customer" 9 | apperror "github.com/lelledaniele/upaygo/error" 10 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 11 | apppaymentsource "github.com/lelledaniele/upaygo/payment/source" 12 | 13 | "github.com/stripe/stripe-go" 14 | "github.com/stripe/stripe-go/paymentintent" 15 | ) 16 | 17 | // Create creates an intent in Stripe and returns it as an instance of Intent 18 | func Create(a appamount.Amount, p apppaymentsource.Source, c appcustomer.Customer) (apppaymentintent.Intent, error) { 19 | if a == nil || p == nil { 20 | return nil, errors.New("impossible to create a payment intent without required parameters") 21 | } 22 | 23 | sck, e := appconfig.GetStripeAPIConfigByCurrency(a.GetCurrency().GetISO4217()) 24 | if e != nil { 25 | return nil, e 26 | } 27 | 28 | stripe.Key = sck.GetSK() 29 | 30 | ic := &stripe.PaymentIntentParams{ 31 | Amount: stripe.Int64(int64(a.GetAmount())), 32 | Currency: stripe.String(a.GetCurrency().GetISO4217()), 33 | PaymentMethod: stripe.String(p.GetGatewayReference()), 34 | SetupFutureUsage: stripe.String("off_session"), 35 | ConfirmationMethod: stripe.String("manual"), 36 | CaptureMethod: stripe.String("manual"), 37 | } 38 | 39 | if c != nil { 40 | ic.Customer = stripe.String(c.GetGatewayReference()) 41 | ic.SavePaymentMethod = stripe.Bool(true) 42 | } 43 | 44 | intent, e := paymentintent.New(ic) 45 | if e != nil { 46 | m, es := apperror.GetStripeErrorMessage(e) 47 | if es == nil { 48 | return nil, errors.New(m) 49 | } 50 | 51 | return nil, e 52 | } 53 | 54 | return apppaymentintent.FromStripeToAppIntent(*intent), nil 55 | } 56 | -------------------------------------------------------------------------------- /payment/intent/create/intentcreate_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apppaymentintentcreate_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | appamount "github.com/lelledaniele/upaygo/amount" 12 | appconfig "github.com/lelledaniele/upaygo/config" 13 | appcurrency "github.com/lelledaniele/upaygo/currency" 14 | appcustomer "github.com/lelledaniele/upaygo/customer" 15 | apppaymentintentcreate "github.com/lelledaniele/upaygo/payment/intent/create" 16 | apppaymentsource "github.com/lelledaniele/upaygo/payment/source" 17 | 18 | "github.com/stripe/stripe-go/customer" 19 | "github.com/stripe/stripe-go/paymentintent" 20 | ) 21 | 22 | func TestMain(m *testing.M) { 23 | var fcp string 24 | 25 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 26 | flag.Parse() 27 | 28 | if fcp == "" { 29 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 30 | os.Exit(1) 31 | } 32 | 33 | fc, e := os.Open(fcp) 34 | if e != nil { 35 | fmt.Printf("Impossible to get configuration file: %v\n", e) 36 | os.Exit(1) 37 | } 38 | defer fc.Close() 39 | 40 | e = appconfig.ImportConfig(fc) 41 | if e != nil { 42 | fmt.Printf("Error durring file config import: %v", e) 43 | os.Exit(1) 44 | } 45 | 46 | os.Exit(m.Run()) 47 | } 48 | 49 | func TestCreate(t *testing.T) { 50 | cur, _ := appcurrency.New("EUR") 51 | am := 2045 52 | cus, _ := appcustomer.NewStripe("email@email.com", cur) 53 | a, _ := appamount.New(am, cur.GetISO4217()) 54 | ps := apppaymentsource.New("pm_card_visa") 55 | 56 | pi, e := apppaymentintentcreate.Create(a, ps, cus) 57 | if e != nil { 58 | t.Errorf("impossible to create a new payment intent: %v", e) 59 | } 60 | 61 | if pi.GetGatewayReference() == "" { 62 | t.Error("intent new is incorrect, created an intent without gateway reference") 63 | } 64 | 65 | if pi.GetConfirmationMethod() != "manual" { 66 | t.Errorf("intent should have confirmation method set to manual, got %v", pi.GetConfirmationMethod()) 67 | } 68 | 69 | if pi.GetNextAction() != "" { 70 | t.Errorf("intent should not have next action as it is not confirmed, got %v", pi.GetNextAction()) 71 | } 72 | 73 | if !pi.IsOffSession() { 74 | t.Error("intent should be enable for off session payment") 75 | } 76 | 77 | if pi.GetCreatedAt().Unix() == 0 { 78 | t.Error("intent should have a create timestamp") 79 | } 80 | 81 | if pi.GetCustomer().GetGatewayReference() != cus.GetGatewayReference() { 82 | t.Errorf("intent customer is incorrect, got: %v want %v", pi.GetCustomer(), cus) 83 | } 84 | 85 | if pi.GetSource().GetGatewayReference() == "" { 86 | t.Error("intent source is empty") 87 | } 88 | 89 | if !pi.GetAmount().Equal(a) { 90 | t.Errorf("intent amount is incorrect, got: %v want %v", pi.GetAmount(), a) 91 | } 92 | 93 | if pi.IsCanceled() { 94 | t.Error("a new intent should not be cancelled") 95 | } 96 | 97 | if pi.IsSucceeded() { 98 | t.Error("a new intent should not be succeeded") 99 | } 100 | 101 | if pi.RequiresCapture() { 102 | t.Error("a new intent should not require capture") 103 | } 104 | 105 | if !pi.RequiresConfirmation() { 106 | t.Error("a new intent should require confirmation") 107 | } 108 | 109 | _, _ = paymentintent.Cancel(pi.GetGatewayReference(), nil) 110 | _, _ = customer.Del(cus.GetGatewayReference(), nil) 111 | } 112 | 113 | func TestCreateWithoutCustomer(t *testing.T) { 114 | cur, _ := appcurrency.New("EUR") 115 | am := 2045 116 | a, _ := appamount.New(am, cur.GetISO4217()) 117 | ps := apppaymentsource.New("pm_card_visa") 118 | 119 | pi, e := apppaymentintentcreate.Create(a, ps, nil) 120 | if e != nil { 121 | t.Errorf("impossible to create a new payment intent: %v", e) 122 | } 123 | 124 | if pi.GetCustomer() != nil { 125 | t.Errorf("intent customer should be blank, got: %v", pi.GetCustomer()) 126 | } 127 | 128 | _, _ = paymentintent.Cancel(pi.GetGatewayReference(), nil) 129 | } 130 | 131 | func TestCreateWithoutAmount(t *testing.T) { 132 | ps := apppaymentsource.New("pm_card_visa") 133 | 134 | _, e := apppaymentintentcreate.Create(nil, ps, nil) 135 | if e == nil { 136 | t.Error("intent without amount created") 137 | } 138 | } 139 | 140 | func TestCreateWithoutPaymentSource(t *testing.T) { 141 | am := 2045 142 | a, _ := appamount.New(am, "EUR") 143 | 144 | _, e := apppaymentintentcreate.Create(a, nil, nil) 145 | if e == nil { 146 | t.Error("intent without amount created") 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /payment/intent/get/intentget.go: -------------------------------------------------------------------------------- 1 | package apppaymentintentget 2 | 3 | import ( 4 | "errors" 5 | 6 | appconfig "github.com/lelledaniele/upaygo/config" 7 | appcurrency "github.com/lelledaniele/upaygo/currency" 8 | apperror "github.com/lelledaniele/upaygo/error" 9 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 10 | 11 | "github.com/stripe/stripe-go" 12 | "github.com/stripe/stripe-go/paymentintent" 13 | ) 14 | 15 | // Get gets the gf intent from c Stripe account and returns it as an instance of i 16 | func Get(gf string, c appcurrency.Currency) (apppaymentintent.Intent, error) { 17 | if gf == "" || c == nil { 18 | return nil, errors.New("impossible to get the payment intent without required parameters") 19 | } 20 | 21 | sck, e := appconfig.GetStripeAPIConfigByCurrency(c.GetISO4217()) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | stripe.Key = sck.GetSK() 27 | 28 | intent, e := paymentintent.Get(gf, nil) 29 | if e != nil { 30 | m, es := apperror.GetStripeErrorMessage(e) 31 | if es == nil { 32 | return nil, errors.New(m) 33 | } 34 | 35 | return nil, e 36 | } 37 | 38 | return apppaymentintent.FromStripeToAppIntent(*intent), nil 39 | } 40 | -------------------------------------------------------------------------------- /payment/intent/get/intentget_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build stripe 2 | 3 | package apppaymentintentget_test 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | appconfig "github.com/lelledaniele/upaygo/config" 12 | appcurrency "github.com/lelledaniele/upaygo/currency" 13 | apppaymentintentget "github.com/lelledaniele/upaygo/payment/intent/get" 14 | 15 | "github.com/stripe/stripe-go" 16 | "github.com/stripe/stripe-go/paymentintent" 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | var fcp string 21 | 22 | flag.StringVar(&fcp, "config", "", "Provide config file as an absolute path") 23 | flag.Parse() 24 | 25 | if fcp == "" { 26 | fmt.Print("Integration Stripe test needs the config file absolute path as flag -config") 27 | os.Exit(1) 28 | } 29 | 30 | fc, e := os.Open(fcp) 31 | if e != nil { 32 | fmt.Printf("Impossible to get configuration file: %v\n", e) 33 | os.Exit(1) 34 | } 35 | defer fc.Close() 36 | 37 | e = appconfig.ImportConfig(fc) 38 | if e != nil { 39 | fmt.Printf("Error durring file config import: %v", e) 40 | os.Exit(1) 41 | } 42 | 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestGet(t *testing.T) { 47 | cur, _ := appcurrency.New("EUR") 48 | am := int64(2088) 49 | pip := &stripe.PaymentIntentParams{ 50 | Amount: stripe.Int64(am), 51 | Currency: stripe.String(cur.GetISO4217()), 52 | } 53 | 54 | sck, _ := appconfig.GetStripeAPIConfigByCurrency(cur.GetISO4217()) 55 | stripe.Key = sck.GetSK() 56 | 57 | intent, e := paymentintent.New(pip) 58 | if e != nil { 59 | t.Errorf("impossible to create a new payment intent for testing: %v", e) 60 | } 61 | 62 | appintent, e := apppaymentintentget.Get(intent.ID, cur) 63 | if e != nil { 64 | t.Errorf("impossible to get %v payment intent: %v", intent.ID, e) 65 | } 66 | 67 | if appintent.GetGatewayReference() != intent.ID { 68 | t.Errorf("intent get is incorrect, got an intent with different ID. Got: %v want: %v", appintent.GetGatewayReference(), intent.ID) 69 | } 70 | 71 | _, _ = paymentintent.Cancel(appintent.GetGatewayReference(), nil) 72 | } 73 | 74 | func TestGetWithoutID(t *testing.T) { 75 | cur, _ := appcurrency.New("EUR") 76 | _, e := apppaymentintentget.Get("", cur) 77 | if e == nil { 78 | t.Error("expecting an error if get an intent without ID") 79 | } 80 | } 81 | 82 | func TestGetWithoutCurrency(t *testing.T) { 83 | _, e := apppaymentintentget.Get("in_xxx", nil) 84 | if e == nil { 85 | t.Error("expecting an error if get an intent without currency") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /payment/intent/intent.go: -------------------------------------------------------------------------------- 1 | package apppaymentintent 2 | 3 | import ( 4 | "time" 5 | 6 | appamount "github.com/lelledaniele/upaygo/amount" 7 | appcustomer "github.com/lelledaniele/upaygo/customer" 8 | apppaymentsource "github.com/lelledaniele/upaygo/payment/source" 9 | ) 10 | 11 | type Intent interface { 12 | GetGatewayReference() string 13 | GetConfirmationMethod() string 14 | GetNextAction() string 15 | IsOffSession() bool 16 | GetCreatedAt() time.Time 17 | GetCustomer() appcustomer.Customer 18 | GetSource() apppaymentsource.Source 19 | GetAmount() appamount.Amount 20 | 21 | // From the status 22 | IsCanceled() bool 23 | IsSucceeded() bool 24 | RequiresCapture() bool 25 | RequiresConfirmation() bool 26 | } 27 | 28 | type i struct { 29 | R string `json:"gateway_reference"` // Gateway reference 30 | CM string `json:"confirmation_method"` // Confirmation method 31 | NA string `json:"next_action"` // Next Action 32 | OFFS bool `json:"off_session"` // Off session 33 | CT time.Time `json:"created_at"` // Create At 34 | 35 | C appcustomer.Customer `json:"customer"` 36 | PS apppaymentsource.Source `json:"payment_source"` 37 | A appamount.Amount `json:"amount"` 38 | S status `json:"status"` 39 | } 40 | 41 | // GetGatewayReference exposes i.R value 42 | func (i *i) GetGatewayReference() string { 43 | return i.R 44 | } 45 | 46 | // GetGatewayReference exposes i.CM value 47 | func (i *i) GetConfirmationMethod() string { 48 | return i.CM 49 | } 50 | 51 | // GetGatewayReference exposes i.NA value 52 | func (i *i) GetNextAction() string { 53 | return i.NA 54 | } 55 | 56 | // GetGatewayReference exposes i.OFFS value 57 | func (i *i) IsOffSession() bool { 58 | return i.OFFS 59 | } 60 | 61 | // GetGatewayReference exposes i.CT. value 62 | func (i *i) GetCreatedAt() time.Time { 63 | return i.CT 64 | } 65 | 66 | // GetGatewayReference exposes i.C value 67 | func (i *i) GetCustomer() appcustomer.Customer { 68 | return i.C 69 | } 70 | 71 | // GetGatewayReference exposes i.PS value 72 | func (i *i) GetSource() apppaymentsource.Source { 73 | return i.PS 74 | } 75 | 76 | // GetGatewayReference exposes i.A value 77 | func (i *i) GetAmount() appamount.Amount { 78 | return i.A 79 | } 80 | 81 | // IsCanceled if status.s is canceled 82 | func (i *i) IsCanceled() bool { 83 | return i.S.S == canceled 84 | } 85 | 86 | // IsSucceeded if status.s is succeeded 87 | func (i *i) IsSucceeded() bool { 88 | return i.S.S == succeeded 89 | } 90 | 91 | // RequiresCapture if status.s is requirescapture 92 | func (i *i) RequiresCapture() bool { 93 | return i.S.S == requirescapture 94 | } 95 | 96 | // RequiresConfirmation if status.s is requiresconfirmation 97 | func (i *i) RequiresConfirmation() bool { 98 | return i.S.S == requiresconfirmation 99 | } 100 | -------------------------------------------------------------------------------- /payment/intent/status.go: -------------------------------------------------------------------------------- 1 | package apppaymentintent 2 | 3 | const ( 4 | requiresconfirmation = "requires_confirmation" 5 | requirescapture = "requires_capture" 6 | canceled = "canceled" 7 | succeeded = "succeeded" 8 | ) 9 | 10 | type status struct { 11 | S string `json:"gateway_reference"` 12 | } 13 | -------------------------------------------------------------------------------- /payment/intent/stripeconv.go: -------------------------------------------------------------------------------- 1 | package apppaymentintent 2 | 3 | import ( 4 | "time" 5 | 6 | appamount "github.com/lelledaniele/upaygo/amount" 7 | appcustomer "github.com/lelledaniele/upaygo/customer" 8 | apppaymentsource "github.com/lelledaniele/upaygo/payment/source" 9 | 10 | "github.com/stripe/stripe-go" 11 | ) 12 | 13 | func FromStripeToAppIntent(intent stripe.PaymentIntent) Intent { 14 | var nat string 15 | na := intent.NextAction 16 | if na != nil { 17 | nat = string(na.Type) 18 | } 19 | 20 | var ps apppaymentsource.Source 21 | if intent.PaymentMethod != nil { 22 | ps = apppaymentsource.New(intent.PaymentMethod.ID) 23 | } 24 | 25 | a, _ := appamount.New(int(intent.Amount), intent.Currency) 26 | 27 | var cus appcustomer.Customer 28 | if intent.Customer != nil { 29 | cus = appcustomer.New(intent.Customer.ID, intent.Customer.Email) 30 | } 31 | 32 | return &i{ 33 | R: intent.ID, 34 | CM: string(intent.ConfirmationMethod), 35 | NA: nat, 36 | OFFS: intent.SetupFutureUsage == "off_session", 37 | CT: time.Unix(intent.Created, 0), 38 | C: cus, 39 | PS: ps, 40 | A: a, 41 | S: status{S: string(intent.Status)}, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /payment/intent/stripeconv_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package apppaymentintent_test 4 | 5 | import ( 6 | "testing" 7 | 8 | apppaymentintent "github.com/lelledaniele/upaygo/payment/intent" 9 | 10 | "github.com/stripe/stripe-go" 11 | ) 12 | 13 | func TestFromStripeToAppIntent(t *testing.T) { 14 | pmID := "card_xxx" 15 | pm := stripe.PaymentMethod{ 16 | ID: pmID, 17 | } 18 | 19 | nat := "stripe_sdk" 20 | na := stripe.PaymentIntentNextAction{ 21 | Type: stripe.PaymentIntentNextActionType(nat), 22 | } 23 | 24 | cusID := "cus_xxx" 25 | cus := stripe.Customer{ 26 | ID: cusID, 27 | } 28 | 29 | intent := stripe.PaymentIntent{ 30 | ID: "intent_xxx", 31 | NextAction: &na, 32 | PaymentMethod: &pm, 33 | Customer: &cus, 34 | } 35 | 36 | appintent := apppaymentintent.FromStripeToAppIntent(intent) 37 | 38 | if appintent.GetNextAction() != nat { 39 | t.Errorf("incorrect next_action intent domain conversion, got: %v want: %v", appintent.GetNextAction(), nat) 40 | } 41 | 42 | if appintent.GetSource().GetGatewayReference() != pmID { 43 | t.Errorf("incorrect payment_method intent domain conversion, got: %v want: %v", appintent.GetSource().GetGatewayReference(), pmID) 44 | } 45 | 46 | if appintent.GetCustomer().GetGatewayReference() != cusID { 47 | t.Errorf("incorrect customer intent domain conversion, got: %v want: %v", appintent.GetCustomer().GetGatewayReference(), cusID) 48 | } 49 | } 50 | 51 | func TestFromStripeToAppIntentWithoutNextAction(t *testing.T) { 52 | pmID := "card_xxx" 53 | pm := stripe.PaymentMethod{ 54 | ID: pmID, 55 | } 56 | 57 | cusID := "cus_xxx" 58 | cus := stripe.Customer{ 59 | ID: cusID, 60 | } 61 | 62 | intent := stripe.PaymentIntent{ 63 | ID: "intent_xxx", 64 | PaymentMethod: &pm, 65 | Customer: &cus, 66 | } 67 | 68 | appintent := apppaymentintent.FromStripeToAppIntent(intent) 69 | 70 | if appintent.GetNextAction() != "" { 71 | t.Errorf("incorrect next_action intent domain conversion, got: %v want: ''", appintent.GetNextAction()) 72 | } 73 | } 74 | 75 | func TestFromStripeToAppIntentWithoutPaymentMethod(t *testing.T) { 76 | nat := "stripe_sdk" 77 | na := stripe.PaymentIntentNextAction{ 78 | Type: stripe.PaymentIntentNextActionType(nat), 79 | } 80 | 81 | cusID := "cus_xxx" 82 | cus := stripe.Customer{ 83 | ID: cusID, 84 | } 85 | 86 | intent := stripe.PaymentIntent{ 87 | ID: "intent_xxx", 88 | NextAction: &na, 89 | Customer: &cus, 90 | } 91 | 92 | appintent := apppaymentintent.FromStripeToAppIntent(intent) 93 | 94 | if appintent.GetSource() != nil { 95 | t.Errorf("incorrect payment_method intent domain conversion, got: %v want: nil", appintent.GetSource().GetGatewayReference()) 96 | } 97 | } 98 | 99 | func TestFromStripeToAppIntentWithoutCustomer(t *testing.T) { 100 | pmID := "card_xxx" 101 | pm := stripe.PaymentMethod{ 102 | ID: pmID, 103 | } 104 | 105 | nat := "stripe_sdk" 106 | na := stripe.PaymentIntentNextAction{ 107 | Type: stripe.PaymentIntentNextActionType(nat), 108 | } 109 | 110 | intent := stripe.PaymentIntent{ 111 | ID: "intent_xxx", 112 | NextAction: &na, 113 | PaymentMethod: &pm, 114 | } 115 | 116 | appintent := apppaymentintent.FromStripeToAppIntent(intent) 117 | 118 | if appintent.GetCustomer() != nil { 119 | t.Errorf("incorrect customer intent domain conversion, got: %v want: nil", appintent.GetCustomer().GetGatewayReference()) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /payment/source/paymentsource.go: -------------------------------------------------------------------------------- 1 | package apppaymentsource 2 | 3 | type Source interface { 4 | GetGatewayReference() string 5 | } 6 | 7 | type ps struct { 8 | R string `json:"gateway_reference"` // Gateway reference 9 | } 10 | 11 | // GetGatewayReference exposes ps.R value 12 | func (ps *ps) GetGatewayReference() string { 13 | return ps.R 14 | } 15 | 16 | // New returns a new instance of ps 17 | func New(r string) Source { 18 | return &ps{r} 19 | } 20 | -------------------------------------------------------------------------------- /payment/source/paymentsource_test.go: -------------------------------------------------------------------------------- 1 | // +build unit 2 | 3 | package apppaymentsource_test 4 | 5 | import ( 6 | "testing" 7 | 8 | apppaymentsource "github.com/lelledaniele/upaygo/payment/source" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | r := "card_XXX" 13 | got := apppaymentsource.New(r) 14 | 15 | if got.GetGatewayReference() != r { 16 | t.Errorf("New payment source reference is incorrect, got %v want %v", got.GetGatewayReference(), r) 17 | } 18 | } 19 | --------------------------------------------------------------------------------