Skip to content

Commit 0f92e37

Browse files
committed
feat: migrate to stripe payment intents
1 parent a3b8f98 commit 0f92e37

5 files changed

Lines changed: 235 additions & 30 deletions

File tree

.github/dependabot.yml

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,33 @@ updates:
33
- package-ecosystem: "gomod"
44
directory: "/"
55
schedule:
6-
interval: "daily"
7-
open-pull-requests-limit: 20
6+
interval: "weekly"
7+
open-pull-requests-limit: 10
8+
reviewers:
9+
- "malwarebo"
10+
assignees:
11+
- "malwarebo"
812
commit-message:
9-
prefix: (build)-
10-
include: scope
11-
pull-request-branch-name:
12-
separator: "-"
13+
prefix: "security"
14+
include: "scope"
15+
labels:
16+
- "security"
17+
- "dependencies"
18+
ignore:
19+
- dependency-name: "*"
20+
update-types: ["version-update:semver-major"]
21+
- package-ecosystem: "docker"
22+
directory: "/"
23+
schedule:
24+
interval: "weekly"
25+
open-pull-requests-limit: 5
26+
reviewers:
27+
- "malwarebo"
28+
assignees:
29+
- "malwarebo"
30+
commit-message:
31+
prefix: "docker"
32+
include: "scope"
33+
labels:
34+
- "docker"
35+
- "security"
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: Security Scan
2+
3+
on:
4+
push:
5+
branches: [ main, develop ]
6+
pull_request:
7+
branches: [ main ]
8+
schedule:
9+
- cron: '0 2 * * 1'
10+
11+
jobs:
12+
dependency-scan:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.24'
22+
23+
- name: Run govulncheck
24+
run: |
25+
go install golang.org/x/vuln/cmd/govulncheck@latest
26+
govulncheck ./...
27+
28+
- name: Run gosec
29+
uses: securecodewarrior/github-action-gosec@master
30+
with:
31+
args: '-fmt sarif -out gosec.sarif ./...'
32+
33+
- name: Upload SARIF file
34+
uses: github/codeql-action/upload-sarif@v3
35+
if: always()
36+
with:
37+
sarif_file: gosec.sarif
38+
39+
license-scan:
40+
runs-on: ubuntu-latest
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v4
44+
45+
- name: Set up Go
46+
uses: actions/setup-go@v5
47+
with:
48+
go-version: '1.24'
49+
50+
- name: Install go-licenses
51+
run: go install github.com/google/go-licenses@latest
52+
53+
- name: Check licenses
54+
run: |
55+
go-licenses report ./... --template="{{.Name}} {{.Version}} {{.License}}"
56+
57+
- name: Generate SBOM
58+
run: |
59+
go-licenses csv ./... > sbom.csv
60+
echo "SBOM generated: sbom.csv"
61+
62+
docker-scan:
63+
runs-on: ubuntu-latest
64+
steps:
65+
- name: Checkout code
66+
uses: actions/checkout@v4
67+
68+
- name: Build Docker image
69+
run: docker build -t gopay:security-scan .
70+
71+
- name: Run Trivy vulnerability scanner
72+
uses: aquasecurity/trivy-action@master
73+
with:
74+
image-ref: 'gopay:security-scan'
75+
format: 'sarif'
76+
output: 'trivy-results.sarif'
77+
78+
- name: Upload Trivy scan results
79+
uses: github/codeql-action/upload-sarif@v3
80+
if: always()
81+
with:
82+
sarif_file: 'trivy-results.sarif'

middleware/auth.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,13 @@ func (am *AuthMiddleware) HeadersMiddleware(next http.Handler) http.Handler {
140140
w.Header().Set("X-Content-Type-Options", "nosniff")
141141
w.Header().Set("X-Frame-Options", "DENY")
142142
w.Header().Set("X-XSS-Protection", "1; mode=block")
143-
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
144-
w.Header().Set("Content-Security-Policy", "default-src 'self'")
143+
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
144+
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'")
145145
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
146-
w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
146+
w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()")
147+
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
148+
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
149+
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
147150

148151
next.ServeHTTP(w, r)
149152
})

