payment

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2022 License: MIT Imports: 5 Imported by: 0

README

go-payment

Payment module used as proxy for multiple payment gateways. Currently it only supports Midtrans SNAP and Xendit Ewallet and XenInvoice. Support for other channels will be added incrementally.

This payment proxy is a payment service I used for my personal site. Thinking that this might be useful to help other people so that they can start accept money ASAP, so I decided to make this module open source.


View table of contents

Payment Channels Supported

In general, this payment proxy can support payment through this following channels:

  • Recurring payment with Credit Card, OVO, Bank Transfer
  • Credit card payment with/without installment
  • Ewallet (GoPay, OVO, Dana, LinkAja)
  • Retail Outlet (Alfamart, Alfamidi, Dan+Dan)
  • Cardless Credit (Akulaku)
  • Bank Transfer via Virtual Account (BCA, BNI, BRI, Mandiri, Permata, Other Bank).

❗ Recurring payment is only supported via XenditInvoice.

Why you should use this payment proxy?

  • If you are planning to use Midtrans SNAP and Xendit Invoice as the UI for the payment, you are strongly encouraged to use this proxy because it supports both UIs.
  • This proxy helps you managing the payment gateway used for each channel. It internally connects to both payment gateway as you need, in no time. What your API user knows is only one single API to generate Invoice
  • This proxy helps you seemlesly switch the gateway for a payment channel whenever one of them is not functioning properly/down for maintenance. For instance, Bank Transfer by VA, are supported by Midtrans and Xendit. If Midtrans VA is going south, you can easily switch the gateway to Xendit simply by updating the configuration file.
  • You can choose whether to absorb the admin/installment fees by yourself or to off load it to your user by changing the payment configuration written in yaml.
  • This proxy can generate Invoice storing informations about the customer info, item, payment method selected, and its state. Invoice state will change over the time depends on the transaction status callback sent by payment gateway.
  • You can opt-in to store payment notification callback to your database. Currently it only stores midtrans transaction status. Support for xendit will be added soon.

Current Limitations

  1. For simplify the query creation for database join, I use gorm.io as the ORM library.
  2. This proxy is not made for supporting all use cases available out there. It's hard requirement is just so that people can accept payment with as low effort as possible without need to worry about custom UI flow.
  3. No callback trigger at least of now once the payment manager is done procesing this request. This will be the next priority of the next release. This issue is documented here
  4. Callback or redirect URL is globally configured. This means, you cant configure callback for each request differently on the fly.

Implemented Channels

This tables shows which payment channels that has been implemented by this proxy.

✅ : ready

❗ : in progress

❌ : not yet supported natively by payment gateway

Channels Midtrans (Snap) Xendit (ewallet/XenInvoice)
Credit Card without installment
Credit Card with installment
BCA VA
Mandiri VA
BNI VA
Permata VA
Other VA
BRI VA
Alfamart, Alfamidi, Dan+Dan
QRIS
Gopay
OVO
DANA
LinkAja
ShopeePay
Akulaku
Kredivo

Getting Started

Here some preparations that you might need before using this proxy.

Payment Gateway Registration

This can be tricky. If you have personal business, this might be easier. If you have business entity (PT, CV, etc), there are some additional processes you have to follow and some documents that you have to provide. In this context, I will just assume that you have personal business like what I do: imrenagi.com

Midtrans

Please review this page before creating an account.

Xendit

Please visit this registration page for creating an account.

Midtrans VS Xendit Onboarding

Here is the comparison between Midtrans and Xendit onboarding based on my onboarding experience.

