Skip to content

Commit

Permalink
feat: add mellat payment gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
aamirmousavi committed Apr 8, 2024
1 parent 1be58bb commit 7f1099c
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 1 deletion.
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/",
}
}

0 comments on commit 7f1099c

Please sign in to comment.