Apple应用内支付详细代码实现

前一段时间项目中有用到内支付功能,虽然之前有接触过,但都是别人把内购申请整个流程都做完了,我只需要写代码实现具体内购功能就行,但这一次我是完完全全走了一边内购的流程,从APPstore上填写税务协议,联系人具体信息,银行卡信息,到创建沙盒测试帐号,再到具体代码的实现,整个流程虽然这次执行的比较顺利,但中间还是出现一些不可避免的小麻烦小问题。

这次我只是简单的说一下流程,内购申请和沙盒帐号申请的流程在这里我不做具体的介绍,但是会有链接申请内购,沙盒帐号申请

一、IOS 应用内支付(内购 /In App Purchase)有两种模式:

1.单机模式

单机模式的流程可以简单的总结为以下几步:
1.1app从app store 获取产品信息
1.2 用户选择需要购买的产品
1.3app发送支付请求到app store
1.4 app store 处理支付请求,并返回transaction信息
1.5 app将购买的内容展示给用户

2.服务器模式

服务器模式的主要流程如下所示:
2.1 app从服务器获取产品标识列表
2.2 app从app store 获取产品信息
2.3 用户选择需要购买的产品
2.4 app 发送 支付请求到app store
2.5 app store 处理支付请求,返回transaction信息
2.6 app 将transaction receipt 发送到服务器
2.7 服务器收到收据后发送到app stroe验证收据的有效性
2.8 app store 返回收据的验证结果
2.9 根据app store 返回的结果决定用户是否购买成功

3.两种模式比较

上述两种模式的不同之处主要在于:交易的收据验证,内建模式没有专门去验证交易收据,而服务器模式会使用独立的服务器去验证交易收据。内建模式简单快捷,但容易被破解。服务器模式流程相对复杂,但相对安全。

4.国内连接苹果服务器的稳定性

开发之初,苹果方就很负责的告知:我们的服务器不稳定。真正开发之后,发现苹果方果然是很负责的,不仅是不稳定,而且足够慢。app store server验证一个收据需要3-6s时间。
1.用户能否忍受3-6s的等待时间
2.如果app store server 宕机,如何确保成功付费的用户能够得到正常服务。
对于第一个问题,我们有理由相信用户完全无法忍受,所以采用异步验证的方式,服务器收到客户端的请求后,就将请求放到MCQ中去处理。
对于第二个问题,由于苹果人员很负责人的告知:我们的服务器不稳定,所以不排除收据验证超时的情况。对于验证超时的收据,保存到数据库中并标记为验证超时,定时任务每隔一定的时间去app store验证,确保能够获取收据的验证结果。

二、这次主要先讲一下单机模式

先列一下内购代码实现的流程:

1.添加支付结果的监听。
2.点击需要购买的产品,然后发起购买请求。
3.在获取产品信息成功回调中,将产品包装成SKPayment(支付)发送给苹果服务器。
4.苹果服务器购买成功后会回调监听方法。

下面开始讲具体的实现流程:

首先需要添加#import <StoreKit/StoreKit.h>头文件,整个支付代码我做了封装,暴露出需要的东西
这是整个工具类的.h文件

@protocol PuchaseToolDelegate<NSObject>
//购买成功
-(void)puchaseSuccessWithUpdatedTransactions:(NSArray *)transactions;
//购买失败
-(void)puchaseFailWithUpdatedTransactions:(NSArray *)transactions;
//重复购买
-(void)puchaseRestoredWithUpdatedTransactions:(NSArray *)transactions;
//购买是请求产品列表错误信息
-(void)puchaseRequestFailWithError:(NSError *)error;
//购买是请求产品列表完成
-(void)puchaseRequestFinish;
@end
@interface PuchaseTool : NSObject
/**
 是否可以支付
 */
@property (assign, nonatomic,readonly) BOOL canPay;
@property (weak, nonatomic) id <PuchaseToolDelegate>delegate;
#pragma mark - 初始化方法
+ (PuchaseTool *)shareInstance;
/**
 添加支付监听
 */