Criteria Midtrans Xendit
Document to provide for registration KTP, NPWP KTP, NPWP
Cooperation Agreement (Perjanjian Kerja Sama) Online Signing Paper Signing and use Legalized Stamp
Active channels after agreement is signed Gopay, Bank Transfer Bank Transfer, Credit Card
OVO, LinkAja, Dana activation n/a Fill additional forms on the dashboard. Activation varies between weeks - months
Alfamart activation Require additional request and midtrans review for the activation No need to be PT, CV. Just fill and sign additional form on the dashboard. Might take weeks or months.
Credit card activation Require additional request and midtrans review for the activation Immediately activated after document sign
Disbursement feature Not included on the same PKS. Need to contact IRIS team for new agreement, activation and onboarding Immediately activated after document sign
Akulaku activation Might require business entity (PT, CV) n/a
Kredivo activation n/a Ask your account manager to activate this payment method
API Documentation Available Available
Golang SDK Available Available, but under development. Expect breaking changes in newer version

Payment Gateway Callback

Since this library is just providing the http.Handler, you can choose the REST API endpoint used by each callback. You can check the example on server.go.

Some Xendit Legacy Ewallet API(s) require you to set callback and redirect URL on the body request. You can override this value by using environment variables. Go to Mandatory Environment Variables.

Midtrans

To set your callback URL,

  • Login to https://dashboard.midtrans.com
  • Choose environment (Sandbox or Production)
  • Click Settings > Configuration
  • Set your Payment Notification URL with your server callback. For instance: https://api.imrenagi.com/payment/midtrans/callback
  • Set your Finish, Unfinish, and Error redirect URL
  • Click Update
Xendit

To set your callback URL,

  • Login to https://dashboard.xendit.co
  • Choose environment (Live or Test)
  • Click Settings > Callbacks
  • Set your callbacks for Invoices Paid. For instance: https://api.imrenagi.com/payment/xendit/invoice/callback
  • Check option Also notify my application when an invoice is expired
  • Click Save and Test

LinkAja and DANA callback URL are not defined on xendit dashboard. Instead, they are given while the proxy is initiating the payment request to Xendit API. You can find the callback URL set on linkaja.go and dana.go

Application Secret

Before using this application, you might need to update secret.yaml file containing application secret like database and payment gateway credential.

Application Config

As of now, application config stores configuration about which API that you would like to use for Xendit ewallet payments such as Dana, OVO, and LinkAja. Please check config.yaml.

xendit:
  ewallet:
    ovo:
      invoice: false
      legacy: false
  • xendit.ewallet.[ewallet].invoice set to true if you want to use XenInvoice instead of using direct API integration
  • xendit.ewallet.[ewallet].legacy set to true if you want to use legacy Xendit Ewallet API. Note that this API will be deprecated by first quarter of 2022.
Database

I removed MySQL as default database for this library. This library only accept instance of gorm.DB for database. Thus, you can use any database you like and provide the gorm.DB instance of chosen database.

For more, please check server.go

Midtrans Credential
  • Login to https://dashboard.midtrans.com
  • Choose environment (Sandbox or Production)
  • Click Settings > Access Keys
  • Grab the credentials, and update the secret.yaml
payment:
  midtrans:
    secretKey: "midtrans-server-secret"
    clientKey: "midtrans-client-key"
    clientId: "midtrans-merchant-id"
Xendit Credential
  • Login to https://dashboard.xendit.co
  • Choose environment (Live or Test)
  • Click Settings > API Keys > Generate secret key
  • Add key Name. Grant write permission for both Money-in products
  • Take the generated API Keys and Verification Callback Token, update the secret.yaml
payment:
  ...
  xendit:
    secretKey: "xendit-api-key"
    callbackToken: "xendit-callback-token"

Configuration File

You can take a look sample configuration file named payment-methods.yml. For instance:

card_payment:
  payment_type: "credit_card"
  installments:
    - type: offline
      display_name: ""
      gateway: midtrans
      bank: bca
      channel: migs
      default: true
      active: true
      terms:
        - term: 0
          admin_fee:
            IDR:
              val_percentage: 2.9
              val_currency: 2000
              currency: "IDR"
        - term: 3
          installment_fee:
            IDR:
              val_percentage: 5.5
              val_currency: 2200
              currency: "IDR"

