Skip to content

Subscription

I-Chiao Hsu edited this page Feb 3, 2020 · 6 revisions

subscription is dedicated to monthly subscription function. it provides functions to record and update subscribe informations, and automates credit card transaction monthly with information users provided.

Table of Contents

Modularity

subscription depends on Golang interface largely to realize its pluggable modularity. It relies on four domain: database layer, payments, invoice, and mail package. They are basically replaceable.

Dependencies

Currently we uses MySQL as the data persistence layer. subscription was designed to be agnostic about database, so we need to do dependency injection when initializing subscription service. The injection happens in SetRoutes.

There are three third-party dependencies:

  • Tappay: /pkg/payment/tappay
  • ezPay: /pkg/invoice/ezPay
  • mail: /pkg/mail

They will be injected in mysql package.

Package layout

subscription
├── http
│   ├── filter.go
│   ├── handler.go
│   ├── handler_test.go
│   ├── time_parser.go
│   └── time_parser_test.go
├── mysql
│   ├── mysql.go
│   └── mysql_test.go
├── test
│   └── mock
│       └── mock.go
└── subscription.go

Domain-concern

subscription.go contains the domain types, constants, and interfaces that subscription depends on.

The constants, like status of subscription, are declared with Golang's iota functionality to achieve Enum-like constant groups instead of putting them in config.json formerly.

const (
	// StatusInit means subscriptions is init. It's different from zero-value of Go
	StatusInit = iota + 1
	// StatusOK means it is a functioning subscription
	StatusOK
	// StatusInactive indicates this is not an active subscription now
	StatusInactive
	// StatusInitPayFail denotes fail when pay for the first time
	StatusInitPayFail
	// StatusRoutinePayFail indicates failure when pays with token
	StatusRoutinePayFail
)

mysql

The integration of all services happends here. The SubscriptionService in mysql.go holds the MySQL instances, service interface for Tappay, EzPay, and Gmail.

MySQL instance will be initalized in http package.

payment and invoice service will be initialized explicitly in each service functions. They are decided based on the informations stored in Subscription struct, PaymentService and InvoiceService to decide which service instances should be injected to this struct.

mail will be injected with only Gmail service. It's the only mail service we support now. This also happens inside each functions.

http

http package holds the http controller for endpoints Subscription provides. Its main entrance is handler.go. As always, SetRoutes will be imported in routes/routes.go to register handlers subscription provides.

In the function SetRoutes, The global sqlx.DB instance rrsql.DB.DB will be injected if there is no assigned database layer.

s := &mysql.SubscriptionService{DB: rrsql.DB.DB}
h.Service = s

test

pkg/subscription/test
└── mock
    └── mock.go

mock

mock stores the interface generated by GoMock. It will be consumed by unit tests for handlers.

Supposedly integration tests for subscription should be put here, too.


Usage

POST /subscriptions

This endpoint provides a handler to add information about a new subscriber. The payload for frontend to attach will looks like this:

{
	"member_id": 648,
	"payment_service": "tappay",
	"invoice_service": "ezpay",
	"amount": 100,
	"email": "[email protected]",
	"created_at": "2019-11-28T00:11:46Z",
	"payment_infos": {
		"prime": "test_3a2fb2b7e892b914a03c95dd4dd5dc7970c908df67a49527c0a648b2bc9",
		"currency": "TWD",
		"details": "show me the money",
		"cardholder": {
			"phone_number": "+886919123467",
			"name": "wonderwomen",
			"email": "[email protected]"
		}
	},
	"invoice_infos":{
		"item_name": ["訂閱"],
		"item_unit": ["月"],
		"carrier_type":"",
		"carrier_num": "",
		"buyer_name": "wonderwoman",
		"item_price": [30],
		"item_count": [1]
	}
}
  • member_id: (int) member's id in READr
  • amount(required): (int) how much we want to get from users?
  • payment_service: (string) which payment service to use in subscription. Default tappay
  • invoice_service: (string) which invoice service to use in subscription. Default ezpay

payment_infos

payment_infos records necessary information to finish payment procedure. In the object above is the example for Tappay. the meaning of those fields are as follows:

  • prime(required): (string) the prime token retrieved from Tappay by frontend
  • currency: (string) default TWD by Tappay
  • details(required): (string) description about this transaction

In cardholder object, the information will be send to fraud detector. Those fields are required:

  • phone_number(required): (string) phone number of the payer
  • name(required): (string) name of the payer
  • email(required): (string) email of the payer

invoice_infos

invoice_infos records the necessary information for our invoice service. The example above demonstrate how to do it with ezPay:

  • item_name(required): (array[string]) indicates names for each purchased item
  • item_unit(required): (array[string]) indicates the corresponding unit for each item_count
  • carrier_type: (string) applicable when category=B2C,
    • "0"=手機條碼載具
    • "1"=自然人憑證條碼載具
    • "2"=ezPay 電子發票載具
  • carrier_num: (string) required when carrier_type is filled,
  • love_code: (string) Donate code
  • buyer_ubn: (string) Set when category=B2B
  • buyer_address: (string) buyer's address
  • buyer_name: (string) buyer's name
  • item_price(required): (array[int]) indicates the price for each item
  • item_count(required): (array[int]) indicates the amount for each purchase item

PUT /subscriptions/{id}

This endpoint provides the function to modify a single subscription information for a user.

id: the subscription id


GET /subscription/recurring