From 60502f015c0d32a1f3bc30998578352f3ca6298f Mon Sep 17 00:00:00 2001 From: Drew Dara-Abrams <> Date: Mon, 23 Dec 2024 20:01:17 -0800 Subject: [PATCH] attempt to use Stripe v2 Meter event stream --- go.mod | 7 +- go.sum | 41 +++++ meters/stripe/stripe.go | 324 +++++++++++++++++++++++++++++++++++ meters/stripe/stripe_test.go | 266 ++++++++++++++++++++++++++++ mocks/stripe_backend.go | 111 ++++++++++++ 5 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 meters/stripe/stripe.go create mode 100644 meters/stripe/stripe_test.go create mode 100644 mocks/stripe_backend.go diff --git a/go.mod b/go.mod index 7d82829..25d0c52 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,15 @@ module github.com/interline-io/transitland-mw -go 1.21.5 +go 1.22 + +toolchain go1.23.4 require ( github.com/amberflo/metering-go/v2 v2.5.0 github.com/auth0/go-auth0 v0.17.2 github.com/form3tech-oss/jwt-go v3.2.5+incompatible github.com/go-redis/redis/v8 v8.11.5 + github.com/golang/mock v1.6.0 github.com/interline-io/log v0.0.0-20241212203449-4bcff214cd71 github.com/interline-io/transitland-dbutil v0.0.0-20241212203507-15a69a52c1c4 github.com/jellydator/ttlcache/v2 v2.11.1 @@ -14,9 +17,11 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/rs/zerolog v1.33.0 github.com/stretchr/testify v1.9.0 + github.com/stripe/stripe-go/v81 v81.2.0 github.com/tidwall/gjson v1.17.3 github.com/tidwall/tinylru v1.2.1 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c + go.uber.org/mock v0.5.0 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index e2fa264..1240fa6 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/PuerkitoBio/rehttp v1.3.0 h1:w54Pb72MQn2eJrSdPsvGqXlAfiK1+NMTGDrOJJ4YvSU= github.com/PuerkitoBio/rehttp v1.3.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/amberflo/metering-go/v2 v2.5.0 h1:2Xu9TfQVtGeh0oXEZigknMbcQQHJ+GfIRreZjaSSuc4= github.com/amberflo/metering-go/v2 v2.5.0/go.mod h1:jiKk5ddwmHDR49qjexDm1l+Yl0y9sbkDbRc2cgvAjZY= github.com/auth0/go-auth0 v0.17.2 h1:qEttAY4yYeEJl6wu0iOwlet26wUKA2G5YOUomfuxcy4= @@ -14,8 +18,11 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -23,15 +30,22 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8= github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -40,6 +54,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/interline-io/log v0.0.0-20241212203449-4bcff214cd71 h1:RI4mfj5B0VPK3XznLKTRPzFScySmRDYYp6tACSqZfoE= github.com/interline-io/log v0.0.0-20241212203449-4bcff214cd71/go.mod h1:chJaM8SKcHI6ivoeFuZ8M8axTjSV4TPmuQ+sAyAHa34= github.com/interline-io/transitland-dbutil v0.0.0-20241212203507-15a69a52c1c4 h1:25yHjhbhKqJI5Gt/16WVQ2m9HtsymVm46UdAm50i/wg= @@ -62,6 +77,9 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -85,6 +103,9 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -110,12 +131,15 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stripe/stripe-go/v81 v81.2.0 h1:AduJoFed6xif3uG7rXRa2LxY+AJiialVA1hXDak1aUk= +github.com/stripe/stripe-go/v81 v81.2.0/go.mod h1:C/F4jlmnGNacvYtBp/LUHCvVUJEZffFQCobkzwY1WOo= github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -125,11 +149,16 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/tinylru v1.2.1 h1:VgBr72c2IEr+V+pCdkPZUwiQ0KJknnWIYbhxAVkYfQk= github.com/tidwall/tinylru v1.2.1/go.mod h1:9bQnEduwB6inr2Y7AkBP7JPgCkyrhTV/ZpX0oOOpBI4= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -140,12 +169,17 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= @@ -160,13 +194,16 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -179,14 +216,18 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= diff --git a/meters/stripe/stripe.go b/meters/stripe/stripe.go new file mode 100644 index 0000000..07698c8 --- /dev/null +++ b/meters/stripe/stripe.go @@ -0,0 +1,324 @@ +package stripe + +import ( + "encoding/json" + "fmt" + "os" + "sync" + "time" + + "net/http" + + "github.com/interline-io/log" + "github.com/interline-io/transitland-mw/meters" + "github.com/stripe/stripe-go/v81" + "github.com/stripe/stripe-go/v81/client" + "github.com/stripe/stripe-go/v81/rawrequest" +) + +func init() { + var _ meters.MeterProvider = &StripeMeterProvider{} +} + +// StripeMeterProvider implements the MeterProvider interface for Stripe's metering API +type StripeMeterProvider struct { + client *client.API + interval time.Duration + cfgs map[string]stripeConfig + sessionAuthToken string + sessionExpiresAt string + apiKey string + batchEvents []meterEvent + batchMutex sync.Mutex + eventChan chan meterEvent + done chan struct{} +} + +type stripeConfig struct { + Name string `json:"name,omitempty"` + DefaultUser string `json:"default_user,omitempty"` + ExternalIDKey string `json:"external_id_key,omitempty"` + Dimensions meters.Dimensions `json:"dimensions,omitempty"` +} + +// NewStripeMeterProvider creates a new Stripe meter provider +func NewStripeMeterProvider(apiKey string, interval time.Duration) *StripeMeterProvider { + config := &stripe.BackendConfig{ + EnableTelemetry: stripe.Bool(false), + } + sc := client.New(apiKey, &stripe.Backends{ + API: stripe.GetBackendWithConfig(stripe.APIBackend, config), + }) + + mp := &StripeMeterProvider{ + client: sc, + interval: interval, + cfgs: map[string]stripeConfig{}, + apiKey: apiKey, + eventChan: make(chan meterEvent, maxBatchSize), + done: make(chan struct{}), + } + go mp.batchWorker() + return mp +} + +// LoadConfig loads meter configurations from a JSON file +func (m *StripeMeterProvider) LoadConfig(path string) error { + cfgs := map[string]stripeConfig{} + data, err := os.ReadFile(path) + if err != nil { + return err + } + if err := json.Unmarshal(data, &cfgs); err != nil { + return err + } + m.cfgs = cfgs + return nil +} + +func (m *StripeMeterProvider) NewMeter(user meters.MeterUser) meters.ApiMeter { + return &stripeMeter{ + user: user, + mp: m, + } +} + +func (m *StripeMeterProvider) Close() error { + close(m.done) + return nil +} + +func (m *StripeMeterProvider) Flush() error { + m.batchMutex.Lock() + events := m.batchEvents + m.batchEvents = nil + m.batchMutex.Unlock() + + if len(events) == 0 { + return nil + } + + // Refresh session token if needed + if err := m.refreshMeterEventSession(); err != nil { + return fmt.Errorf("unable to refresh meter event session: %v", err) + } + + // Get meter events backend with session token + b, err := stripe.GetRawRequestBackend(stripe.MeterEventsBackend) + if err != nil { + return err + } + sessionClient := rawrequest.Client{B: b, Key: m.sessionAuthToken} + + // Convert events to API payload + eventPayloads := make([]interface{}, len(events)) + for i, evt := range events { + eventPayloads[i] = map[string]interface{}{ + "event_name": evt.EventName, + "payload": map[string]interface{}{ + "stripe_customer_id": evt.CustomerId, + "value": fmt.Sprintf("%f", evt.Value), + "metadata": buildMetadataFromDimensions(nil, evt.Dimensions), + }, + } + } + + params := map[string]interface{}{ + "events": eventPayloads, + } + + body, err := json.Marshal(params) + if err != nil { + return err + } + + _, err = sessionClient.RawRequest(http.MethodPost, "/v2/billing/meter_event_stream", string(body), nil) + return err +} + +// sendMeter sends metering data to Stripe +// See Stripe's documentation for creating meter events: https://docs.stripe.com/api/v2/billing/meter-event-stream/create +func (m *StripeMeterProvider) sendMeter(user meters.MeterUser, meterName string, value float64, extraDimensions meters.Dimensions) error { + cfg, ok := m.getcfg(meterName) + if !ok { + return nil + } + + customerId, ok := m.getCustomerID(cfg, user) + if !ok { + log.Error().Str("user", user.ID()).Msg("could not meter; no stripe customer id") + return nil + } + + m.batchMutex.Lock() + m.batchEvents = append(m.batchEvents, meterEvent{ + EventName: meterName, + CustomerId: customerId, + Value: value, + Dimensions: extraDimensions, + }) + shouldFlush := len(m.batchEvents) >= maxBatchSize + m.batchMutex.Unlock() + + if shouldFlush { + return m.Flush() + } + return nil +} + +func buildMetadataFromDimensions(cfgDims, extraDims meters.Dimensions) map[string]string { + metadata := make(map[string]string) + for _, d := range cfgDims { + metadata[d.Key] = d.Value + } + for _, d := range extraDims { + metadata[d.Key] = d.Value + } + return metadata +} + +func (m *StripeMeterProvider) GetValue(user meters.MeterUser, meterName string, startTime time.Time, endTime time.Time, dims meters.Dimensions) (float64, bool) { + cfg, ok := m.getcfg(meterName) + if !ok { + return 0, false + } + + customerId, ok := m.getCustomerID(cfg, user) + if !ok { + log.Error().Str("user", user.ID()).Msg("could not get value; no stripe customer id") + return 0, false + } + + params := &stripe.UsageRecordSummaryListParams{ + SubscriptionItem: stripe.String(customerId), + } + + iter := m.client.UsageRecordSummaries.List(params) + var total float64 + for iter.Next() { + summary := iter.UsageRecordSummary() + if summary.Period.Start >= startTime.Unix() && summary.Period.Start <= endTime.Unix() { + total += float64(summary.TotalUsage) + } + } + if err := iter.Err(); err != nil { + log.Error().Err(err).Msg("could not get usage summary") + return 0, false + } + + return total, true +} + +func (m *StripeMeterProvider) getCustomerID(cfg stripeConfig, user meters.MeterUser) (string, bool) { + customerId := cfg.DefaultUser + if user != nil { + eidKey := cfg.ExternalIDKey + if eidKey == "" { + eidKey = "stripe" + } + if a, ok := user.GetExternalData(eidKey); ok { + customerId = a + } + } + if customerId == "" { + log.Error().Str("user", user.ID()).Str("external_id_key", cfg.ExternalIDKey).Msg("could not get value; no stripe customer id") + } + return customerId, customerId != "" +} + +func (m *StripeMeterProvider) getcfg(meterName string) (stripeConfig, bool) { + cfg, ok := m.cfgs[meterName] + if !ok { + cfg = stripeConfig{ + Name: meterName, + } + } + if cfg.Name == "" { + log.Error().Str("meter", meterName).Msg("could not meter; no stripe config for meter") + return cfg, false + } + return cfg, true +} + +type stripeMeter struct { + user meters.MeterUser + mp *StripeMeterProvider +} + +func (m *stripeMeter) Meter(meterName string, value float64, extraDimensions meters.Dimensions) error { + log.Trace(). + Str("user", m.user.ID()). + Str("meter", meterName). + Float64("meter_value", value). + Msg("meter") + return m.mp.sendMeter(m.user, meterName, value, extraDimensions) +} + +func (m *stripeMeter) GetValue(meterName string, startTime time.Time, endTime time.Time, dims meters.Dimensions) (float64, bool) { + return m.mp.GetValue(m.user, meterName, startTime, endTime, dims) +} + +func (m *stripeMeter) AddDimension(meterName string, key string, value string) { + // No-op: dimensions are handled directly in payload +} + +// Add session refresh logic +// See Stripe's documentation for creating a meter event session: https://docs.stripe.com/api/v2/billing/meter-event-stream/session/create +func (m *StripeMeterProvider) refreshMeterEventSession() error { + currentTime := time.Now().Format(time.RFC3339) + + // Check if session is null or expired + if m.sessionAuthToken == "" || m.sessionExpiresAt <= currentTime { + b, err := stripe.GetRawRequestBackend(stripe.APIBackend) + if err != nil { + return err + } + client := rawrequest.Client{B: b, Key: m.apiKey} + + // Create a new meter event session + rawResp, err := client.RawRequest(http.MethodPost, "/v2/billing/meter_event_session", "", nil) + if err != nil { + return err + } + if rawResp.StatusCode != 200 { + return fmt.Errorf("meter event session request failed: %s", rawResp.Status) + } + + var resp map[string]interface{} + err = json.Unmarshal(rawResp.RawJSON, &resp) + if err != nil { + return err + } + + m.sessionAuthToken = resp["authentication_token"].(string) + m.sessionExpiresAt = resp["expires_at"].(string) + } + return nil +} + +func (m *StripeMeterProvider) batchWorker() { + ticker := time.NewTicker(m.interval) + defer ticker.Stop() + + var batch []meterEvent + for { + select { + case evt := <-m.eventChan: + batch = append(batch, evt) + if len(batch) >= maxBatchSize { + m.sendBatch(batch) + batch = nil + } + case <-ticker.C: + if len(batch) > 0 { + m.sendBatch(batch) + batch = nil + } + case <-m.done: + if len(batch) > 0 { + m.sendBatch(batch) + } + return + } + } +} diff --git a/meters/stripe/stripe_test.go b/meters/stripe/stripe_test.go new file mode 100644 index 0000000..9fb615c --- /dev/null +++ b/meters/stripe/stripe_test.go @@ -0,0 +1,266 @@ +package stripe + +import ( + "errors" + "os" + "testing" + "time" + + "github.com/interline-io/transitland-dbutil/testutil" + "github.com/interline-io/transitland-mw/internal/metertest" + "github.com/interline-io/transitland-mw/meters" + stripemock "github.com/interline-io/transitland-mw/mocks" + "github.com/stripe/stripe-go/v81" + "github.com/stripe/stripe-go/v81/client" + "go.uber.org/mock/gomock" +) + +func TestStripeMeter(t *testing.T) { + checkKeys := []string{ + "TL_TEST_STRIPE_APIKEY", + "TL_TEST_STRIPE_METER1", + "TL_TEST_STRIPE_METER2", + "TL_TEST_STRIPE_USER1", + "TL_TEST_STRIPE_USER2", + "TL_TEST_STRIPE_USER3", + } + for _, k := range checkKeys { + _, a, ok := testutil.CheckEnv(k) + if !ok { + t.Skip(errors.New(a)) + return + } + } + + eidKey := "stripe" + testConfig := metertest.Config{ + TestMeter1: os.Getenv("TL_TEST_STRIPE_METER1"), + TestMeter2: os.Getenv("TL_TEST_STRIPE_METER2"), + User1: metertest.NewTestUser(os.Getenv("TL_TEST_STRIPE_USER1"), map[string]string{eidKey: os.Getenv("TL_TEST_STRIPE_USER1")}), + User2: metertest.NewTestUser(os.Getenv("TL_TEST_STRIPE_USER2"), map[string]string{eidKey: os.Getenv("TL_TEST_STRIPE_USER2")}), + User3: metertest.NewTestUser(os.Getenv("TL_TEST_STRIPE_USER3"), map[string]string{eidKey: os.Getenv("TL_TEST_STRIPE_USER3")}), + } + + mp := NewStripeMeterProvider(os.Getenv("TL_TEST_STRIPE_APIKEY"), 1*time.Second) + mp.cfgs[testConfig.TestMeter1] = stripeConfig{Name: testConfig.TestMeter1, ExternalIDKey: eidKey} + mp.cfgs[testConfig.TestMeter2] = stripeConfig{Name: testConfig.TestMeter2, ExternalIDKey: eidKey} + metertest.TestMeter(t, mp, testConfig) +} + +func TestStripeConfig(t *testing.T) { + mp := NewStripeMeterProvider("test-key", 1*time.Second) + + t.Run("load config", func(t *testing.T) { + testConfig := `{ + "test_meter": { + "name": "test_meter", + "default_user": "default-123", + "external_id_key": "stripe_test", + "dimensions": [ + {"key": "1", "value": "dim1"}, + {"key": "2", "value": "dim2"} + ] + } + }` + tmpfile, err := os.CreateTemp("", "stripe-test-*.json") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write([]byte(testConfig)); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + if err := mp.LoadConfig(tmpfile.Name()); err != nil { + t.Errorf("got error loading config: %v", err) + } + + cfg, ok := mp.cfgs["test_meter"] + if !ok { + t.Error("config not loaded") + } + if cfg.Name != "test_meter" { + t.Errorf("got name %q, want %q", cfg.Name, "test_meter") + } + if cfg.DefaultUser != "default-123" { + t.Errorf("got default user %q, want %q", cfg.DefaultUser, "default-123") + } + }) +} + +func TestCustomerID(t *testing.T) { + mp := NewStripeMeterProvider("test-key", 1*time.Second) + cfg := stripeConfig{ + Name: "test_meter", + DefaultUser: "default-123", + ExternalIDKey: "stripe_test", + } + + t.Run("default user", func(t *testing.T) { + id, ok := mp.getCustomerID(cfg, nil) + if !ok { + t.Error("expected ok") + } + if id != "default-123" { + t.Errorf("got id %q, want %q", id, "default-123") + } + }) + + t.Run("user with external id", func(t *testing.T) { + user := metertest.NewTestUser("test-user", map[string]string{"stripe_test": "user-123"}) + id, ok := mp.getCustomerID(cfg, user) + if !ok { + t.Error("expected ok") + } + if id != "user-123" { + t.Errorf("got id %q, want %q", id, "user-123") + } + }) + + t.Run("user without external id", func(t *testing.T) { + user := metertest.NewTestUser("test-user", nil) + id, ok := mp.getCustomerID(cfg, user) + if !ok { + t.Error("expected ok") + } + if id != "default-123" { + t.Errorf("got id %q, want %q", id, "default-123") + } + }) +} + +func TestGetConfig(t *testing.T) { + mp := NewStripeMeterProvider("test-key", 1*time.Second) + mp.cfgs["test_meter"] = stripeConfig{ + Name: "test_meter", + } + + t.Run("existing config", func(t *testing.T) { + cfg, ok := mp.getcfg("test_meter") + if !ok { + t.Error("expected ok") + } + if cfg.Name != "test_meter" { + t.Errorf("got name %q, want %q", cfg.Name, "test_meter") + } + }) + + t.Run("missing config", func(t *testing.T) { + cfg, ok := mp.getcfg("missing_meter") + if !ok { + t.Error("expected not ok") + } + if cfg.Name != "missing_meter" { + t.Errorf("got name %q, want %q", cfg.Name, "missing_meter") + } + }) +} + +func TestStripeMeterWithMock(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockBackend := stripemock.NewMockBackend(mockCtrl) + sc := client.New("test_key", &stripe.Backends{ + API: mockBackend, + Uploads: mockBackend, + }) + + mp := &StripeMeterProvider{ + client: sc, + interval: 1 * time.Second, + apiKey: "test_key", + cfgs: map[string]stripeConfig{ + "test_meter": { + Name: "test_meter", + DefaultUser: "default-123", + ExternalIDKey: "stripe_id", + Dimensions: meters.Dimensions{ + {Key: "1", Value: "dimension1"}, + }, + }, + }, + } + + testUser := metertest.NewTestUser("test-user", map[string]string{ + "stripe_id": "customer-123", + }) + + t.Run("sendMeter", func(t *testing.T) { + // Mock session creation with response + mockBackend.EXPECT(). + CallRaw( + gomock.Any(), + "POST", + "/v2/billing/meter_event_session", + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + Return(nil).Times(1) + + // Mock meter event stream call + mockBackend.EXPECT(). + CallRaw( + gomock.Any(), + "POST", + "/v2/billing/meter_event_stream", + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + Return(nil).Times(1) + + err := mp.sendMeter(testUser, "test_meter", 100, meters.Dimensions{ + {Key: "2", Value: "extra_dimension"}, + }) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + + // Ensure the worker processes the batch + time.Sleep(2 * time.Second) + }) + + t.Run("GetValue", func(t *testing.T) { + startTime := time.Now().Add(-1 * time.Hour) + endTime := time.Now() + + mockBackend.EXPECT(). + CallRaw( + gomock.Any(), + "GET", + "/v1/subscription_items/customer-123/usage_record_summaries", + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + DoAndReturn(func(_ interface{}, _ interface{}, _ interface{}, _ interface{}, _ interface{}, result interface{}) error { + summary := &stripe.UsageRecordSummaryList{ + Data: []*stripe.UsageRecordSummary{ + { + TotalUsage: 150, + Period: &stripe.Period{ + Start: startTime.Unix(), + End: endTime.Unix(), + }, + }, + }, + } + *(result.(*stripe.UsageRecordSummaryList)) = *summary + return nil + }) + + value, ok := mp.GetValue(testUser, "test_meter", startTime, endTime, nil) + if !ok { + t.Error("expected ok to be true") + } + if value != 150 { + t.Errorf("expected value 150, got %f", value) + } + }) +} diff --git a/mocks/stripe_backend.go b/mocks/stripe_backend.go new file mode 100644 index 0000000..0f7ce56 --- /dev/null +++ b/mocks/stripe_backend.go @@ -0,0 +1,111 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/stripe/stripe-go/v81 (interfaces: Backend) +// +// Generated by this command: +// +// mockgen -destination=mocks/stripe_backend.go -package=mocks github.com/stripe/stripe-go/v81 Backend +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + bytes "bytes" + reflect "reflect" + + stripe "github.com/stripe/stripe-go/v81" + form "github.com/stripe/stripe-go/v81/form" + gomock "go.uber.org/mock/gomock" +) + +// MockBackend is a mock of Backend interface. +type MockBackend struct { + ctrl *gomock.Controller + recorder *MockBackendMockRecorder + isgomock struct{} +} + +// MockBackendMockRecorder is the mock recorder for MockBackend. +type MockBackendMockRecorder struct { + mock *MockBackend +} + +// NewMockBackend creates a new mock instance. +func NewMockBackend(ctrl *gomock.Controller) *MockBackend { + mock := &MockBackend{ctrl: ctrl} + mock.recorder = &MockBackendMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBackend) EXPECT() *MockBackendMockRecorder { + return m.recorder +} + +// Call mocks base method. +func (m *MockBackend) Call(method, path, key string, params stripe.ParamsContainer, v stripe.LastResponseSetter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Call", method, path, key, params, v) + ret0, _ := ret[0].(error) + return ret0 +} + +// Call indicates an expected call of Call. +func (mr *MockBackendMockRecorder) Call(method, path, key, params, v any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockBackend)(nil).Call), method, path, key, params, v) +} + +// CallMultipart mocks base method. +func (m *MockBackend) CallMultipart(method, path, key, boundary string, body *bytes.Buffer, params *stripe.Params, v stripe.LastResponseSetter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CallMultipart", method, path, key, boundary, body, params, v) + ret0, _ := ret[0].(error) + return ret0 +} + +// CallMultipart indicates an expected call of CallMultipart. +func (mr *MockBackendMockRecorder) CallMultipart(method, path, key, boundary, body, params, v any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallMultipart", reflect.TypeOf((*MockBackend)(nil).CallMultipart), method, path, key, boundary, body, params, v) +} + +// CallRaw mocks base method. +func (m *MockBackend) CallRaw(method, path, key string, body *form.Values, params *stripe.Params, v stripe.LastResponseSetter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CallRaw", method, path, key, body, params, v) + ret0, _ := ret[0].(error) + return ret0 +} + +// CallRaw indicates an expected call of CallRaw. +func (mr *MockBackendMockRecorder) CallRaw(method, path, key, body, params, v any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallRaw", reflect.TypeOf((*MockBackend)(nil).CallRaw), method, path, key, body, params, v) +} + +// CallStreaming mocks base method. +func (m *MockBackend) CallStreaming(method, path, key string, params stripe.ParamsContainer, v stripe.StreamingLastResponseSetter) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CallStreaming", method, path, key, params, v) + ret0, _ := ret[0].(error) + return ret0 +} + +// CallStreaming indicates an expected call of CallStreaming. +func (mr *MockBackendMockRecorder) CallStreaming(method, path, key, params, v any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CallStreaming", reflect.TypeOf((*MockBackend)(nil).CallStreaming), method, path, key, params, v) +} + +// SetMaxNetworkRetries mocks base method. +func (m *MockBackend) SetMaxNetworkRetries(maxNetworkRetries int64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMaxNetworkRetries", maxNetworkRetries) +} + +// SetMaxNetworkRetries indicates an expected call of SetMaxNetworkRetries. +func (mr *MockBackendMockRecorder) SetMaxNetworkRetries(maxNetworkRetries any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxNetworkRetries", reflect.TypeOf((*MockBackend)(nil).SetMaxNetworkRetries), maxNetworkRetries) +}