-(void)addTransactionObserver;
/**
移除支付监听
 */
-(void)removeTransactionObserver;
//请求产品信息
-(void)requestProductInfoWithProductArray:(NSArray *)product;

1.添加支付结果的监听

具体内部代码
-(void)addTransactionObserver{
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
具体调用
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupSubviews];
    [self setupData];
}
-(void)setupSubviews{
    [self.puchaseTool addTransactionObserver];
}

我在初始化的时候就添加了监听,同时在视图释放的时候添加了移除操作

具体内部代码
-(void)removeTransactionObserver{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];//解除监听
}
具体调用
-(void)dealloc
{
    [self.puchaseTool removeTransactionObserver];//解除监听
}

2.请求产品列表

2.1这是我申请产品ID数据
//在内购项目中创的商品单号
#define ProductID_IAPp12 @"com.quzhua.app1"//12
#define ProductID_IAPp30 @"com.quzhua.app2" //30
#define ProductID_IAPp50 @"com.quzhua.app3" //50
#define ProductID_IAPp98 @"com.quzhua.app4" //98
#define ProductID_IAPp488 @"com.quzhua.app5" //488
#define ProductID_IAPp998 @"com.quzhua.app6" //998
2.2选择对应的产品向苹果请求对应的产品信息(相当于下订单)
//创建苹果支付的订单
-(void)createApplePayOrder{
    //可以先告诉客户端并生成订单  然后 去支付
    //去支付
    [self buy:selectBuyType];
}
//购买对应的商品
-(void)buy:(int)type
{
    buyType = type;
    if ([self.puchaseTool canPay]) {
        [self RequestProductData];
        NSLog(@"允许程序内付费购买");
    }else{
        NSLog(@"不允许程序内付费购买");
        UIAlertView *alerView =  [[UIAlertView alloc] initWithTitle:@"提示"
                                                            message:@"您的手机没有打开程序内付费购买"
                                                           delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
        
        [alerView show];
    }
}

在购买的时候首先需要判断用户是否允许该软件进行内购支付,如果允许直接请求产品信息

2.3请求产品信息
具体内部代码
//请求产品信息
-(void)requestProductInfoWithProductArray:(NSArray *)product{
    NSSet *nsset = [NSSet setWithArray:product];
    SKProductsRequest *request=[[SKProductsRequest alloc] initWithProductIdentifiers: nsset];
    request.delegate=self;
    [request start];
}

它会设置SKProductsRequestDelegate的代理并且监听请求的回调信息

SKProductsRequestDelegate代理方法
#pragma mark- SKProductsRequestDelegate
//收到的产品信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    //-----------收到产品反馈信息--------------
    NSArray *myProduct = response.products;
    NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);
    NSLog(@"产品付费数量: %d", (int)[myProduct count]);
    // populate UI
    for(SKProduct *product in myProduct){
        NSLog(@"product info");
        NSLog(@"SKProduct 描述信息%@", [product description]);
        NSLog(@"产品标题 %@" , product.localizedTitle);
        NSLog(@"产品描述信息: %@" , product.localizedDescription);
        NSLog(@"价格: %@" , product.price);
        NSLog(@"Product id: %@" , product.productIdentifier);
    }
    SKPayment *payment = [SKPayment paymentWithProduct:(SKProduct *)myProduct.firstObject];
    //---------发送购买请求------------
    [[SKPaymentQueue defaultQueue] addPayment:payment];
    
}
- (void)requestProUpgradeProductData
{
    //------请求升级数据---------
    NSSet *productIdentifiers = [NSSet setWithObject:@"com.productid"];
    SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    productsRequest.delegate = self;
    [productsRequest start];
}
//弹出错误信息
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    //-------弹出错误信息----------
    if (self.delegate && [self.delegate respondsToSelector:@selector(puchaseRequestFailWithError:)]) {
        [self.delegate puchaseRequestFailWithError:error];
    }
}
-(void)requestDidFinish:(SKRequest *)request{
    //----------反馈信息结束--------------
    if (self.delegate && [self.delegate respondsToSelector:@selector(puchaseRequestFinish)]) {
        [self.delegate puchaseRequestFinish];
    }
}
-(void) PurchasedTransaction: (SKPaymentTransaction *)transaction{
    //-----PurchasedTransaction----
    NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil];
    [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions];
}
具体调用
//请求对应商品信息
-(void)RequestProductData
{
    NSLog(@"---------请求对应的产品信息------------");
    NSArray *product = nil;
    switch (buyType) {
        case buyCoinsTagIAPp12:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp12,nil];
            break;
        case buyCoinsTagIAPp30:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp30,nil];
            break;
        case buyCoinsTagIAPp50:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp50,nil];
            break;
        case buyCoinsTagIAPp98:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp98,nil];
            break;
        case buyCoinsTagIAPp488:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp488,nil];
            break;
        case buyCoinsTagIAPp998:
            product=[[NSArray alloc] initWithObjects:ProductID_IAPp998,nil];
            break;
        default:
            break;
    }
    [self.puchaseTool requestProductInfoWithProductArray:product];
}

