@waffo/waffo-integrate 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/SKILL.md +462 -0
- package/bin/install.js +85 -0
- package/docs/INDEX.md +50 -0
- package/package.json +29 -0
- package/references/api-contract.md +539 -0
- package/references/go.md +688 -0
- package/references/java.md +615 -0
- package/references/node.md +576 -0
package/references/go.md
ADDED
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
# Go Integration Templates
|
|
2
|
+
|
|
3
|
+
## Module
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
github.com/waffo-com/waffo-go
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Install:
|
|
10
|
+
```bash
|
|
11
|
+
go get github.com/waffo-com/waffo-go@latest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## SDK Initialization
|
|
17
|
+
|
|
18
|
+
```go
|
|
19
|
+
// internal/waffo/client.go
|
|
20
|
+
package waffo
|
|
21
|
+
|
|
22
|
+
import (
|
|
23
|
+
"os"
|
|
24
|
+
"sync"
|
|
25
|
+
|
|
26
|
+
waffogo "github.com/waffo-com/waffo-go"
|
|
27
|
+
"github.com/waffo-com/waffo-go/config"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
var (
|
|
31
|
+
instance *waffogo.Waffo
|
|
32
|
+
once sync.Once
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
func GetClient() *waffogo.Waffo {
|
|
36
|
+
once.Do(func() {
|
|
37
|
+
env := config.Sandbox
|
|
38
|
+
if os.Getenv("WAFFO_ENVIRONMENT") == "PRODUCTION" {
|
|
39
|
+
env = config.Production
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cfg := config.WaffoConfig{
|
|
43
|
+
ApiKey: os.Getenv("WAFFO_API_KEY"),
|
|
44
|
+
PrivateKey: os.Getenv("WAFFO_PRIVATE_KEY"),
|
|
45
|
+
WaffoPublicKey: os.Getenv("WAFFO_PUBLIC_KEY"),
|
|
46
|
+
MerchantId: os.Getenv("WAFFO_MERCHANT_ID"),
|
|
47
|
+
Environment: env,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
instance = waffogo.NewWaffo(cfg)
|
|
51
|
+
})
|
|
52
|
+
return instance
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Order Payment
|
|
59
|
+
|
|
60
|
+
```go
|
|
61
|
+
// internal/waffo/payment.go
|
|
62
|
+
package waffo
|
|
63
|
+
|
|
64
|
+
import (
|
|
65
|
+
"context"
|
|
66
|
+
"fmt"
|
|
67
|
+
"strings"
|
|
68
|
+
|
|
69
|
+
"github.com/google/uuid"
|
|
70
|
+
"github.com/waffo-com/waffo-go/types/order"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
type CreatePaymentInput struct {
|
|
74
|
+
MerchantOrderId string
|
|
75
|
+
Amount string
|
|
76
|
+
Currency string
|
|
77
|
+
Description string
|
|
78
|
+
NotifyUrl string
|
|
79
|
+
SuccessRedirectUrl string
|
|
80
|
+
UserId string
|
|
81
|
+
UserEmail string
|
|
82
|
+
UserTerminal string // WEB | APP | WAP | SYSTEM (default: WEB)
|
|
83
|
+
PayMethodType string // optional: "CREDITCARD", "EWALLET"
|
|
84
|
+
PayMethodName string // optional: "CC_VISA", "DANA"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// genRequestID generates a 32-char request ID (UUID without dashes, max length 32)
|
|
88
|
+
func genRequestID() string {
|
|
89
|
+
return strings.ReplaceAll(uuid.New().String(), "-", "")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func CreatePayment(ctx context.Context, input CreatePaymentInput) (*order.CreateOrderData, error) {
|
|
93
|
+
client := GetClient()
|
|
94
|
+
|
|
95
|
+
userTerminal := input.UserTerminal
|
|
96
|
+
if userTerminal == "" {
|
|
97
|
+
userTerminal = "WEB"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
params := order.CreateOrderParams{
|
|
101
|
+
PaymentRequestId: genRequestID(),
|
|
102
|
+
MerchantOrderId: input.MerchantOrderId,
|
|
103
|
+
OrderCurrency: input.Currency,
|
|
104
|
+
OrderAmount: input.Amount,
|
|
105
|
+
OrderDescription: input.Description,
|
|
106
|
+
NotifyUrl: input.NotifyUrl,
|
|
107
|
+
SuccessRedirectUrl: input.SuccessRedirectUrl,
|
|
108
|
+
UserInfo: &order.UserInfo{
|
|
109
|
+
UserId: input.UserId,
|
|
110
|
+
UserEmail: input.UserEmail,
|
|
111
|
+
UserTerminal: userTerminal,
|
|
112
|
+
},
|
|
113
|
+
PaymentInfo: &order.PaymentInfo{
|
|
114
|
+
ProductName: "ONE_TIME_PAYMENT",
|
|
115
|
+
PayMethodType: input.PayMethodType,
|
|
116
|
+
PayMethodName: input.PayMethodName,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
resp, err := client.Order().Create(ctx, params)
|
|
121
|
+
if err != nil {
|
|
122
|
+
return nil, fmt.Errorf("create payment error: %w", err)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if !resp.IsSuccess() {
|
|
126
|
+
return nil, fmt.Errorf("create payment failed: code=%s, msg=%s",
|
|
127
|
+
resp.GetCode(), resp.GetMessage())
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
data := resp.GetData()
|
|
131
|
+
return &data, nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func QueryOrder(ctx context.Context, paymentRequestId string) (*order.InquiryOrderData, error) {
|
|
135
|
+
client := GetClient()
|
|
136
|
+
|
|
137
|
+
resp, err := client.Order().Inquiry(ctx, order.InquiryOrderParams{
|
|
138
|
+
PaymentRequestId: paymentRequestId,
|
|
139
|
+
})
|
|
140
|
+
if err != nil {
|
|
141
|
+
return nil, fmt.Errorf("inquiry error: %w", err)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if !resp.IsSuccess() {
|
|
145
|
+
return nil, fmt.Errorf("inquiry failed: code=%s, msg=%s",
|
|
146
|
+
resp.GetCode(), resp.GetMessage())
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
data := resp.GetData()
|
|
150
|
+
return &data, nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func CancelOrder(ctx context.Context, paymentRequestId string) (*order.CancelOrderData, error) {
|
|
154
|
+
client := GetClient()
|
|
155
|
+
|
|
156
|
+
resp, err := client.Order().Cancel(ctx, order.CancelOrderParams{
|
|
157
|
+
PaymentRequestId: paymentRequestId,
|
|
158
|
+
})
|
|
159
|
+
if err != nil {
|
|
160
|
+
return nil, fmt.Errorf("cancel error: %w", err)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if !resp.IsSuccess() {
|
|
164
|
+
return nil, fmt.Errorf("cancel failed: code=%s, msg=%s",
|
|
165
|
+
resp.GetCode(), resp.GetMessage())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
data := resp.GetData()
|
|
169
|
+
return &data, nil
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Refund
|
|
176
|
+
|
|
177
|
+
```go
|
|
178
|
+
// internal/waffo/refund.go
|
|
179
|
+
package waffo
|
|
180
|
+
|
|
181
|
+
import (
|
|
182
|
+
"context"
|
|
183
|
+
"fmt"
|
|
184
|
+
|
|
185
|
+
"strings"
|
|
186
|
+
|
|
187
|
+
"github.com/google/uuid"
|
|
188
|
+
"github.com/waffo-com/waffo-go/types/order"
|
|
189
|
+
"github.com/waffo-com/waffo-go/types/refund"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
func RefundOrder(ctx context.Context, origPaymentRequestId, refundAmount, refundReason string) (*order.RefundOrderData, error) {
|
|
193
|
+
client := GetClient()
|
|
194
|
+
|
|
195
|
+
params := order.RefundOrderParams{
|
|
196
|
+
RefundRequestId: strings.ReplaceAll(uuid.New().String(), "-", ""),
|
|
197
|
+
OrigPaymentRequestId: origPaymentRequestId,
|
|
198
|
+
RefundAmount: refundAmount,
|
|
199
|
+
RefundReason: refundReason,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
resp, err := client.Order().Refund(ctx, params)
|
|
203
|
+
if err != nil {
|
|
204
|
+
return nil, fmt.Errorf("refund error: %w", err)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if !resp.IsSuccess() {
|
|
208
|
+
return nil, fmt.Errorf("refund failed: code=%s, msg=%s",
|
|
209
|
+
resp.GetCode(), resp.GetMessage())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
data := resp.GetData()
|
|
213
|
+
return &data, nil
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func QueryRefund(ctx context.Context, refundRequestId string) (*refund.InquiryRefundData, error) {
|
|
217
|
+
client := GetClient()
|
|
218
|
+
|
|
219
|
+
resp, err := client.Refund().Inquiry(ctx, refund.InquiryRefundParams{
|
|
220
|
+
RefundRequestId: refundRequestId,
|
|
221
|
+
})
|
|
222
|
+
if err != nil {
|
|
223
|
+
return nil, fmt.Errorf("refund inquiry error: %w", err)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if !resp.IsSuccess() {
|
|
227
|
+
return nil, fmt.Errorf("refund inquiry failed: code=%s, msg=%s",
|
|
228
|
+
resp.GetCode(), resp.GetMessage())
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
data := resp.GetData()
|
|
232
|
+
return &data, nil
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Subscription
|
|
239
|
+
|
|
240
|
+
```go
|
|
241
|
+
// internal/waffo/subscription.go
|
|
242
|
+
package waffo
|
|
243
|
+
|
|
244
|
+
import (
|
|
245
|
+
"context"
|
|
246
|
+
"fmt"
|
|
247
|
+
|
|
248
|
+
"strings"
|
|
249
|
+
|
|
250
|
+
"github.com/google/uuid"
|
|
251
|
+
"github.com/waffo-com/waffo-go/types/subscription"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
type CreateSubscriptionInput struct {
|
|
255
|
+
MerchantSubscriptionId string
|
|
256
|
+
Amount string
|
|
257
|
+
Currency string
|
|
258
|
+
Description string
|
|
259
|
+
NotifyUrl string
|
|
260
|
+
UserId string
|
|
261
|
+
UserEmail string
|
|
262
|
+
ProductId string
|
|
263
|
+
ProductName string
|
|
264
|
+
PeriodType string // DAILY, WEEKLY, MONTHLY
|
|
265
|
+
PeriodInterval string
|
|
266
|
+
GoodsId string
|
|
267
|
+
GoodsName string
|
|
268
|
+
GoodsUrl string
|
|
269
|
+
SuccessRedirectUrl string
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func CreateSubscription(ctx context.Context, input CreateSubscriptionInput) (*subscription.CreateSubscriptionData, error) {
|
|
273
|
+
client := GetClient()
|
|
274
|
+
|
|
275
|
+
params := subscription.CreateSubscriptionParams{
|
|
276
|
+
SubscriptionRequest: strings.ReplaceAll(uuid.New().String(), "-", ""),
|
|
277
|
+
MerchantSubscriptionId: input.MerchantSubscriptionId,
|
|
278
|
+
Currency: input.Currency,
|
|
279
|
+
Amount: input.Amount,
|
|
280
|
+
OrderDescription: input.Description,
|
|
281
|
+
NotifyUrl: input.NotifyUrl,
|
|
282
|
+
SuccessRedirectUrl: input.SuccessRedirectUrl,
|
|
283
|
+
ProductInfo: &subscription.ProductInfo{
|
|
284
|
+
ProductId: input.ProductId,
|
|
285
|
+
ProductName: input.ProductName,
|
|
286
|
+
Description: input.Description,
|
|
287
|
+
PeriodType: input.PeriodType,
|
|
288
|
+
PeriodInterval: input.PeriodInterval,
|
|
289
|
+
},
|
|
290
|
+
GoodsInfo: &subscription.GoodsInfo{
|
|
291
|
+
GoodsId: input.GoodsId,
|
|
292
|
+
GoodsName: input.GoodsName,
|
|
293
|
+
GoodsUrl: input.GoodsUrl,
|
|
294
|
+
},
|
|
295
|
+
UserInfo: &subscription.UserInfo{
|
|
296
|
+
UserId: input.UserId,
|
|
297
|
+
UserEmail: input.UserEmail,
|
|
298
|
+
UserTerminal: input.UserTerminal, // WEB for PC, APP for mobile/tablet
|
|
299
|
+
},
|
|
300
|
+
PaymentInfo: &subscription.PaymentInfo{
|
|
301
|
+
ProductName: "SUBSCRIPTION",
|
|
302
|
+
PayMethodType: "CREDITCARD,DEBITCARD,APPLEPAY,GOOGLEPAY",
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resp, err := client.Subscription().Create(ctx, params)
|
|
307
|
+
if err != nil {
|
|
308
|
+
return nil, fmt.Errorf("create subscription error: %w", err)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if !resp.IsSuccess() {
|
|
312
|
+
return nil, fmt.Errorf("create subscription failed: code=%s, msg=%s",
|
|
313
|
+
resp.GetCode(), resp.GetMessage())
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
data := resp.GetData()
|
|
317
|
+
return &data, nil
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
func QuerySubscription(ctx context.Context, subscriptionRequest string) (*subscription.InquirySubscriptionData, error) {
|
|
321
|
+
client := GetClient()
|
|
322
|
+
|
|
323
|
+
resp, err := client.Subscription().Inquiry(ctx, subscription.InquirySubscriptionParams{
|
|
324
|
+
SubscriptionRequest: subscriptionRequest,
|
|
325
|
+
})
|
|
326
|
+
if err != nil {
|
|
327
|
+
return nil, fmt.Errorf("inquiry error: %w", err)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if !resp.IsSuccess() {
|
|
331
|
+
return nil, fmt.Errorf("inquiry failed: code=%s, msg=%s",
|
|
332
|
+
resp.GetCode(), resp.GetMessage())
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
data := resp.GetData()
|
|
336
|
+
return &data, nil
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
func CancelSubscription(ctx context.Context, subscriptionRequest string) (*subscription.CancelSubscriptionData, error) {
|
|
340
|
+
client := GetClient()
|
|
341
|
+
|
|
342
|
+
resp, err := client.Subscription().Cancel(ctx, subscription.CancelSubscriptionParams{
|
|
343
|
+
SubscriptionRequest: subscriptionRequest,
|
|
344
|
+
})
|
|
345
|
+
if err != nil {
|
|
346
|
+
return nil, fmt.Errorf("cancel error: %w", err)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if !resp.IsSuccess() {
|
|
350
|
+
return nil, fmt.Errorf("cancel failed: code=%s, msg=%s",
|
|
351
|
+
resp.GetCode(), resp.GetMessage())
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
data := resp.GetData()
|
|
355
|
+
return &data, nil
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ManageSubscription returns a management URL for the subscription.
|
|
359
|
+
// Only works when the subscription is ACTIVE.
|
|
360
|
+
func ManageSubscription(ctx context.Context, subscriptionRequest string) (string, error) {
|
|
361
|
+
client := GetClient()
|
|
362
|
+
|
|
363
|
+
params := subscription.ManageSubscriptionParams{
|
|
364
|
+
SubscriptionRequest: subscriptionRequest,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
resp, err := client.Subscription().Manage(ctx, params)
|
|
368
|
+
if err != nil {
|
|
369
|
+
return "", fmt.Errorf("manage error: %w", err)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if !resp.IsSuccess() {
|
|
373
|
+
return "", fmt.Errorf("manage failed: code=%s, msg=%s",
|
|
374
|
+
resp.GetCode(), resp.GetMessage())
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
managementURL := resp.GetData().ManagementURL
|
|
378
|
+
return managementURL, nil
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Webhook Handler
|
|
385
|
+
|
|
386
|
+
### Gin
|
|
387
|
+
|
|
388
|
+
```go
|
|
389
|
+
// internal/waffo/webhook.go
|
|
390
|
+
package waffo
|
|
391
|
+
|
|
392
|
+
import (
|
|
393
|
+
"io"
|
|
394
|
+
"net/http"
|
|
395
|
+
|
|
396
|
+
"github.com/gin-gonic/gin"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
func WebhookHandler() gin.HandlerFunc {
|
|
400
|
+
return func(c *gin.Context) {
|
|
401
|
+
client := GetClient()
|
|
402
|
+
|
|
403
|
+
body, err := io.ReadAll(c.Request.Body)
|
|
404
|
+
if err != nil {
|
|
405
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read body"})
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
signature := c.GetHeader("X-SIGNATURE")
|
|
410
|
+
|
|
411
|
+
handler := client.Webhook().
|
|
412
|
+
OnPayment(func(notification map[string]interface{}) {
|
|
413
|
+
result, _ := notification["result"].(map[string]interface{})
|
|
414
|
+
// TODO: Update your order status in database
|
|
415
|
+
_ = result
|
|
416
|
+
}).
|
|
417
|
+
OnRefund(func(notification map[string]interface{}) {
|
|
418
|
+
result, _ := notification["result"].(map[string]interface{})
|
|
419
|
+
// TODO: Update your refund status in database
|
|
420
|
+
_ = result
|
|
421
|
+
}).
|
|
422
|
+
OnSubscriptionStatus(func(notification map[string]interface{}) {
|
|
423
|
+
result, _ := notification["result"].(map[string]interface{})
|
|
424
|
+
// TODO: Update your subscription status in database
|
|
425
|
+
_ = result
|
|
426
|
+
}).
|
|
427
|
+
OnSubscriptionPeriodChanged(func(notification map[string]interface{}) {
|
|
428
|
+
result, _ := notification["result"].(map[string]interface{})
|
|
429
|
+
// TODO: Record billing period result
|
|
430
|
+
_ = result
|
|
431
|
+
}).
|
|
432
|
+
OnSubscriptionChange(func(notification map[string]interface{}) {
|
|
433
|
+
result, _ := notification["result"].(map[string]interface{})
|
|
434
|
+
// TODO: Handle subscription upgrade/downgrade
|
|
435
|
+
_ = result
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
webhookResult := handler.HandleWebhook(string(body), signature)
|
|
439
|
+
|
|
440
|
+
c.Header("X-SIGNATURE", webhookResult.ResponseSignature)
|
|
441
|
+
c.String(http.StatusOK, webhookResult.ResponseBody)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Echo
|
|
447
|
+
|
|
448
|
+
```go
|
|
449
|
+
// internal/waffo/webhook_echo.go
|
|
450
|
+
package waffo
|
|
451
|
+
|
|
452
|
+
import (
|
|
453
|
+
"io"
|
|
454
|
+
"net/http"
|
|
455
|
+
|
|
456
|
+
"github.com/labstack/echo/v4"
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
func WebhookEchoHandler(c echo.Context) error {
|
|
460
|
+
client := GetClient()
|
|
461
|
+
|
|
462
|
+
body, err := io.ReadAll(c.Request().Body)
|
|
463
|
+
if err != nil {
|
|
464
|
+
return c.JSON(http.StatusBadRequest, map[string]string{"error": "failed to read body"})
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
signature := c.Request().Header.Get("X-SIGNATURE")
|
|
468
|
+
|
|
469
|
+
handler := client.Webhook().
|
|
470
|
+
OnPayment(func(notification map[string]interface{}) {
|
|
471
|
+
// TODO: Handle payment
|
|
472
|
+
}).
|
|
473
|
+
OnRefund(func(notification map[string]interface{}) {
|
|
474
|
+
// TODO: Handle refund
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
webhookResult := handler.HandleWebhook(string(body), signature)
|
|
478
|
+
|
|
479
|
+
c.Response().Header().Set("X-SIGNATURE", webhookResult.ResponseSignature)
|
|
480
|
+
return c.String(http.StatusOK, webhookResult.ResponseBody)
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Fiber
|
|
485
|
+
|
|
486
|
+
```go
|
|
487
|
+
// internal/waffo/webhook_fiber.go
|
|
488
|
+
package waffo
|
|
489
|
+
|
|
490
|
+
import (
|
|
491
|
+
"github.com/gofiber/fiber/v2"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
func WebhookFiberHandler(c *fiber.Ctx) error {
|
|
495
|
+
client := GetClient()
|
|
496
|
+
|
|
497
|
+
body := string(c.Body())
|
|
498
|
+
signature := c.Get("X-SIGNATURE")
|
|
499
|
+
|
|
500
|
+
handler := client.Webhook().
|
|
501
|
+
OnPayment(func(notification map[string]interface{}) {
|
|
502
|
+
// TODO: Handle payment
|
|
503
|
+
}).
|
|
504
|
+
OnRefund(func(notification map[string]interface{}) {
|
|
505
|
+
// TODO: Handle refund
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
webhookResult := handler.HandleWebhook(body, signature)
|
|
509
|
+
|
|
510
|
+
c.Set("X-SIGNATURE", webhookResult.ResponseSignature)
|
|
511
|
+
return c.SendString(webhookResult.ResponseBody)
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Test Template (Sandbox Integration)
|
|
518
|
+
|
|
519
|
+
```go
|
|
520
|
+
// internal/waffo/waffo_test.go
|
|
521
|
+
package waffo_test
|
|
522
|
+
|
|
523
|
+
import (
|
|
524
|
+
"context"
|
|
525
|
+
"os"
|
|
526
|
+
"strings"
|
|
527
|
+
"testing"
|
|
528
|
+
|
|
529
|
+
"github.com/google/uuid"
|
|
530
|
+
waffogo "github.com/waffo-com/waffo-go"
|
|
531
|
+
"github.com/waffo-com/waffo-go/config"
|
|
532
|
+
"github.com/waffo-com/waffo-go/types/order"
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
func hasCredentials() bool {
|
|
536
|
+
return os.Getenv("WAFFO_API_KEY") != "" &&
|
|
537
|
+
os.Getenv("WAFFO_PRIVATE_KEY") != "" &&
|
|
538
|
+
os.Getenv("WAFFO_PUBLIC_KEY") != "" &&
|
|
539
|
+
os.Getenv("WAFFO_MERCHANT_ID") != ""
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
func newTestClient(t *testing.T) *waffogo.Waffo {
|
|
543
|
+
t.Helper()
|
|
544
|
+
if !hasCredentials() {
|
|
545
|
+
t.Skip("Waffo credentials not configured, skipping Sandbox test")
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
cfg := config.WaffoConfig{
|
|
549
|
+
ApiKey: os.Getenv("WAFFO_API_KEY"),
|
|
550
|
+
PrivateKey: os.Getenv("WAFFO_PRIVATE_KEY"),
|
|
551
|
+
WaffoPublicKey: os.Getenv("WAFFO_PUBLIC_KEY"),
|
|
552
|
+
MerchantId: os.Getenv("WAFFO_MERCHANT_ID"),
|
|
553
|
+
Environment: config.Sandbox,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return waffogo.NewWaffo(cfg)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
func genRequestID() string {
|
|
560
|
+
return strings.ReplaceAll(uuid.New().String(), "-", "")
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
func TestCreateOrder(t *testing.T) {
|
|
564
|
+
client := newTestClient(t)
|
|
565
|
+
ctx := context.Background()
|
|
566
|
+
paymentRequestID := genRequestID()
|
|
567
|
+
|
|
568
|
+
params := order.CreateOrderParams{
|
|
569
|
+
PaymentRequestId: paymentRequestID,
|
|
570
|
+
MerchantOrderId: "test-go-" + uuid.New().String()[:8],
|
|
571
|
+
OrderCurrency: "USD",
|
|
572
|
+
OrderAmount: "1.00",
|
|
573
|
+
OrderDescription: "Go integration test order",
|
|
574
|
+
NotifyUrl: "https://example.com/webhook",
|
|
575
|
+
SuccessRedirectUrl: "https://example.com/success",
|
|
576
|
+
UserInfo: &order.UserInfo{
|
|
577
|
+
UserId: "test-user",
|
|
578
|
+
UserEmail: "test@example.com",
|
|
579
|
+
UserTerminal: "WEB",
|
|
580
|
+
},
|
|
581
|
+
PaymentInfo: &order.PaymentInfo{
|
|
582
|
+
ProductName: "ONE_TIME_PAYMENT",
|
|
583
|
+
},
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
resp, err := client.Order().Create(ctx, params)
|
|
587
|
+
if err != nil {
|
|
588
|
+
t.Fatalf("Create order error: paymentRequestID=%s, err=%v", paymentRequestID, err)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if !resp.IsSuccess() {
|
|
592
|
+
t.Fatalf("Create order failed: paymentRequestID=%s, code=%s, msg=%s, data=%+v",
|
|
593
|
+
paymentRequestID, resp.GetCode(), resp.GetMessage(), resp.GetData())
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
data := resp.GetData()
|
|
597
|
+
if data.AcquiringOrderId == "" {
|
|
598
|
+
t.Fatal("AcquiringOrderId is empty")
|
|
599
|
+
}
|
|
600
|
+
t.Logf("Order created: acquiringOrderId=%s", data.AcquiringOrderId)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
func TestQueryOrder(t *testing.T) {
|
|
604
|
+
client := newTestClient(t)
|
|
605
|
+
ctx := context.Background()
|
|
606
|
+
paymentRequestID := genRequestID()
|
|
607
|
+
|
|
608
|
+
// Create first
|
|
609
|
+
_, err := client.Order().Create(ctx, order.CreateOrderParams{
|
|
610
|
+
PaymentRequestId: paymentRequestID,
|
|
611
|
+
MerchantOrderId: "test-go-" + uuid.New().String()[:8],
|
|
612
|
+
OrderCurrency: "USD",
|
|
613
|
+
OrderAmount: "1.00",
|
|
614
|
+
OrderDescription: "Test",
|
|
615
|
+
NotifyUrl: "https://example.com/webhook",
|
|
616
|
+
SuccessRedirectUrl: "https://example.com/success",
|
|
617
|
+
UserInfo: &order.UserInfo{UserId: "test-user", UserEmail: "test@example.com", UserTerminal: "WEB"},
|
|
618
|
+
PaymentInfo: &order.PaymentInfo{ProductName: "ONE_TIME_PAYMENT"},
|
|
619
|
+
})
|
|
620
|
+
if err != nil {
|
|
621
|
+
t.Fatalf("Create order error: %v", err)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Then query
|
|
625
|
+
resp, err := client.Order().Inquiry(ctx, order.InquiryOrderParams{
|
|
626
|
+
PaymentRequestId: paymentRequestID,
|
|
627
|
+
})
|
|
628
|
+
if err != nil {
|
|
629
|
+
t.Fatalf("Inquiry error: paymentRequestID=%s, err=%v", paymentRequestID, err)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if !resp.IsSuccess() {
|
|
633
|
+
t.Fatalf("Inquiry failed: paymentRequestID=%s, code=%s, msg=%s",
|
|
634
|
+
paymentRequestID, resp.GetCode(), resp.GetMessage())
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Merchant Config & Payment Method Query
|
|
642
|
+
|
|
643
|
+
```go
|
|
644
|
+
// internal/waffo/config_query.go
|
|
645
|
+
package waffo
|
|
646
|
+
|
|
647
|
+
import (
|
|
648
|
+
"context"
|
|
649
|
+
"fmt"
|
|
650
|
+
|
|
651
|
+
"github.com/waffo-com/waffo-go/types/merchant"
|
|
652
|
+
"github.com/waffo-com/waffo-go/types/paymethod"
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
func GetMerchantConfig(ctx context.Context) (*merchant.InquiryMerchantConfigData, error) {
|
|
656
|
+
client := GetClient()
|
|
657
|
+
|
|
658
|
+
resp, err := client.MerchantConfig().Inquiry(ctx, merchant.InquiryMerchantConfigParams{})
|
|
659
|
+
if err != nil {
|
|
660
|
+
return nil, fmt.Errorf("merchant config error: %w", err)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if !resp.IsSuccess() {
|
|
664
|
+
return nil, fmt.Errorf("merchant config failed: code=%s, msg=%s",
|
|
665
|
+
resp.GetCode(), resp.GetMessage())
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
data := resp.GetData()
|
|
669
|
+
return &data, nil
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
func GetPaymentMethods(ctx context.Context) (*paymethod.InquiryPayMethodConfigData, error) {
|
|
673
|
+
client := GetClient()
|
|
674
|
+
|
|
675
|
+
resp, err := client.PayMethodConfig().Inquiry(ctx, paymethod.InquiryPayMethodConfigParams{})
|
|
676
|
+
if err != nil {
|
|
677
|
+
return nil, fmt.Errorf("pay method error: %w", err)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if !resp.IsSuccess() {
|
|
681
|
+
return nil, fmt.Errorf("pay method failed: code=%s, msg=%s",
|
|
682
|
+
resp.GetCode(), resp.GetMessage())
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
data := resp.GetData()
|
|
686
|
+
return &data, nil
|
|
687
|
+
}
|
|
688
|
+
```
|