With above configuration, for installment offline with bca, you can apply this following fees to the invoice after user generates new invoice:

  1. 2.9% + IDR 2000 admin fee for credit card transaction without any installment, or
  2. 5.5% + IDR 2200 installment fee for credit card transaction with installment for 3 month tenure.

If you want to absorb the fee, you can simply set val_percentage and val_currency as 0

If you only want to apply fee just either by using val_pecentage or val_currency, simply set the value to one of them and give 0 to the other. For instance:

bank_transfers:
  - gateway: midtrans
    payment_type: "bca_va"
    display_name: "BCA"
    admin_fee:
      IDR:
        val_percentage: 0
        val_currency: 4000
        currency: "IDR"

admin_fee and installment_fee are optional key.

Mandatory Environment Variables

You need to set these environment variables to make sure this proxy to work.

Environment Variable Required Description Example
ENVIRONMENT yes decide whether the server is for testing or production. For production, use prod. prod
LOG_LEVEL no Log level. Default to DEBUG. Available values: DEBUG, INFO, WARN, ERROR DEBUG
INVOICE_SUCCESS_REDIRECT_URL yes Redirect URL used by xendit if invoice is successfully paid http://example.com/donate/thanks
INVOICE_FAILED_REDIRECT_URL yes Redirect URL used by xendit if invoice is failed http://example.com/donate/error
DANA_LEGACY_CALLBACK_URL yes, if you are using legacy ewallet xendit API Callback URL used for xendit legacy ewallet API to send payment callback http://api.example.com/payment/xendit/dana/callback
DANA_LEGACY_REDIRECT_URL yes, if you are using legacy ewallet xendit API Redirect URL used by xendit legacy ewallet API to redirect user after payment succeeded http://example.com/donate/thanks
LINKAJA_LEGACY_CALLBACK_URL yes, if you are using legacy ewallet xendit API Callback URL used for xendit legacy ewallet API to send payment callback http://api.example.com/payment/xendit/linkaja/callback
LINKAJA_LEGACY_REDIRECT_URL yes, if you are using legacy ewallet xendit API Redirect URL used by xendit legacy ewallet API to redirect user after payment succeeded http://example.com/donate/thanks
RECURRING_SUCCESS_REDIRECT_URL yes, if you are using subscription feature Redirect URL used by xendit subscription API to redirect user after payment succeeded http://example.com/donate/thanks
RECURRING_FAILED_REDIRECT_URL yes, if you are using subscription feature Redirect URL used by xendit subscription API to redirect user after payment failed http://example.com/donate/error
DANA_SUCCESS_REDIRECT_URL yes, if you are using new xendit ewallet API Redirect URL used by xendit new ewallet API if payment with dana is success http://example.com/success
LINKAJA_SUCCESS_REDIRECT_URL yes, if you are using new xendit ewallet API Redirect URL used by xendit new ewallet API if payment with dana is failed http://example.com/success

Example Code

You can find the sample code in here

API Usage

You can find the details of API usage in here

Contributing

No rules for now. Feel free to add issue first and optionally submit a PR. Cheers

License

MIT. Copyright 2022 Imre Nagi

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound used if a resource is searched for is missing/not found
	ErrNotFound = fmt.Errorf("not found")
	// ErrInternal used if any internal errors call etc is happened
	ErrInternal = fmt.Errorf("internal error")
	// ErrDatabase used if there is some issue with database
	ErrDatabase = fmt.Errorf("database error")
	// ErrBadRequest used if a user/caller sent the wrong request parameters
	ErrBadRequest = fmt.Errorf("bad request")
	// ErrCantProceed used if a function call is violating some state/logic
	ErrCantProceed = fmt.Errorf("can't proceed")
	// ErrUnauthorized used if a caller is not authenticated
	ErrUnauthorized = fmt.Errorf("unauthorized access")
	// ErrForbidden used if a caller is not authorized to access a resource
	ErrForbidden = fmt.Errorf("forbidden access")
)

