-
Notifications
You must be signed in to change notification settings - Fork 772
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(paypal): add webhook support (#434)
* feat(paypal): add webhook support
- Loading branch information
Showing
4 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package paypal | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"github.com/go-pay/gopay" | ||
"net/http" | ||
) | ||
|
||
// CreateWebhook 创建Webhook | ||
func (c *Client) CreateWebhook(ctx context.Context, bm gopay.BodyMap) (ppRsp *CreateWebhookRsp, err error) { | ||
if err = bm.CheckEmptyError("url", "event_types"); nil != err { | ||
return nil, err | ||
} | ||
res, bs, err := c.doPayPalPost(ctx, bm, createWebhook) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &CreateWebhookRsp{Code: Success} | ||
ppRsp.Response = new(Webhook) | ||
if err = json.Unmarshal(bs, &ppRsp.Response); nil != err { | ||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) | ||
} | ||
if res.StatusCode != http.StatusCreated { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} | ||
|
||
// ListWebhook 查询Webhook列表 | ||
func (c *Client) ListWebhook(ctx context.Context) (ppRsp *ListWebhookRsp, err error) { | ||
res, bs, err := c.doPayPalGet(ctx, listWebhook) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &ListWebhookRsp{Code: Success} | ||
if err = json.Unmarshal(bs, &ppRsp.Response); nil != err { | ||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} | ||
|
||
// ShowWebhookDetail 查询Webhook | ||
func (c *Client) ShowWebhookDetail(ctx context.Context, webhookId string) (ppRsp *WebhookDetailRsp, err error) { | ||
url := fmt.Sprintf(showWebhookDetail, webhookId) | ||
res, bs, err := c.doPayPalGet(ctx, url) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &WebhookDetailRsp{Code: Success} | ||
if err = json.Unmarshal(bs, &ppRsp.Response); nil != err { | ||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} | ||
|
||
// UpdateWebhook 更新Webhook消息 | ||
func (c *Client) UpdateWebhook(ctx context.Context, webhookId string, patchs []*Patch) (ppRsp *WebhookDetailRsp, err error) { | ||
url := fmt.Sprintf(updateWebhook, webhookId) | ||
res, bs, err := c.doPayPalPatch(ctx, patchs, url) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &WebhookDetailRsp{Code: Success} | ||
if err = json.Unmarshal(bs, &ppRsp.Response); nil != err { | ||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} | ||
|
||
// DeleteWebhook 删除Webhook消息 | ||
func (c *Client) DeleteWebhook(ctx context.Context, webhookId string) (ppRsp *WebhookDetailRsp, err error) { | ||
url := fmt.Sprintf(deleteWebhook, webhookId) | ||
res, bs, err := c.doPayPalDelete(ctx, url) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &WebhookDetailRsp{Code: Success} | ||
if res.StatusCode != http.StatusNoContent { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} | ||
|
||
// VerifyWebhookSignature 验证Webhook签名 | ||
// 文档:https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature_post | ||
func (c *Client) VerifyWebhookSignature(ctx context.Context, bm gopay.BodyMap) (verifyRes *VerifyWebhookResponse, err error) { | ||
if err = bm.CheckEmptyError("auth_algo", "cert_url", "transmission_id", "transmission_sig", "transmission_time", "webhook_id", "webhook_event"); err != nil { | ||
return nil, err | ||
} | ||
res, bs, err := c.doPayPalPost(ctx, bm, verifyWebhookSignature) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
return verifyRes, errors.New("request paypal url[verify-webhook-signature_post] error") | ||
} | ||
verifyRes = &VerifyWebhookResponse{} | ||
if err = json.Unmarshal(bs, verifyRes); err != nil { | ||
return nil, fmt.Errorf("[%w]: %v, bytes: %s", gopay.UnmarshalErr, err, string(bs)) | ||
} | ||
return verifyRes, nil | ||
} | ||
|
||
// ShowWebhookEventDetail 查询Webhook-event消息 | ||
func (c *Client) ShowWebhookEventDetail(ctx context.Context, eventId string) (ppRsp *WebhookEventDetailRsp, err error) { | ||
url := fmt.Sprintf(showWebhookEventDetail, eventId) | ||
res, bs, err := c.doPayPalGet(ctx, url) | ||
if nil != err { | ||
return nil, err | ||
} | ||
ppRsp = &WebhookEventDetailRsp{Code: Success} | ||
if err = json.Unmarshal(bs, &ppRsp.Response); nil != err { | ||
return nil, fmt.Errorf("json.Unmarshal(%s):%w", string(bs), err) | ||
} | ||
if res.StatusCode != http.StatusOK { | ||
ppRsp.Code = res.StatusCode | ||
ppRsp.Error = string(bs) | ||
ppRsp.ErrorResponse = new(ErrorResponse) | ||
_ = json.Unmarshal(bs, ppRsp.ErrorResponse) | ||
} | ||
return ppRsp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package paypal | ||
|
||
import ( | ||
"github.com/go-pay/gopay" | ||
"github.com/go-pay/xlog" | ||
"testing" | ||
) | ||
|
||
var ( | ||
webhookId = "" | ||
replaceUrl = "" | ||
) | ||
|
||
func TestClient_CreateWebhook(t *testing.T) { | ||
url := "https://thahao-test.mynatapp.cc/pay_order/paypal" | ||
bm := make(gopay.BodyMap) | ||
|
||
var eventTypes []*WebhookEventType | ||
item := &WebhookEventType{ | ||
Name: "PAYMENT.CAPTURE.REFUNDED", | ||
} | ||
eventTypes = append(eventTypes, item) | ||
bm.Set("url", url). | ||
Set("event_types", eventTypes) | ||
ppRsp, err := client.CreateWebhook(client.ctx, bm) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
if ppRsp.Code != Success { | ||
xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) | ||
xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) | ||
xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) | ||
return | ||
} | ||
xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) | ||
} | ||
|
||
func TestClient_ListWebhook(t *testing.T) { | ||
ppRsp, err := client.ListWebhook(client.ctx) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
if ppRsp.Code != Success { | ||
xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) | ||
xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) | ||
xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) | ||
return | ||
} | ||
xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) | ||
} | ||
|
||
func TestClient_ShowWebhookDetail(t *testing.T) { | ||
ppRsp, err := client.ShowWebhookDetail(client.ctx, webhookId) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
if ppRsp.Code != Success { | ||
xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) | ||
xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) | ||
xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) | ||
return | ||
} | ||
xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) | ||
} | ||
|
||
func TestClient_UpdateWebhook(t *testing.T) { | ||
var ps []*Patch | ||
item := &Patch{ | ||
Op: "replace", | ||
Path: "/url", // reference_id is yourself set when create order | ||
Value: replaceUrl, | ||
} | ||
ps = append(ps, item) | ||
ppRsp, err := client.UpdateWebhook(client.ctx, webhookId, ps) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
if ppRsp.Code != Success { | ||
xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) | ||
xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) | ||
xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) | ||
return | ||
} | ||
xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) | ||
} | ||
|
||
func TestClient_DeleteWebhook(t *testing.T) { | ||
ppRsp, err := client.DeleteWebhook(client.ctx, webhookId) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
if ppRsp.Code != Success { | ||
xlog.Debugf("ppRsp.Code: %+v", ppRsp.Code) | ||
xlog.Debugf("ppRsp.Error: %+v", ppRsp.Error) | ||
xlog.Debugf("ppRsp.ErrorResponse: %+v", ppRsp.ErrorResponse) | ||
return | ||
} | ||
xlog.Debugf("ppRsp.Response: %+v", ppRsp.Response) | ||
} | ||
|
||
func TestClient_VerifyWebhookSignature(t *testing.T) { | ||
bm := make(gopay.BodyMap) | ||
bm.Set("auth_algo", "SHA256withRSA"). | ||
Set("cert_url", "https://api.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-ad47cb8d"). | ||
Set("transmission_id", "b9d46480-2162-11ee-a2ae-61fbe51a886c"). | ||
Set("transmission_sig", "NcbK6Mxok1iu12VU2bEgXUiFhifdX9eYlJJLtfc0etlVPgbigCZiQq3+Z8z7uNnCMh9S9rKjGr5eTscIHvUmB3jnPqUeLlGI3d670lXUkATH+p6Q/HI33ZidDAFTsgc3kZizqlONsPvmu5fdSA9UmKsaDmBEbACZXH/P4hTY4/pdAmk9OOPdySAhXj7gDwSz4ChMM0H+nSwXdyQC5IrjFQdoGABNoEPtRDUI7n0RCphu/kaZmQl7BtDXhoJAKYKmUS0pw4DhVW8hGoxBNrwizSW9eFE5tDhYO5WdGuWraGPKS5X/FD5JVfA2Kxj83rFvxHgyfKuYiMtnvevZVDp3Xg=="). | ||
Set("transmission_time", "2023-07-13T09:50:40Z"). | ||
Set("webhook_id", "3WA07241VT312694T"). | ||
SetBodyMap("webhook_event", func(b gopay.BodyMap) { | ||
b.Set("event_version", "1.0"). | ||
Set("resource_version", "2.0") | ||
}) | ||
|
||
xlog.Debug("bm:", bm.JsonBody()) | ||
verifyRes, err := client.VerifyWebhookSignature(ctx, bm) | ||
if err != nil { | ||
xlog.Error(err) | ||
return | ||
} | ||
xlog.Debugf("verifyRes: %+v", verifyRes) | ||
} |