Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mellat payment gateway #41

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func main() {
- [x] vandar.io
- [ ] rayanpay
- [ ] nextpay
- [ ] mellat
- [x] mellat
- [ ] parsian
- [ ] pasargad
- [x] sadad
Expand Down
47 changes: 47 additions & 0 deletions _example/mellat/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"context"
"fmt"
"log"

"github.com/GoFarsi/paygap/client"
"github.com/GoFarsi/paygap/providers/mellat"
)

func main() {
c := client.New()
m, err := mellat.New(c, "username", "password")
if err != nil {
log.Fatal(err)
}

resp, err := m.CreateTransaction(
context.Background(),
mellat.NewPaymentRequest(
"1", // order id
1_000_000, //amount
"https://example.com/callback", // callback
"1", // payer id (user id)
),
)
if err != nil {
log.Fatal(err)
}

fmt.Println(resp)

verifyResp, err := m.VerifyTransaction(
context.Background(),
mellat.NewVerifyRequest(
"1", // order id
"SaleOrderId", // SaleOrderId PostForm parameter in callback
"SaleReferenceId", // SaleReferenceId PostForm parameter in callback
),
)
if err != nil {
log.Fatal(err)
}

fmt.Println(verifyResp)
}
82 changes: 82 additions & 0 deletions providers/mellat/mellat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mellat

import (
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
"net/http"

"github.com/GoFarsi/paygap/client"
"github.com/GoFarsi/paygap/status"
"google.golang.org/grpc/codes"
)

const mellatURL = "https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl"

// New create mellat object for create new request
func New(client client.Transporter, username, password string) (*Mellat, error) {
if client == nil {
return nil, status.ERR_CLIENT_IS_NIL
}
mellat := &Mellat{
client: client,
username: username,
password: password,
url: mellatURL,
}
if err := client.GetValidator().Struct(mellat); err != nil {
return nil, status.New(0, http.StatusBadRequest, codes.InvalidArgument, err.Error())
}
return mellat, nil
}

func (m *Mellat) CreateTransaction(ctx context.Context, req *paymentRequest) (*PaymentResponse, error) {
payload, err := req.raw(m.username, m.password)
if err != nil {
return nil, err
}
return request[*PaymentResponse]("POST", m.url, payload)
}

func (m *Mellat) VerifyTransaction(ctx context.Context, req *verifyRequest) (*VerifyResponse, error) {
payload, err := req.raw(m.username, m.password)
if err != nil {
return nil, err
}
return request[*VerifyResponse]("POST", m.url, payload)
}
func request[Rs response](
method, url string,
body []byte,
) (
response Rs,
err error,
) {
request, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
return response, err
}
request.Header.Set("Content-Type", "text/xml")
request.Header.Set("charset", "utf-8")
resp, err := http.DefaultClient.Do(request)
if err != nil {
return response, err
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return response, err
}
if err := xml.Unmarshal(responseBody, response); err != nil {
return response, fmt.Errorf("error raw response: %v", string(responseBody))
}
if resp.StatusCode != http.StatusOK|http.StatusCreated {
return response, status.New(response.ResponseCode(), http.StatusFailedDependency, codes.OK, fmt.Sprintf("response code %v", response.ResponseCode()))
}
if err := response.modifyResponse(); err != nil {
return response, err
}
return response, nil
}
193 changes: 193 additions & 0 deletions providers/mellat/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package mellat

import (
"encoding/xml"
"strconv"
"strings"
"time"

"github.com/GoFarsi/paygap/client"
)

type Mellat struct {
client client.Transporter
username string `validate:"required"`
password string `validate:"required"`

url string
}

type response interface {
ResponseCode() int
modifyResponse() error
}

type paymentRequest struct {
XMLName xml.Name `xml:"ns1:bpPayRequest"`
TerminalId string `xml:"terminalId"`
UserName string `xml:"userName"`
UserPassword string `xml:"userPassword"`
OrderId string `xml:"orderId"`
Amount uint64 `xml:"amount"`
LocalDate string `xml:"localDate"`
LocalTime string `xml:"localTime"`
CallBackUrl string `xml:"callBackUrl"`
PayerId string `xml:"payerId"`
}