Functions

This section is empty.

Types

type Bank

type Bank string

Bank is a bank

const (
	BankBCA Bank = "bca"
	BankBNI Bank = "bni"
	BankBRI Bank = "bri"
)

type CreditCard

type CreditCard struct {
	Bank        Bank
	Installment Installment
}

CreditCard tells information about the acquire bank and installment used

type Gateway

type Gateway int

Gateway represent payment gateway used

const (
	// UnknownGateway gateway is unknown
	UnknownGateway Gateway = iota
	// GatewayMidtrans is midtrans payment gateway
	GatewayMidtrans
	// GatewayXendit is xendit payment gateway
	GatewayXendit
)

func NewGateway

func NewGateway(name string) Gateway

NewGateway return a gateway for its string name

func (Gateway) String

func (g Gateway) String() string

func (*Gateway) UnmarshalYAML

func (g *Gateway) UnmarshalYAML(unmarshal func(interface{}) error) error

UnmarshalYAML convert string to Gateway enum

type Installment

type Installment struct {
	Type InstallmentType
	Term int
}

Installment tells installent type and term

type InstallmentType

type InstallmentType string

InstallmentType shows the type of installment.

const (
	// InstallmentOnline used if the cardholder's card is the same as the the bank providing the installment
	InstallmentOnline InstallmentType = "online"
	// InstallmentOffline used if the cardholders's card might not be the same as the bank providing the installment
	InstallmentOffline InstallmentType = "offline"
)

type Model

type Model struct {
	ID        uint64     `json:"id" gorm:"primary_key"`
	CreatedAt time.Time  `json:"created_at" gorm:"not null;"`
	UpdatedAt time.Time  `json:"updated_at" gorm:"not null;"`
	DeletedAt *time.Time `json:"deleted_at" sql:"index"`
}

Model is base for database struct

type Money

type Money struct {
	Value         float64 `json:"value"`
	ValuePerMonth float64 `json:"value_per_month,omitempty"`
	Currency      string  `json:"curency"`
}

Money is just notation for showing the money value and its currency

type Option

type Option func(*Options)

Option is type closure accepting a Options

func NewPaymentMethodListOptions

func NewPaymentMethodListOptions(r *http.Request) ([]Option, error)

NewPaymentMethodListOptions accepts http.Request and returns set of option containing the price and its currency.

func WithCreditCard

func WithCreditCard(bank Bank, installmentType InstallmentType, installmentTerm int) Option

WithCreditCard can be used if user want to use the installment feature. It accepts the acquire bank, installment type and term

func WithPrice

func WithPrice(price float64, currency string) Option

WithPrice can be used if user want to add optional price information used for estimating the admin/installment fee

type Options

type Options struct {
	Price      *Money
	CreditCard *CreditCard
}

Options stores all optional properties for payment purposes

type PaymentType

type PaymentType string

PaymentType represent the payment method name

const (
	SourceCreditCard PaymentType = "credit_card"
	SourceBNIVA      PaymentType = "bni_va"
	SourcePermataVA  PaymentType = "permata_va"
	SourceBCAVA      PaymentType = "bca_va"
	SourceOtherVA    PaymentType = "other_va"
	SourceAlfamart   PaymentType = "alfamart"
	SourceGopay      PaymentType = "gopay"
	SourceAkulaku    PaymentType = "akulaku"
	SourceOvo        PaymentType = "ovo"
	SourceDana       PaymentType = "dana"
	SourceLinkAja    PaymentType = "linkaja"
	SourceShopeePay  PaymentType = "shopeepay"
	SourceQRIS       PaymentType = "qris"
	SourceBRIVA      PaymentType = "bri_va"
	SourceMandiriVA  PaymentType = "mandiri_va"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL