iOS内购详细教程&iOS内购坑

1、收款协议以及账户等的创建

内购收款协议等的创建,这里一般由运营负责,这里不做介绍,但是如果想要了解,请参考这位博主的文章,里面的图文都解释得很清楚。https://www.jianshu.com/p/86ac7d3b593a

2、开发者中心文件创建

要开启iOS内购功能,首先在apple develop 中心,先创建证书以及描述文件并包含内购功能。在项目中打开 In - App-purchase 功能即可继续下面的代码实现。

3、代码实现

我这边的代码实现自己实现了一个工具类。然后内购的相关代码以及逻辑在这个类实现,这样做的好处是不需要在控制器中写过多的代码,方便转移使用,符合代码高聚合性低耦合性的原则。

首先导入在项目的 Build Phases 下的Link Binary With libraires 中添加StoreKit.framework

在这个工具类里面 ,我写了一个单例方法,包括添加内购监听,停止内购监听以及发起内购购买的方法,话不多说直接上代码 。

下面是IPAPurchase.h的代码

#import <Foundation/Foundation.h>

/**
 block

 @param isSuccess 是否支付成功
 @param certificate 支付成功得到的凭证(用于在自己服务器验证)
 @param errorMsg 错误信息
 */
typedef void(^PayResult)(BOOL isSuccess,NSString *certificate,NSString *errorMsg);

@interface IPAPurchase : NSObject
@property (nonatomic, copy)PayResult payResultBlock;

/**
单例方法
*/
+ (instancetype)manager;

/**
 开启内购监听 在程序入口didFinishLaunchingWithOptions实现
 */
-(void)startManager;

/**
停止内购监听 在AppDelegate.m中的applicationWillTerminate方法实现
*/
-(void)stopManager;

/**
 拉起内购支付
 @param productID 内购商品ID
 @param payResult 结果
 */

-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult;

以下是IPAPurchase.m文件的代码

#import "IPAPurchase.h"
#import <StoreKit/StoreKit.h>

static NSString * const receiptKey = @"receipt_key";

dispatch_queue_t iap_queue() {
static dispatch_queue_t as_iap_queue;
static dispatch_once_t onceToken_iap_queue;
dispatch_once(&onceToken_iap_queue, ^{
    as_iap_queue = dispatch_queue_create("com.iap.queue", DISPATCH_QUEUE_CONCURRENT);
});
    return as_iap_queue;
 }

@interface IPAPurchase()<SKPaymentTransactionObserver,
                    SKProductsRequestDelegate>
{
      SKProductsRequest *request;
}

//购买凭证
@property (nonatomic,copy)NSString *receipt;//存储base64编码的交易凭证

//产品ID
@property (nonnull,copy)NSString * profductId;

@end

static IPAPurchase * manager = nil;

@implementation IPAPurchase
#pragma mark -- 单例方法
+ (instancetype)manager{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!manager) {
        manager = [[IPAPurchase alloc] init];
    }
});   
     return manager;
}

#pragma mark -- 漏单处理
-(void)startManager{

dispatch_sync(iap_queue(), ^{

 [[SKPaymentQueue defaultQueue] addTransactionObserver:manager];

 });   
}

#pragma mark -- 移除交易事件

-(void)stopManager{

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

});
}

#pragma mark -- 发起购买的方法
-(void)buyProductWithProductID:(NSString *)productID  payResult:(PayResult)payResult{

self.payResultBlock = payResult;
//移除上次未完成的交易订单
[self removeAllUncompleteTransactionBeforeStartNewTransaction];

[RRHUD showWithContainerView:RR_keyWindow status:NSLocalizedString(@"购买中...", @"")];

self.profductId = productID;

if (!self.profductId.length) {

    UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"没有对应的商品" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];

    [alertView show];
}

if ([SKPaymentQueue canMakePayments]) {

    [self requestProductInfo:self.profductId];

}else{

    UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"请先开启应用内付费购买功能。" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];

    [alertView show];

      }
}

#pragma mark -- 结束上次未完成的交易 防止串单
-(void)removeAllUncompleteTransactionBeforeStartNewTransaction{

NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
    //检测是否有未完成的交易
    SKPaymentTransaction* transaction = [transactions firstObject];
    if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        return;
    }
}
}

#pragma mark -- 发起购买请求
-(void)requestProductInfo:(NSString *)productID{

    NSArray * productArray = [[NSArray alloc]initWithObjects:productID,nil];

NSSet * IDSet = [NSSet setWithArray:productArray];

request = [[SKProductsRequest alloc]initWithProductIdentifiers:IDSet];

request.delegate = self;

[request start];

}

#pragma mark -- SKProductsRequestDelegate 查询成功后的回调
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;

