@@ -80,13 +80,45 @@ func (a *API) PaymentListForOrder(w http.ResponseWriter, r *http.Request) error
80
80
return sendJSON (w , http .StatusOK , order .Transactions )
81
81
}
82
82
83
- // PaymentCreate is the endpoint for creating a payment for an order
84
- func (a * API ) PaymentCreate (w http.ResponseWriter , r * http.Request ) error {
83
+ func paymentComplete (r * http.Request , tx * gorm.DB , tr * models.Transaction , order * models.Order ) {
85
84
ctx := r .Context ()
86
85
log := getLogEntry (r )
87
86
config := gcontext .GetConfig (ctx )
87
+
88
+ tr .Status = models .PaidState
89
+ if tx .NewRecord (tr ) {
90
+ tx .Create (tr )
91
+ } else {
92
+ tx .Save (tr )
93
+ }
94
+ order .PaymentState = models .PaidState
95
+ tx .Save (order )
96
+
97
+ if config .Webhooks .Payment != "" {
98
+ hook , err := models .NewHook ("payment" , config .SiteURL , config .Webhooks .Payment , order .UserID , config .Webhooks .Secret , order )
99
+ if err != nil {
100
+ log .WithError (err ).Error ("Failed to process webhook" )
101
+ }
102
+ tx .Save (hook )
103
+ }
104
+ }
105
+
106
+ func sendOrderConfirmation (ctx context.Context , log logrus.FieldLogger , tr * models.Transaction ) {
88
107
mailer := gcontext .GetMailer (ctx )
89
108
109
+ err1 := mailer .OrderConfirmationMail (tr )
110
+ err2 := mailer .OrderReceivedMail (tr )
111
+
112
+ if err1 != nil || err2 != nil {
113
+ log .Errorf ("Error sending order confirmation mails: %v %v" , err1 , err2 )
114
+ }
115
+ }
116
+
117
+ // PaymentCreate is the endpoint for creating a payment for an order
118
+ func (a * API ) PaymentCreate (w http.ResponseWriter , r * http.Request ) error {
119
+ ctx := r .Context ()
120
+ log := getLogEntry (r )
121
+
90
122
params := PaymentParams {Currency : "USD" }
91
123
err := json .NewDecoder (r .Body ).Decode (& params )
92
124
if err != nil {
@@ -100,7 +132,7 @@ func (a *API) PaymentCreate(w http.ResponseWriter, r *http.Request) error {
100
132
if provider == nil {
101
133
return badRequestError ("Payment provider '%s' not configured" , params .ProviderType )
102
134
}
103
- charge , err := provider .NewCharger (ctx , r )
135
+ charge , err := provider .NewCharger (ctx , r , log . WithField ( "component" , "payment_provider" ) )
104
136
if err != nil {
105
137
return badRequestError ("Error creating payment provider: %v" , err )
106
138
}
@@ -156,18 +188,33 @@ func (a *API) PaymentCreate(w http.ResponseWriter, r *http.Request) error {
156
188
return internalServerError ("We failed to authorize the amount for this order: %v" , err )
157
189
}
158
190
159
- invoiceNumber , err := models .NextInvoiceNumber (tx , order .InstanceID )
160
- if err != nil {
161
- tx .Rollback ()
162
- return internalServerError ("We failed to generate a valid invoice ID, please try again later: %v" , err )
191
+ invoiceNumber := order .InvoiceNumber
192
+ if invoiceNumber == 0 {
193
+ var err error
194
+ invoiceNumber , err = models .NextInvoiceNumber (tx , order .InstanceID )
195
+ if err != nil {
196
+ tx .Rollback ()
197
+ return internalServerError ("We failed to generate a valid invoice ID, please try again later: %v" , err )
198
+ }
199
+ order .InvoiceNumber = invoiceNumber
163
200
}
164
201
165
202
tr := models .NewTransaction (order )
166
203
processorID , err := charge (params .Amount , params .Currency , order , invoiceNumber )
167
204
tr .ProcessorID = processorID
168
205
tr .InvoiceNumber = invoiceNumber
206
+ order .PaymentProcessor = provider .Name ()
169
207
170
208
if err != nil {
209
+ if pendingErr , ok := err .(* payments.PaymentPendingError ); ok {
210
+ tr .Status = models .PendingState
211
+ tr .ProviderMetadata = pendingErr .Metadata ()
212
+ tx .Create (tr )
213
+ tx .Save (order )
214
+ tx .Commit ()
215
+ return sendJSON (w , 200 , tr )
216
+ }
217
+
171
218
tr .FailureCode = strconv .FormatInt (http .StatusInternalServerError , 10 )
172
219
tr .FailureDescription = err .Error ()
173
220
tr .Status = models .FailedState
@@ -176,34 +223,88 @@ func (a *API) PaymentCreate(w http.ResponseWriter, r *http.Request) error {
176
223
return internalServerError ("There was an error charging your card: %v" , err ).WithInternalError (err )
177
224
}
178
225
179
- // mark order and transaction as paid
180
- tr .Status = models .PaidState
181
- tx .Create (tr )
182
- order .PaymentProcessor = provider .Name ()
183
- order .PaymentState = models .PaidState
184
- order .InvoiceNumber = invoiceNumber
185
- tx .Save (order )
226
+ paymentComplete (r , tx , tr , order )
227
+ if err := tx .Commit ().Error ; err != nil {
228
+ return internalServerError ("Saving payment failed" ).WithInternalError (err )
229
+ }
186
230
187
- if config .Webhooks .Payment != "" {
188
- hook , err := models .NewHook ("payment" , config .SiteURL , config .Webhooks .Payment , order .UserID , config .Webhooks .Secret , order )
189
- if err != nil {
190
- log .WithError (err ).Error ("Failed to process webhook" )
231
+ go sendOrderConfirmation (ctx , log , tr )
232
+
233
+ return sendJSON (w , http .StatusOK , tr )
234
+ }
235
+
236
+ // PaymentConfirm allows client to confirm if a pending transaction has been completed. Updates transaction and order
237
+ func (a * API ) PaymentConfirm (w http.ResponseWriter , r * http.Request ) error {
238
+ ctx := r .Context ()
239
+ log := getLogEntry (r )
240
+
241
+ payID := chi .URLParam (r , "payment_id" )
242
+ trans , httpErr := a .getTransaction (payID )
243
+ if httpErr != nil {
244
+ return httpErr
245
+ }
246
+
247
+ if trans .UserID != "" {
248
+ token := gcontext .GetToken (ctx )
249
+ if token == nil {
250
+ return unauthorizedError ("You must be logged in to confirm this payment" )
251
+ }
252
+ claims := token .Claims .(* claims.JWTClaims )
253
+ if trans .UserID != claims .Subject {
254
+ return unauthorizedError ("You must be logged in to confirm this payment" )
191
255
}
192
- tx .Save (hook )
193
256
}
194
257
195
- tx .Commit ()
258
+ if trans .Status == models .PaidState {
259
+ return sendJSON (w , http .StatusOK , trans )
260
+ }
196
261
197
- go func () {
198
- err1 := mailer .OrderConfirmationMail (tr )
199
- err2 := mailer .OrderReceivedMail (tr )
262
+ order := & models.Order {}
263
+ if rsp := a .db .Find (order , "id = ?" , trans .OrderID ); rsp .Error != nil {
264
+ if rsp .RecordNotFound () {
265
+ return notFoundError ("Order not found" )
266
+ }
267
+ return internalServerError ("Error while querying for order" ).WithInternalError (rsp .Error )
268
+ }
269
+ if order .PaymentProcessor == "" {
270
+ return badRequestError ("Order does not specify a payment provider" )
271
+ }
272
+
273
+ provider := gcontext .GetPaymentProviders (ctx )[order .PaymentProcessor ]
274
+ if provider == nil {
275
+ return badRequestError ("Payment provider '%s' not configured" , order .PaymentProcessor )
276
+ }
277
+ confirm , err := provider .NewConfirmer (ctx , r , log .WithField ("component" , "payment_provider" ))
278
+ if err != nil {
279
+ return badRequestError ("Error creating payment provider: %v" , err )
280
+ }
200
281
201
- if err1 != nil || err2 != nil {
202
- log .Errorf ("Error sending order confirmation mails: %v %v" , err1 , err2 )
282
+ if err := confirm (trans .ProcessorID ); err != nil {
283
+ if confirmFail , ok := err .(* payments.PaymentConfirmFailError ); ok {
284
+ return badRequestError ("Error confirming payment: %s" , confirmFail .Error ())
203
285
}
204
- }()
286
+ return internalServerError ("Error on provider while trying to confirm: %v. Try again later." , err )
287
+ }
205
288
206
- return sendJSON (w , http .StatusOK , tr )
289
+ tx := a .db .Begin ()
290
+
291
+ if trans .InvoiceNumber == 0 {
292
+ invoiceNumber , err := models .NextInvoiceNumber (tx , order .InstanceID )
293
+ if err != nil {
294
+ tx .Rollback ()
295
+ return internalServerError ("We failed to generate a valid invoice ID, please try again later: %v" , err )
296
+ }
297
+ trans .InvoiceNumber = invoiceNumber
298
+ }
299
+
300
+ paymentComplete (r , tx , trans , order )
301
+ if err := tx .Commit ().Error ; err != nil {
302
+ return internalServerError ("Saving payment failed" ).WithInternalError (err )
303
+ }
304
+
305
+ go sendOrderConfirmation (ctx , log , trans )
306
+
307
+ return sendJSON (w , http .StatusOK , trans )
207
308
}
208
309
209
310
// PaymentList will list all the payments that meet the criteria. It is only available to admins.
@@ -280,7 +381,7 @@ func (a *API) PaymentRefund(w http.ResponseWriter, r *http.Request) error {
280
381
if provider == nil {
281
382
return badRequestError ("Payment provider '%s' not configured" , order .PaymentProcessor )
282
383
}
283
- refund , err := provider .NewRefunder (ctx , r )
384
+ refund , err := provider .NewRefunder (ctx , r , log . WithField ( "component" , "payment_provider" ) )
284
385
if err != nil {
285
386
return badRequestError ("Error creating payment provider: %v" , err )
286
387
}
@@ -328,6 +429,7 @@ func (a *API) PaymentRefund(w http.ResponseWriter, r *http.Request) error {
328
429
// PreauthorizePayment creates a new payment that can be authorized in the browser
329
430
func (a * API ) PreauthorizePayment (w http.ResponseWriter , r * http.Request ) error {
330
431
ctx := r .Context ()
432
+ log := getLogEntry (r )
331
433
params := PaymentParams {}
332
434
ct := r .Header .Get ("Content-Type" )
333
435
mediaType , _ , err := mime .ParseMediaType (ct )
@@ -362,7 +464,7 @@ func (a *API) PreauthorizePayment(w http.ResponseWriter, r *http.Request) error
362
464
if provider == nil {
363
465
return badRequestError ("Payment provider '%s' not configured" , providerType )
364
466
}
365
- preauthorize , err := provider .NewPreauthorizer (ctx , r )
467
+ preauthorize , err := provider .NewPreauthorizer (ctx , r , log . WithField ( "component" , "payment_provider" ) )
366
468
if err != nil {
367
469
return badRequestError ("Error creating payment provider: %v" , err )
368
470
}
@@ -426,7 +528,8 @@ func createPaymentProviders(c *conf.Configuration) (map[string]payments.Provider
426
528
provs := map [string ]payments.Provider {}
427
529
if c .Payment .Stripe .Enabled {
428
530
p , err := stripe .NewPaymentProvider (stripe.Config {
429
- SecretKey : c .Payment .Stripe .SecretKey ,
531
+ SecretKey : c .Payment .Stripe .SecretKey ,
532
+ UsePaymentIntents : c .Payment .Stripe .UsePaymentIntents ,
430
533
})
431
534
if err != nil {
432
535
return nil , err
0 commit comments