-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
228 lines (169 loc) · 6.28 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os/user"
"regexp"
"strings"
"time"
"github.com/chargebee/chargebee-go"
customerAction "github.com/chargebee/chargebee-go/actions/customer"
customer "github.com/chargebee/chargebee-go/models/customer"
"github.com/pariz/gountries"
"github.com/pkg/errors"
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/balancetransaction"
"github.com/stripe/stripe-go/payout"
"github.com/vjeantet/jodaTime"
)
var start = flag.Int("period_start", 0, "start of report period")
var end = flag.Int("period_end", 0, "end of report period")
func main() {
flag.Parse()
if *start == 0 {
log.Fatal("Please specify period start date")
}
if *end == 0 {
log.Fatal("Please specify period end date")
}
usr, err := user.Current()
if err != nil {
log.Fatal("Cannot get current user")
}
stripe_key_path := usr.HomeDir + "/.stripe_key"
data, err := ioutil.ReadFile(stripe_key_path)
if err != nil {
log.Fatalf("Cannot read Stripe key from file %s with error %v", stripe_key_path, err)
}
stripe.Key = strings.TrimSpace(string(data))
chargebee_key_path := usr.HomeDir + "/.chargebee_key"
dataChargebee, err := ioutil.ReadFile(chargebee_key_path)
if err != nil {
log.Fatalf("Cannot read Chargebee key from file %s with error %v", chargebee_key_path, err)
}
chargebee.Configure(strings.TrimSpace(string(dataChargebee)), "fastnetmon")
// Supported log levels: LevelDebug, LevelInfo, LevelWarn, LevelError
stripe.DefaultLeveledLogger = &stripe.LeveledLogger{
Level: stripe.LevelError,
}
i := payout.List(&stripe.PayoutListParams{ArrivalDateRange: &stripe.RangeQueryParams{
GreaterThanOrEqual: int64(*start),
LesserThanOrEqual: int64(*end),
}})
payouts := []*stripe.Payout{}
for i.Next() {
payout := i.Payout()
payouts = append(payouts, payout)
}
if err := i.Err(); err != nil {
log.Fatalf("Cannot retrieve all payouts: %v", err)
}
payoutsReverse := []*stripe.Payout{}
for i := len(payouts) - 1; i >= 0; i-- {
payoutsReverse = append(payoutsReverse, payouts[i])
}
for _, payout := range payoutsReverse {
timeArrived := time.Unix(payout.ArrivalDate, 0)
dateArrived := jodaTime.Format("dd/MM/YYYY", timeArrived)
payment_result := ""
if payout.FailureCode != "" {
payment_result = fmt.Sprintf("Failed due to reason: %s", payout.FailureCode)
}
fmt.Printf("Date Arrived: %v Amount: %s %s %s\n", dateArrived, PrintAmount(payout.Amount), payout.Currency, payment_result)
// Get sub transactions for this payout
transactions, err := GetPayoutTransactions(payout.ID)
if err != nil {
log.Fatalf("Cannot get transactions: %v", err)
}
for _, txn := range transactions {
// "ChargeBee customer: JXXXX111YYY777ZZZ"
re := regexp.MustCompile(`ChargeBee customer: \w+`)
chargebeeUser := re.FindString(txn.Description)
if chargebeeUser == "" {
if txn.Description == "REFUND FOR PAYOUT (STRIPE PAYOUT)" {
fmt.Printf("Net amount: %v It's probably refund transaction retry from previously failed refunds\n", PrintAmount(txn.Net))
continue
} else if txn.Type == "refund_failure" {
fmt.Printf("Net amount: %v It's probably refund transaction\n", PrintAmount(txn.Net))
continue
} else if txn.Type == "stripe_fee" {
fmt.Printf("Net amount: %v Radar for Fraud teams fee, it's Stripe fee for fraud screening\n", PrintAmount(txn.Net))
continue
} else if txn.Type == "adjustment" && txn.ReportingCategory == "dispute" {
// &{Amount:-206192 AvailableOn:1717696135 Created:1717696135 Currency:gbp
// Description:Chargeback withdrawal for ch_xxx ExchangeRate:0 ID:txn_xxx
// Fee:2000 FeeDetails:[0x14000d46050] Net:-208192 Recipient: ReportingCategory:dispute
// Source:0x14000531570 Status:available Type:adjustment}
fmt.Printf("Net amount: %v It's probably chargeback transaction\n", PrintAmount(txn.Net))
continue
} else {
log.Fatalf("Unexpected issue with match from description: '%s' for transaction: %+v", txn.Description, txn)
}
}
// Cut prefix
chargebeeUser = strings.TrimPrefix(chargebeeUser, "ChargeBee customer: ")
user, err := GetChargebeeUser(chargebeeUser)
if err != nil {
log.Fatalf("Cannot get user id from transaction description string: %s", txn.Description)
}
vatSection := ""
if user.VatNumber != "" {
vatSection = fmt.Sprintf("VAT: %s Validation status: %s", user.VatNumber, user.VatNumberStatus)
}
query := gountries.New()
full_country_name := ""
// Due to following issue: https://github.com/pariz/gountries/issues/30
// We have to handle Kosovo in a special way
if user.BillingAddress.Country == "XK" {
full_country_name = "Kosovo"
} else {
country_lookup_res, err := query.FindCountryByAlpha(user.BillingAddress.Country)
if err != nil {
log.Fatalf("Cannot get country name for code: %s %v", user.BillingAddress.Country, err)
}
full_country_name = country_lookup_res.Name.BaseLang.Common
}
fmt.Printf("Net amount: %v %s Company: %s Country: %s %s\n", PrintAmount(txn.Net), txn.Currency, user.BillingAddress.Company, full_country_name, vatSection)
}
fmt.Printf("\n")
}
}
// Returns absolute value for negative or positive values
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
func PrintAmount(amount int64) string {
// Amount can be negative
return fmt.Sprintf("%d.%02d", amount/100, abs(amount)%100)
}
// Returns all sub-transactions from payout
func GetPayoutTransactions(payoutID string) ([]*stripe.BalanceTransaction, error) {
j := balancetransaction.List(&stripe.BalanceTransactionListParams{Payout: &payoutID})
payoutTransactions := []*stripe.BalanceTransaction{}
for j.Next() {
txn := j.BalanceTransaction()
// List of transactions includes transaction itself
// We should filetr it out, we need only nested transactions, they have type "charge"
if txn.Type == "payout" {
continue
}
payoutTransactions = append(payoutTransactions, txn)
}
if err := j.Err(); err != nil {
return nil, errors.Errorf("Cannot get sub transactions: %v", err)
}
return payoutTransactions, nil
}
// Retrives user from Chargebee
func GetChargebeeUser(userID string) (*customer.Customer, error) {
customerRetrieved, err := customerAction.Retrieve(userID).Request()
if err != nil {
return nil, err
}
return customerRetrieved.Customer, nil
}