if (myProduct.count == 0) {

    [RRHUD hide];
    [RRHUD showErrorWithContainerView:UL_rootVC.view status:NSLocalizedString(@"没有该商品信息", @"")];

    if (self.payResultBlock) {
        self.payResultBlock(NO, nil, @"无法获取产品信息,购买失败");
    }

    return;
}

SKProduct * product = nil;
for(SKProduct * pro in myProduct){

    NSLog(@"SKProduct 描述信息%@", [pro description]);
    NSLog(@"产品标题 %@" , pro.localizedTitle);
    NSLog(@"产品描述信息: %@" , pro.localizedDescription);
    NSLog(@"价格: %@" , pro.price);
    NSLog(@"Product id: %@" , pro.productIdentifier);

    if ([pro.productIdentifier isEqualToString:self.profductId]) {

        product = pro;
        break;

    }
}

if (product) {

SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//内购透传参数
payment.applicationUsername = self.order;

[[SKPaymentQueue defaultQueue] addPayment:payment];

}else{

    NSLog(@"没有此商品");
   }
}

//查询失败后的回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {

if (self.payResultBlock) {
    self.payResultBlock(NO, nil, [error localizedDescription]);
      }
}

//如果没有设置监听购买结果将直接跳至反馈结束;
-(void)requestDidFinish:(SKRequest *)request{

}

#pragma mark -- 监听结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{

//当用户购买的操作有结果时,就会触发下面的回调函数,
for (SKPaymentTransaction * transaction in transactions) {

    switch (transaction.transactionState) {

        case SKPaymentTransactionStatePurchased:{

        [self completeTransaction:transaction];

        }break;

        case SKPaymentTransactionStateFailed:{

        [self failedTransaction:transaction];
        }break;

        case SKPaymentTransactionStateRestored:{//已经购买过该商品

        [RRHUD hide];
        [self restoreTransaction:transaction];

        }break;

        case SKPaymentTransactionStatePurchasing:{

            NSLog(@"正在购买中...");

        }break;

        case SKPaymentTransactionStateDeferred:{

            NSLog(@"最终状态未确定");

        }break;

        default:
            break;
      }
   }
}

//完成交易
#pragma mark -- 交易完成的回调
- (void)completeTransaction:(SKPaymentTransaction *)transaction{

NSLog(@"购买成功,准备验证发货");
[self getReceipt]; //获取交易成功后的购买凭证
[self saveReceipt:transaction]; //存储交易凭证
[self checkIAPFiles:transaction];
}

#pragma mark -- 处理交易失败回调
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{

[RRHUD hide];
NSString *error = nil;
if(transaction.error.code != SKErrorPaymentCancelled) {

    [RRHUD showInfoWithContainerView:UL_rootVC.view status:NSLocalizedString(@"购买失败", @"")];
} else {

    [RRHUD showInfoWithContainerView:UL_rootVC.view status:NSLocalizedString(@"取消购买", @"")];
}

if (self.payResultBlock) {
    self.payResultBlock(NO, nil, error);
}

[[SKPaymentQueue defaultQueue]finishTransaction:transaction];

}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction{

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

#pragma mark -- 获取购买凭证
-(void)getReceipt{

NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
 self.receipt = base64String;
}

#pragma mark -- 存储购买凭证
-(void)saveReceipt:(SKPaymentTransaction *)transaction{
NSString * userId;
NSString * order;

if (self.userid) {

    userId = self.userid;

    [[NSUserDefaults standardUserDefaults]setObject:userId forKey:@"unlock_iap_userId"];
}else{

    userId = [[NSUserDefaults standardUserDefaults]objectForKey:@"unlock_iap_userId"];
}

order = transaction.payment.applicationUsername;

NSString *fileName = [NSString UUID];
NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];
NSMutableDictionary * dic = [[NSMutableDictionary alloc]init];
[dic setValue: self.receipt forKey:receiptKey];
[dic setValue: userId forKey:@"user_id"];
[dic setValue: order forKey:@"order"];
BOOL ifWriteSuccess  = [dic writeToFile:savedPath atomically:YES];

if (ifWriteSuccess) {

    NSLog(@"购买凭据存储成功!");
  }
}

#pragma mark -- 验证本地数据
-(void)checkIAPFiles:(SKPaymentTransaction *)transaction{

NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];

if (error == nil) {

    for (NSString *name in cacheFileNameArray) {

        if ([name hasSuffix:@".plist"]){ //如果有plist后缀的文件,说明就是存储的购买凭证

            NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];

            [self sendAppStoreRequestBuyPlist:filePath trans:transaction];
        }
    }

} else {

  }
}