3.开始支付

在收到请求产品信息成功的地方对产品信息使用SKPayment进行包装,然后发送支付请求

//收到的产品信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    //-----------收到产品反馈信息--------------
    NSArray *myProduct = response.products;
    NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers);
    NSLog(@"产品付费数量: %d", (int)[myProduct count]);
    // populate UI
    for(SKProduct *product in myProduct){
        NSLog(@"product info");
        NSLog(@"SKProduct 描述信息%@", [product description]);
        NSLog(@"产品标题 %@" , product.localizedTitle);
        NSLog(@"产品描述信息: %@" , product.localizedDescription);
        NSLog(@"价格: %@" , product.price);
        NSLog(@"Product id: %@" , product.productIdentifier);
    }
    SKPayment *payment = [SKPayment paymentWithProduct:(SKProduct *)myProduct.firstObject];
    //---------发送购买请求------------
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

4.监听购买回调信息

此时监听SKPaymentTransactionObserver的方法,监听支付结果的回调信息

//----监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:{//交易完成
                //-----交易完成 --------
                [self completeTransaction:transaction];
                if (self.delegate && [self.delegate respondsToSelector:@selector(puchaseSuccessWithUpdatedTransactions:)]) {
                    [self.delegate puchaseSuccessWithUpdatedTransactions:transactions];
                }
            } break;
            case SKPaymentTransactionStateFailed://交易失败
            {
                //-----交易失败 --------
                [self failedTransaction:transaction];
                if (self.delegate && [self.delegate respondsToSelector:@selector(puchaseFailWithUpdatedTransactions:)]) {
                    [self.delegate puchaseFailWithUpdatedTransactions:transactions];
                }
                
            }break;
            case SKPaymentTransactionStateRestored://已经购买过该商品
            {
                //-----已经购买过该商品 --------
                [self restoreTransaction:transaction];
                if (self.delegate && [self.delegate respondsToSelector:@selector(puchaseRestoredWithUpdatedTransactions:)]) {
                    [self.delegate puchaseRestoredWithUpdatedTransactions:transactions];
                }
            }
                break;
            case SKPaymentTransactionStatePurchasing:      //商品添加进列表
                //-----商品添加进列表 --------
                break;
            default:
                break;
        }
    }
}

无论是购买成功还是失败都需要移除这个订单信息

// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

内部具体代码
//-----------------------购买成功-----------------
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    // Your application should implement these two methods.
    NSString *product = transaction.payment.productIdentifier;
    if ([product length] > 0) {
        NSArray *tt = [product componentsSeparatedByString:@"."];
        NSString *bookid = [tt lastObject];
        if ([bookid length] > 0) {
            [self recordTransaction:bookid];
            [self provideContent:bookid];
        }
    }
    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
