Unified payment system design and implementation and python instance code in game development

Source: Internet
Author: User
Tags abstract constant datetime http request passthrough redis in python

I already have several game products in my company. Each time various channels are used with different billing methods, and every game development must repeat the painful sdk access process.

Various reports and statistics are required for game payment. Each game consumes a huge amount of manpower independently.

Based on the above points, I have designed a unified payment system.

This series will be divided into two articles, corresponding to the system's v1 and v2, we will first introduce this article from v1.

After carefully analyzing the majority of domestic payment SDKs, we sorted out that the payment process of the game can be roughly implemented into two categories:

Third-party sdk server for notification of payment results

The third-party sdk client directly returns the payment result notification without the server payment result notification.

The two methods have their own advantages for the caller.

The first method is more secure, but the payment call takes a relatively long time.

The second method is faster, but it is easy to be cracked by malicious people.

Next, let's take a look at the unified payment process designed here.

Client:

 

 

Server:

 

 

A brief explanation:

Each time the payment starts, the server must generate an order as the payment record. The order id is bill_id. Order Status 4: order generation, payment failed, payment successful, delivery successful.

Pay_server is the server end of the unified payment system. Considering the call volume and convenience of debugging, the simple http protocol + json + sign method is used.

The only trouble inside the server is the interface waiting for the pay_server payment result notification. Because this http request must support suspension, after the third-party payment server notifies pay_server, the pay_server modifies the order status based on the passthrough bill_id in the notification, and then returns the result to the client.

Since the backend is implemented in python, the implementation of django + gevent is quite simple. The sample code is as follows:

If bill_id in bill_to_result:
Result = bill_to_result [bill_id]
Else:
Result = AsyncResult ()
Bill_to_result [bill_id] = result

Try:
Ret, error = result. get (timeout = config. BILL_RESULT_TIMEOUT)
Failed T gevent. Timeout:
Logger. error ('result wait timeout. bill_id: % s', bill_id)
Return jsonify (
Ret = config. RET_WAIT_BILL_RESULT_TIMEOUT,
Msg = u'no returned upon timeout'
        )
Except t:
Logger. fatal ('exc occur. bill_id: % s', bill_id, exc_info = True)
Return jsonify (
Ret = config. RET_SERVER_BUSY,
Msg = u'server busy'
        )
Else:
Logger. error ('your y result succ. bill_id: % s', bill. id)

Return jsonify (
Ret = ret,
Error = error,
Sign = make_sign (secret, request. path, bill. id)
        )
Finally:
If bill_to_result.get (bill_id) = result:
Bill_to_result.pop (bill_id, None)

When you receive a result notification from a third-party payment server, add the following code:

Def policy_bill_result (bill_id, ret, error ):
Result = bill_to_result.get (bill_id)
If result:
Result. set (ret, error ))

Let's take a look at the fields required for the payment storage:

Class Bill (models. Model ):
Userid = models. CharField (max_length = 64)
# Unique payment item ID
Item_id = models. CharField (max_length = 255)
Source = models. CharField (max_length = 32)
# Channel, similar to CN_91
Channel = models. CharField (max_length = 64, null = True, blank = True)
Create_time = models. DateTimeField (default = datetime. datetime. now)
State = models. IntegerField (default = config. BILL_STATE_INIT, choices = [
(Config. BILL_STATE_INIT, u'order generation '),
(Config. BILL_STATE_FAIL, u 'payment failed '),
(Config. BILL_STATE_SUCC, u'payment successful '),
(Config. BILL_STATE_DELIVERED, u'shipping successful '),
])
Bill_type = models. IntegerField (choices = config. BILL_TYPE_CHOICES)
# It takes effect only when it is a stored value type
Lz_amt = models. FloatField (null = True, blank = True)
# Currency
Lz_cur = models. CharField (max_length = 64, null = True, blank = True)
# Redeem internal game coins
Lz_coin = models. IntegerField (null = True, blank = True)
# Passthrough information
Passinfo = models. TextField (null = True, blank = True, default = '')
# System android/ios
OS = models. CharField (max_length = 16)
# Application version
App_version = models. IntegerField (null = True, blank = True)
# Sdk version
Sdk_version = models. IntegerField (null = True, blank = True)
# For ing users, such as Ping
Map_userid = models. CharField (max_length = 255, null = True, blank = True)
# Dict () cannot be used. The default parameters must be kept in mind.
Extra = jsonfield. JSONField (default = dict, null = True)

Note that the extra field allows the client to include all the fields such as the amount and description.

The entire payment process is basically like this. You need to talk more about the client payment sdk encapsulation.

Currently, a unified payment sdk is encapsulated to pass in the parameters required by all third-party SDKs and start the corresponding payment method based on the passed bill_type.

The benefits of doing so are:

As long as you call a payment method, basically all the payments have been debugged.

The disadvantage is:

There are too many dependent third-party SDKs, so it is difficult to customize only a few sdk versions.

Third-party SDKs are prone to conflicts.

It is very troublesome to support new SDKs and it is easy to affect other payments.

Therefore, the Client sdk encapsulation method is actually poor, so the next v2 version will be available. The specific content of the upgrade will be introduced in the next article.

1. Solve the distributed problem on the server side

The core idea for solving this problem is relatively simple:

Previously, we put event notifications in the process memory. Now we make network communication.