#pragma mark -- 存储成功订单
-(void)SaveIapSuccessReceiptDataWithReceipt:(NSString *)receipt Order:(NSString *)order UserId:(NSString *)userId{

NSMutableDictionary * mdic = [[NSMutableDictionary alloc]init];
[mdic setValue:[self getCurrentZoneTime] forKey:@"time"];
[mdic setValue: order forKey:@"order"];
[mdic setValue: userId forKey:@"userid"];
[mdic setValue: receipt forKey:receiptKey];
NSString *fileName = [NSString UUID];
NSString * successReceiptPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper SuccessIapPath], fileName];
//存储购买成功的凭证
[mdic writeToFile:successReceiptPath atomically:YES];

}

#pragma mark -- 获取系统时间的方法
-(NSString *)getCurrentZoneTime{

NSDate * date = [NSDate date];
NSDateFormatter*formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString*dateTime = [formatter stringFromDate:date];
return dateTime;
}

#pragma mark -- 去服务器验证购买
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath trans:(SKPaymentTransaction *)transaction{

NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * receipt = [dic objectForKey:receiptKey];
NSString * order = [dic objectForKey:@"order"];
NSString * userId = [dic objectForKey:@"user_id"];

 #pragma mark -- 发送信息去验证是否成功
[[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {

    if (RequestSuccess) {

        NSLog(@"服务器验证成功!");

        [[SKPaymentQueue defaultQueue]finishTransaction:transaction];

        [RRHUD hide];

        [RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"购买成功", @"")];

        [[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
        NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];

        NSString *result = [data base64EncodedStringWithOptions:0];

        if (self.payResultBlock) {
            self.payResultBlock(YES, result, nil);
        }
        //这里将成功但存储起来
        [self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];

        [self successConsumptionOfGoodsWithOrder:order];

    }else{            
      //在这里向服务器发送验证失败相关信息
} failure:^(ULSDKAPI *api, NSString *failure) {

}

#pragma mark -- 根据订单号来移除本地凭证的方法
-(void)successConsumptionOfGoodsWithOrder:(NSString * )cpOrder{

NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {

    NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];

    if (error == nil) {

        for (NSString * name in cacheFileNameArray) {

            NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];

            [self removeReceiptWithPlistPath:filePath ByCpOrder:cpOrder];

        }
    }
  }
}

#pragma mark -- 根据订单号来删除 存储的凭证
-(void)removeReceiptWithPlistPath:(NSString *)plistPath   ByCpOrder:(NSString *)cpOrder{

NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * order = [dic objectForKey:@"order"];

if ([cpOrder isEqualToString:order]) {
    //移除与游戏cp订单号一样的plist 文件
    BOOL ifRemove =  [fileManager removeItemAtPath:plistPath error:&error];
    if (ifRemove) { 
        NSLog(@"成功订单移除成功");
    }else{
        NSLog(@"成功订单移除失败");
    }
}else{
        NSLog(@"本地无与之匹配的订单");
    }
}

接下来是遇到的坑与解决

坑 1

因为我们走的服务器验证发货的流程,因此服务器验证这一步尤其重要。如果用户付了款 ,但是没有发货的话那问题就大了,客户是无法忍受这种情况的(你丫的吞老子的钱)。刚开始的时候我是把以下结束交易的代码 写到了购买回调-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions 方法里,导致一走到购买回调里就告诉苹果这个交易结束了。但是如果此时我们服务器没收到购买凭证或者中途出了问题的话,玩家是收不到购买的东西的。导致我们后台没有匹配的订单号,苹果又没有提供与我们平台订单号的匹配的参数,导致无法确定是用户充值了没收到货,还是用户装可怜来讹我们,这一度让我们很痛苦,无奈只能告诉玩家去苹果申请退款。经过各种百度和研究实验,所以在这里重点注意的是!!!!如果是后台做验证的话请把以下代码写到成功提交内购凭证到服务器后台之后再结束这次交易。这样确保后台收到了凭证验证成功,因此每次用户来问我们怎么没收到货或者什么的,我们都有据可循。漏单率也大大降低。

[[SKPaymentQueue defaultQueue]finishTransaction:transaction];

坑2

因为我是在游戏公司写SDK给研发用的,因此在给接口研发对接的时候就遇到一个问题,就是研发接入的时候没有实现内购监听的代码,也就是IPAPurchase.h 中的以下方法,我们看看这个方法是干什么的?苹果的注释是 Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate.假如我们没写这个方法会怎样?答案是:你购买之后他压根就没走购买的代理方法,也就是说,他不会走-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions回调方法,就算你能获取到商品又怎样?反正你不添加监听,我就是不走。这会导致什么问题呢?导致的问题是:你购买成功后的逻辑都不会走,你验证不了,你更发不了货。然而更加恐怖的是什么呢?假如不知道自己没写,不停地点击购买,买了100+次,这下你就摊上大事儿了。当你知道自己没写之后,将[[SKPaymentQueue defaultQueue] addTransactionObserver:self]代码添加进去之后 ,你会发现-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions这个方法被回调了100+次。OMG!所以,记住了,这个代码最好在程序启动入口就实现,这样的话会在程序一进来就去遍历过往未完成的单。

-(void)startManager

坑3

我们假想一种情况,比如有两个玩家A和B,首先A在应用内发起内购购买,成功了但是在去服务器验证的中间发生异常应用退出了。也就是说漏单了,他买了东西没有到账。然后他找到B说,我曹!我刚才买了东西,但是没收到货。我怀疑是不是我手机有问题,我在你手机上登录看看,会不会到货了。于是,A在B的手机上登陆了自己的appid 并且进入应用内发现们依然没有到账。于是他俩都把应用删除了,然后从新下载安装发现,还是没有到货。他们俩很气,找到你们公司客服说:你丫的,怎么我买了东西都没到账。我都收到苹果发送的凭据了。你们信不信我去工商局告你?然后你们客服问后台说,后台,他们说他们已经付款了,但是没收到货,你能查一下后台有没有对应的订单么?然后后台赶紧去看一下,竟然没有对应的订单。于是猜测说他们是来讹我们的,不用管。于是就这样,用户付了钱没收到东西,服务器端也找都不到对应的订单。大家相互猜疑和指责。问题不了了之,对用户而言,他们无辜的浪费了金钱得不到东西,体验很差。对公司而言,无法确认问题,导致用户的流失。这都是我们不想遇到的。那这个问题的出现原因在哪里呢?首先,如果按照漏单流程来走的话,获取A用户在下次进应用的时候就会收到东西,但是他没有这样而是在B的手机上登陆了APPid,也没到账的情况下,他们把应用都删除了。重点就是这一步,删除了。一般的补单流程是这样的,如果没告诉苹果这笔订单已经完成,那么下次进来的时候,他会走一个补单的流程。也就是重新走 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions,成功后走服务器验证->验证成功之后发货。但是如果删除了应用那问题就不一样了。假如,你们后台的验证流程是需要购买凭证以及平台订单号的。那么如果删除了应用,那此时走补单的流程,这个订单号该怎么获取?因为删除了应用也就是说,之前存储的订单号都没了,那为什么B的手机也没法补单成功呢?那是因为B本来就没存储平台订单号。所以去服务器验证当然也验证不过,因为缺少平台订单参数,所以请求无法完成。在iOS7 之前,针对这种情况,没法解决这种情况。但是在iOS7 之后。苹果新增了一个applicationUsername的属性,那这个属性是干嘛的?这个属性是在创建内购支付的透传参数,在iOS 7 之后苹果新增的。他的作用,在发起支付前把这个参数的值设置为平台订单号,是在购买成功之后,这个参数原样一并返回到回调方法的transcation 中,通过transcation.payment.applicationUsername可以获取到,而且是每笔订单一一对应的。这样我们在创建交易的时候加上这个参数,这个参数的值为我们的平台订单号,这样我们的平台订单就能跟每笔内购交易对应上了。

SKMutablePayment * payment = [SKMutablePayment paymentWithProduct:product];
//内购透传参数,与transaction一一对应
 payment.applicationUsername = self.order;
[[SKPaymentQueue defaultQueue] addPayment:payment];

因此,无论我们在那台手机上登录,都可以获取到交易对应的平台订单,也就可以向服务器验证成功了。耶~~

坑4

当你因为某种原因购买了东西,但是没告诉苹果这个交易已经完成的时候再次发起购买,会发生什么事呢?你会发现出现一个提示“您已购买此App内购买项目,此项目将会免费恢复”。当然出现这种的可能性不高,但是还是会有遇到。如果是消耗性的商品,如果不处理会导致这个内购项目一直无法购买的问题。那怎么处理呢?首先,在以下方法中存储着未完成的单,

NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;

在发起新的购买之前,我们先去检查一下是否有已经购买成功但是未结束交易的单,如果有的话,实现以下代码将未结束交易的单结束掉再发起新的购买就OK 啦。

  NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
if (transactions.count > 0) {
    //检测是否有未完成的交易
    SKPaymentTransaction* transaction = [transactions firstObject];
    if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];            
        return;
    }
}

github 的直通链接 https://github.com/jiajiaailaras/ULIPAPurchase 觉得有帮助的。可以给个Star 支持一下。

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

推荐阅读更多精彩内容