//--------------------购买失败------------------
- (void) failedTransaction: (SKPaymentTransaction *)transaction{
    NSLog(@"失败");
    if (transaction.error.code != SKErrorPaymentCancelled){
        
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
}

在回调信息里根据情况添加自己的一些操作代码

三、服务端校验

当客户端购买成功后,服务端需要进行二次校验,校验通过,次订单才算成功。

当应用向Apple服务器请求购买,成功之后,Apple会返回以下四个数据给应用

1.四个验证数据

productIdentifier:cosmosbox.strikehero.gems60
state: Purchased
receipt:
ewoJInNpZ25hdHVyZSIgPSAiQXF1M3JiR1grbmJMeGVvZS9VZGlMa3dQWVlBdkQr
VTE1L1NRL2Y0cGZlaFlBOWFaVGhSbTNMVXpHc25TUGd3aVBoMmsxSTVFaVpweGp6
aEZsS0JDVXBPeHEyWFk5N1lHUGUzMFo0cThMRllDZWJPeHFzWlJaUU01N2xtZFo0
bDN6eHNnaWpGemFiYkRXLzM4cm1EeXFTT0FSYzRES3dXTGFpc2EzYUY5d2JwbUFB
QURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RR
RUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pT
//receipt省略几十行
transactionIdentifier: 1000000160385706

1.1 产品标识符: product Identifier

在itunes store应用内定义的产品ID,例如com.公司名.产品名.道具名(com.xxxx.video.vip)

1.2 交易状态: state

Purchased 购买成功
Restored 恢复购买
Failed 失败
Deferred 等待确认,儿童模式需要询问家长同意

1.3 Receipt

很长的一段字符串,大概49行,作为二次验证的重要依据

1.4交易标识符: transaction Identifier

我们需要把Receipt发送給苹果的苹果的服务器验证,用户的购买信息是否真实

2. 验证服务器地址

在测试服务器中,发送receipt苹果的测试服务器( https://sandbox.itunes.apple.com/verifyReceipt )验证

在正式服务器中(已上线Appstore),发送receipt到苹果的正式服务器( https://buy.itunes.apple.com/verifyReceipt )验证

注:为保证审核的通过,需要在客户端或server进行双重验证,即,先以线上交易验证地址进行验证,如果苹果正式验证服务器的返回验证码code为21007,则再一次连接沙盒测试服务器进行验证即可。在应用提审时,苹果IAP提审验证时是在沙盒环境的进行的,即:苹果在审核App时,只会在sandbox环境购买,其产生的购买凭证,也只能连接苹果的测试验证服务器,如果没有做双验证,需要特别注意此问题,否则会被拒。
2.1验证购买信息

以下是把客户端的购买信息发送到苹果测试服务器进行确认,苹果返回的数据:

ISN: url: https://sandbox.itunes.apple.com/verifyReceipt
ORIGINAL JSON:
{
"receipt":
{
"original_purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles", //购买时间,太平洋标准时间
"purchase_date_ms":"1435031794826", //购买时间毫秒
"unique_identifier":"5bcc5503dbcc886d10d09bef079dc9ab08ac11bb",//唯一标识符
"original_transaction_id":"1000000160390314", //原始交易ID
"bvrs":"1.0",//iPhone程序的版本号
"transaction_id":"1000000160390314", //交易的标识
"quantity":"1", //购买商品的数量
"unique_vendor_identifier":"AEEC55C0-FA41-426A-B9FC-324128342652", //开发商交易ID
"item_id":"1008526677",//App Store用来标识程序的字符串
"product_id":"cosmosbox.strikehero.gems60",//商品的标识
"purchase_date":"2015-06-23 03:56:34 Etc/GMT",//购买时间
"original_purchase_date":"2015-06-23 03:56:34 Etc/GMT", //原始购买时间
"purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles",//太平洋标准时间
"bid":"com.cosmosbox.StrikeHero",//iPhone程序的bundle标识
"original_purchase_date_ms":"1435031794826"//毫秒
},
"status":0 //状态码,0为成功
}

3.苹果返回状态码

Status 描述
21000 App Store不能读取你提供的JSON对象
21002 receipt-data域的数据有问题
21003 receipt无法通过验证
21004 提供的shared secret不匹配你账号中的shared secret
21005 receipt服务器当前不可用
21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务

最好在客户端存一个数据库,跟踪订单的状态,防止用户订单在某个环节出现问题时无法寻找到订单进行二次处理。

去AppStore请求数据时有时候会出现错误,你可以iTunes connect里的connect us去给他们写邮件反馈问题。但是大部分时间你等等就能解决了,对就是什么也不做等着。也许那一天他就好了。

四、客户端验证代码

苹果推荐的方法是将收据发给开发者的server,由server像上述地址post http消息,进行验证, 苹果将结果返回.用来判断到底是真正的购买还是虚假的购买.
但这种方式也可以由客户端完成最后告知服务端即可

#define ITMS_SANDBOX_VERIFY_RECEIPT_URL     @"https://sandbox.itunes.apple.com/verifyReceipt"  
 
#pragma mark - VerifyFinishedTransaction  
-(void)verifyFinishedTransaction:(SKPaymentTransaction *)transaction{  
   if(transaction.transactionState == SKPaymentTransactionStatePurchased){  
       NSData *transactionReceipt  = transaction.transactionReceipt;  
       //将transactionIdentifer和加密后的transactionReceipt数据发送给server端  
//        NSString* receipent = [NSString stringWithFormat:@"%s", transactionReceipt.bytes];//用来POST给server  
       NSDictionary *requestContents = @{  
                                         @"receipt-data": [self encode:(uint8_t *)transactionReceipt.bytes length:transactionReceipt.length]};  
         
       NSLog(@"receipent = %@", requestContents);  
         
       // 在app上做验证, 仅用于测试  
       NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents  
                                                             options:0  
                                                               error:nil];  
       NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:ITMS_SANDBOX_VERIFY_RECEIPT_URL]];  
       [request setHTTPMethod:@"POST"];  
       [request setHTTPBody:requestData];  
       NSError* err;  
       NSURLResponse *theResponse = nil;  
       NSData *data=[NSURLConnection sendSynchronousRequest:request  
                                          returningResponse:&theResponse  
                                                      error:&err];  
       NSError *jsonParsingError = nil;  
       NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonParsingError];  
       NSLog(@"requestDict: %@", dict);  
   }  
}  
//Base64加密  
- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {  
   static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";  
     
   NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];  
   uint8_t *output = (uint8_t *)data.mutableBytes;  
     
   for (NSInteger i = 0; i < length; i += 3) {  
       NSInteger value = 0;  
       for (NSInteger j = i; j < (i + 3); j++) {  
           value <<= 8;  
             
           if (j < length) {  
               value |= (0xFF & input[j]);  
           }  
       }  
         
       NSInteger index = (i / 3) * 4;  
       output[index + 0] =                    table[(value >> 18) & 0x3F];  
       output[index + 1] =                    table[(value >> 12) & 0x3F];  
       output[index + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';  
       output[index + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';  
   }  
     
   return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];  
}  

总结

以上就是内购支付的整个流程,开始以为很难,做过一遍之后你就发现很简单,所有的东西都是固定的模式,只要你知道流程,就能很快写出来,废话不多说了直接上代码内购支付代码,里面可以看到完整的代码,亲测直接可以使用的,如果你已经申请好了内支付和沙盒账号,只需要修改产品单号就直接可以购买了.
我这个流程,我这边跑通了,目前一直在用,可能大家还有不同的流程和方式,有什么不对的地方,欢迎大家留言,相互学习,共同进步.
转载出处:http://blog.csdn.net/yupu56/article/details/46907609
http://blog.csdn.net/tom_and_jerry_zhao/article/details/72770078
https://www.cnblogs.com/zhaoqingqing/p/4597794.html
参考资料:http://www.cnblogs.com/pengyingh/articles/2536828.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容