func NewPaymentRequest(
orderId string,
amount uint64,
callBackUrl string,
payerId string,
) *paymentRequest {
return &paymentRequest{
OrderId: orderId,
Amount: amount,
LocalDate: time.Now().Format("20060402"),
LocalTime: time.Now().Format("150405"),
CallBackUrl: callBackUrl,
PayerId: payerId,
}
}

func (pr *paymentRequest) raw(
userId string,
password string,
) ([]byte, error) {
pr.TerminalId = userId
pr.UserName = userId
pr.UserPassword = password
root := newSoapRoot()
root.Body.Request = pr
return root.Marshal()
}

type PaymentResponse struct {
XMLName xml.Name `xml:"Envelope"`
Body struct {
XMLName xml.Name `xml:"Body"`
BpPay struct {
XMLName xml.Name `xml:"bpPayRequestResponse"`
Return string `xml:"return"`
}
}
responseCode int `xml:"-"`
refId string `xml:"-"`
}

func (pr *PaymentResponse) ResponseCode() int {
if pr == nil {
return 0
}
return pr.responseCode
}

func (pr *PaymentResponse) RefId() string {
return pr.refId
}

func (pr *PaymentResponse) modifyResponse() error {
params := strings.Split(pr.Body.BpPay.Return, ",")
if len(params) > 0 {
if params[0] == "0" {
pr.responseCode = -1
} else {
code, err := strconv.Atoi(params[0])
if err != nil {
return err
}
pr.responseCode = code
}
if len(params) > 1 {
pr.refId = params[1]
}
}
return nil
}

type verifyRequest struct {
XMLName xml.Name `xml:"ns1:bpVerifyRequest"`
TerminalId string `xml:"terminalId"`
UserName string `xml:"userName"`
Password string `xml:"userPassword"`
OrderId string `xml:"orderId"`
SaleOrderId string `xml:"saleOrderId"`
SaleReferenceId string `xml:"saleReferenceId"`
}

func NewVerifyRequest(
orderId string,
saleOrderId string,
saleReferenceId string,
) *verifyRequest {
return &verifyRequest{
OrderId: orderId,
SaleOrderId: saleOrderId,
SaleReferenceId: saleReferenceId,
}
}

func (vr *verifyRequest) raw(
userId string,
password string,
) ([]byte, error) {
vr.TerminalId = userId
vr.UserName = userId
vr.Password = password
root := newSoapRoot()
root.Body.Request = vr
return root.Marshal()
}

type VerifyResponse struct {
XMLName xml.Name `xml:"Envelope"`
Body struct {
XMLName xml.Name `xml:"Body"`
BpPay struct {
XMLName xml.Name `xml:"bpVerifyRequestResponse"`
Return string `xml:"return"`
}
}
responseCode int `xml:"-"`
}

func (vr *VerifyResponse) ResponseCode() int {
return vr.responseCode
}

func (vr *VerifyResponse) modifyResponse() error {
if vr.Body.BpPay.Return == "0" {
vr.responseCode = -1
return nil
}
code, err := strconv.Atoi(vr.Body.BpPay.Return)
if err != nil {
return err
}
vr.responseCode = code
return nil
}

type soapRoot struct {
XMLName xml.Name `xml:"x:Envelope"`
X string `xml:"xmlns:x,attr"`
Ns1 string `xml:"xmlns:ns1,attr"`
Body soapBody
}

func (r *soapRoot) Marshal() ([]byte, error) {
return xml.MarshalIndent(r, "", " ")
}

type soapBody struct {
XMLName xml.Name `xml:"x:Body"`
Request interface{}
}

func newSoapRoot() *soapRoot {
return &soapRoot{
X: "http://schemas.xmlsoap.org/soap/envelope/",
Ns1: "http://interfaces.core.sw.bps.com/",
}
}
Loading