Because the payment request volume is deployed with high concurrency, I gave up the idea of writing a notification server directly and checked whether there is any simple solution.

Due to your previous experiences in using redis, we know that redis has a pubsub mode, which is suitable for such monitoring and notification work.

The python implementation example code is as follows:

Import time
Import config
From share. vals import rds
From share. utils import safe_str
From gevent. timeout import Timeout
From urllib import quote, unquote
 
Class RedisPubSub (object ):
"""
Use redis to subscribe to/publish messages
"""
# Subscription channel
Sub_key = None
Pub_sub = None
Channel = None
_ Prefix = 'unity _ pay: % s'
 
Def _ init _ (self, channel ):
Self. channel = channel
Self. sub_key = self. get_sub_key (self. channel)
 
Def _ del _ (self ):
Self. unsubscribe ()
 
Def subscribe (self ):
If self. pub_sub:
Return
Self. pub_sub = rds. pubsub ()
Self. pub_sub.subscribe (self. sub_key)
 
Def unsubscribe (self ):
If not self. pub_sub:
Return
Self. pub_sub.unsubscribe ()
Self. pub_sub = None
 
Def set (self, * args ):
"""
Set message subscription
"""
Rds. publish (self. sub_key, self. format (* args ))
 
Def get (self, timeout = config. BILL_RESULT_TIMEOUT ):
"""
Get subscription message
"""
Self. subscribe ()
 
Stamp = time. time () + timeout
While time. time () & lt; stamp:
Message = self. pub_sub.get_message (True)
If message and message ['type'] = 'message ':
Return self. parse (message. get ('data ',''))
Time. sleep (1)
 
Raise Timeout
 
@ Classmethod
Def format (cls, * args ):
Return ','. join ([quote (safe_str (arg) for arg in args])
 
@ Classmethod
Def parse (cls, message ):
Args = message. split (',')
 
Return [unquote (arg) for arg in args]
 
@ Classmethod
Def get_sub_key (cls, channel ):
Return cls. _ prefix % channel

 

The code is very simple and I will not explain it much.

The disadvantage of this is that redis has a hot issue, but redis does not store data any more. You can find a hot standby machine to switch over at any time.

 2. It is very difficult for the client to perform plug-in management on the payment sdk

In fact, this problem is not very difficult. The key to solving this problem is to know the following points:

The jar package requires that all classes exist during compilation. However, when the program calls this jar package, some classes in this jar package do not exist and will not crash, but report an exception that can be captured.

Based on this, we can create a same factory function and encapsulate this factory function class into a jar package.

At the same time, we encapsulate a unified interface for each payment method, and the factory function returns the implementation of such an interface. If the encapsulation class of a payment method does not exist, this exception is caught and NULL is returned.

The code for the unified interface is as follows:

Public abstract class PaymentInterf {
/**
* Uniform initialization method
* @ Param context
* @ Param parameters parameter array required during initialization
* 1. Move parameters -- "mmid and mmkey
* 2. Unicom parameters:
* String appid application ID
* String cpCode developer id
* String cpid developer VAC qualification number
* String company developer company name
* String phone number of the developer's customer service
* String game application name
* UnipayPayResultListener mCallBack callback result of the initialization function (currently only China Unicom has a non-String parameter)
     *
* 3. Amazon initialization
* Suk
* CallBack
     *
*/
Public abstract void init (Context context, Object... parameters );
/**
* Unified payment functions
* @ Param context
* @ Param parameters the parameter to be passed during payment, such as payCode billId...
*/
Public abstract void pay (Context context, Object... parameters );
   
Public static void billDeliver (String appKey, String billID ){
HttpLobbiesService. g (). billDeliver (appKey, billID, new HttpLobbiesService. Callback (){
@ Override
Public void onResult (ResultInfo info ){
If (info! = Null & amp; info. getRetCode () = 0 ){
Log. e ("billDeliver", info. getRetCode () + "/:" + info. getObj ());
                }
            }
});
    }
 
}

The code for the factory function is as follows:

Public class PaymentFactoy {
Public static PaymentInterf producePay (int billTypy ){
Try {
Switch (billTypy ){
Case Constant. BILL_TYPE_MMCNCCPAY:
Return MMP ayinstance. getInstance ();
Case Constant. BILL_TYPE_DIANXIN:
Return CTEStoreInstance. getInstance ();
Case Constant. BILL_TYPE_UNIPAY:
Return UnicomInstance. getInstance ();
Case Constant. BILL_TYPE_TAOBAO:
Return TaoBaoInstance. getInstance ();
Case Constant. BILL_TYPE_WEIPAY:
Return WeiPayInstance. getInstance ();
Case Constant. BILL_TYPE_WIMI:
Return WeiMiInstance. getInstance ();
Default:
Break;
            }
} Catch (Exception e ){
E. printStackTrace ();
Return null;
        }
Return null;
};
}

 

The above method only encapsulates the jar package of a factory function, and the other method for encapsulation of each payment still adopts the source code method.

In fact, at the earliest time, I wanted to package every kind of payment into a jar method. Later, my colleagues made the current method, and I thought about it, it may be better to use the source code for the following reasons:

Source code to facilitate debugging
Source Code. When compiled into the game, if some payment forgets to introduce the corresponding jar package, an error will be reported directly.
If one jar package is used for each payment method, the maintenance cost is too high.

This is generally the case.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.