providers/stripe.go

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import (
77

88
"github.com/malwarebo/gopay/models"
99
"github.com/stripe/stripe-go/v82"
10-
"github.com/stripe/stripe-go/v82/charge"
1110
"github.com/stripe/stripe-go/v82/dispute"
11+
"github.com/stripe/stripe-go/v82/paymentintent"
1212
"github.com/stripe/stripe-go/v82/plan"
1313
"github.com/stripe/stripe-go/v82/refund"
1414
"github.com/stripe/stripe-go/v82/subscription"
15+
"github.com/stripe/stripe-go/v82/webhook"
1516
)
1617

1718
type StripeProvider struct {
18-
apiKey string
19+
apiKey string
20+
webhookSecret string
1921
}
2022

2123
func CreateStripeProvider(apiKey string) *StripeProvider {
@@ -25,19 +27,29 @@ func CreateStripeProvider(apiKey string) *StripeProvider {
2527
}
2628
}
2729

30+
func CreateStripeProviderWithWebhook(apiKey, webhookSecret string) *StripeProvider {
31+
stripe.Key = apiKey
32+
return &StripeProvider{
33+
apiKey: apiKey,
34+
webhookSecret: webhookSecret,
35+
}
36+
}
37+
2838
func (p *StripeProvider) Charge(ctx context.Context, req *models.ChargeRequest) (*models.ChargeResponse, error) {
29-
params := &stripe.ChargeParams{
30-
Amount: stripe.Int64(req.Amount), // Amount is already in cents
39+
params := &stripe.PaymentIntentParams{
40+
Amount: stripe.Int64(req.Amount),
3141
Currency: stripe.String(req.Currency),
3242
Description: stripe.String(req.Description),
3343
Customer: stripe.String(req.CustomerID),
3444
}
3545

36-
// Set payment method
3746
if req.PaymentMethod != "" {
38-
if err := params.SetSource(req.PaymentMethod); err != nil {
39-
return nil, fmt.Errorf("failed to set payment method: %w", err)
40-
}
47+
params.PaymentMethod = stripe.String(req.PaymentMethod)
48+
params.Confirm = stripe.Bool(true)
49+
}
50+
51+
params.AutomaticPaymentMethods = &stripe.PaymentIntentAutomaticPaymentMethodsParams{
52+
Enabled: stripe.Bool(true),
4153
}
4254

4355
if req.Metadata != nil {
@@ -49,29 +61,53 @@ func (p *StripeProvider) Charge(ctx context.Context, req *models.ChargeRequest)
4961
}
5062
}
5163

52-
ch, err := charge.New(params)
64+
pi, err := paymentintent.New(params)
5365
if err != nil {
54-
return nil, err
66+
return nil, fmt.Errorf("stripe payment intent creation failed: %w", err)
5567
}
5668

5769
metadata := make(map[string]interface{})
58-
for k, v := range ch.Metadata {
70+
for k, v := range pi.Metadata {
5971
metadata[k] = v
6072
}
6173

62-
return &models.ChargeResponse{
63-
ID: ch.ID,
74+
status := models.PaymentStatusPending
75+
if pi.Status == stripe.PaymentIntentStatusSucceeded {
76+
status = models.PaymentStatusSuccess
77+
} else if pi.Status == stripe.PaymentIntentStatusRequiresAction {
78+
status = models.PaymentStatusPending
79+
} else if pi.Status == stripe.PaymentIntentStatusCanceled {
80+
status = models.PaymentStatusFailed
81+
}
82+
83+
paymentMethodID := ""
84+
if pi.PaymentMethod != nil {
85+
paymentMethodID = pi.PaymentMethod.ID
86+
}
87+
88+
response := &models.ChargeResponse{
89+
ID: pi.ID,
6490
CustomerID: req.CustomerID,
65-
Amount: ch.Amount,
66-
Currency: string(ch.Currency),
67-
Status: models.PaymentStatusSuccess,
68-
PaymentMethod: ch.Source.ID,
91+
Amount: pi.Amount,
92+
Currency: string(pi.Currency),
93+
Status: status,
94+
PaymentMethod: paymentMethodID,
6995
Description: req.Description,
7096
ProviderName: "stripe",
71-
ProviderChargeID: ch.ID,
97+
ProviderChargeID: pi.ID,
7298
Metadata: metadata,
73-
CreatedAt: time.Unix(ch.Created, 0),
74-
}, nil
99+
CreatedAt: time.Unix(pi.Created, 0),
100+
}
101+
102+
if pi.NextAction != nil && pi.NextAction.Type == "redirect_to_url" {
103+
response.Metadata["requires_action"] = true
104+
response.Metadata["next_action_type"] = string(pi.NextAction.Type)
105+
if pi.NextAction.RedirectToURL != nil {
106+
response.Metadata["redirect_url"] = pi.NextAction.RedirectToURL.URL
107+
}
108+
}
109+
110+
return response, nil
75111
}
76112

77113
func (p *StripeProvider) Refund(ctx context.Context, req *models.RefundRequest) (*models.RefundResponse, error) {
@@ -115,7 +151,15 @@ func (p *StripeProvider) Refund(ctx context.Context, req *models.RefundRequest)
115151
}
116152

117153
func (p *StripeProvider) ValidateWebhookSignature(payload []byte, signature string) error {
118-
// Implement webhook signature validation
154+
if p.webhookSecret == "" {
155+
return fmt.Errorf("webhook secret not configured")
156+
}
157+
158+
_, err := webhook.ConstructEvent(payload, signature, p.webhookSecret)
159+
if err != nil {
160+
return fmt.Errorf("webhook signature verification failed: %w", err)
161+
}
162+
119163
return nil
120164
}
121165
func (p *StripeProvider) CreateSubscription(ctx context.Context, req *models.CreateSubscriptionRequest) (*models.Subscription, error) {

scripts/security-check.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
echo "Running Security Compliance Checks..."
6+
7+
echo "Checking Go module dependencies..."
8+
go mod verify
9+
go mod download
10+
11+
echo "Running vulnerability scan..."
12+
if command -v govulncheck &> /dev/null; then
13+
govulncheck ./...
14+
else
15+
echo "Installing govulncheck..."
16+
go install golang.org/x/vuln/cmd/govulncheck@latest
17+
govulncheck ./...
18+
fi
19+
20+
echo "Running security static analysis..."
21+
if command -v gosec &> /dev/null; then
22+
gosec -fmt json -out gosec-report.json ./...
23+
else
24+
echo "Installing gosec..."
25+
go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest
26+
gosec -fmt json -out gosec-report.json ./...
27+
fi
28+
29+
echo "Checking license compliance..."
30+
if command -v go-licenses &> /dev/null; then
31+
go-licenses report ./... > licenses-report.txt
32+
echo "License report generated: licenses-report.txt"
33+
else
34+
echo "Installing go-licenses..."
35+
go install github.com/google/go-licenses@latest
36+
go-licenses report ./... > licenses-report.txt
37+
echo "License report generated: licenses-report.txt"
38+
fi
39+
40+
echo "Scanning Docker image for vulnerabilities..."
41+
if command -v trivy &> /dev/null; then
42+
docker build -t gopay:security-scan .
43+
trivy image gopay:security-scan --format json --output trivy-report.json
44+
echo "Docker security scan completed: trivy-report.json"
45+
else
46+
echo "Trivy not found. Install with: brew install aquasecurity/trivy/trivy"
47+
fi
48+
49+
echo "Security compliance checks completed!"
50+
echo "Reports generated:"
51+
echo " - gosec-report.json (static analysis)"
52+
echo " - licenses-report.txt (license compliance)"
53+
echo " - trivy-report.json (Docker vulnerabilities)"

0 commit comments

Comments
 (0)