ApplePay In-App Provisioning記錄

[TOC]

In-App是什么室抽?

    全稱:Apple Pay In-App Provisioning。就是在應用內(nèi)配置信息,把用戶的銀行卡直接綁定到用戶手機的Apple Wallet中,而不需要用戶手動輸入信息,提供了良好的用戶體驗乔妈。無需跳出應用,直接把用戶信息通過Passkit SDK提供給蘋果氓皱,達到綁卡的目的路召。 starts and finishes 整個流程都是在App中。

    我們要做的波材,也就是ApplePay的應用內(nèi)綁卡功能股淡。和使用AppleWallet綁卡是一樣的,綁卡后可以通過ApplePay購物支付等廷区。

綁卡流程

關鍵角色

  1. Bank Client:對接用戶
  2. Bank Server:提供用戶/卡信息
  3. PKPass:提供Apple Wallet相關查詢/綁卡接口
  4. Apple Server:接收PKPass數(shù)據(jù)
  5. Visa:移動支付運營商(PNO:Payment Network Operator)唯灵。
  6. FD:卡信息提供商,負責發(fā)卡

解釋一下Visa與FD他們的區(qū)別:

Visa是支付運營商躲因,他們定義了一套支付的協(xié)議早敬,不同的銀行可以對接其協(xié)議,達成支付能力大脉。他們會給每個銀行一個編號搞监,支付過程首先流轉到Visa,再根據(jù)編號識別屬于什么銀行镰矿,然后做數(shù)據(jù)流轉琐驴。

類似的還有美國運通,

FD是卡信息提供商秤标,Bank的卡片生成也是FD生成的绝淡,包括cvv2,日期苍姜,卡號等信息牢酵。支付過程中卡信息的確認也發(fā)生在FD。

Bank銀行負責記錄賬戶與卡之間的關系衙猪,卡余額也是記錄在Bank方馍乙。

關鍵短句

  1. DPAN:Device Primary Account Number 設備主賬號(與銀行卡號唯一對應的一串號碼布近,如:V-999916888641233333222)
  2. FPAN:Funding Primary Account Number 資金主賬號(銀行卡號)
  3. SEID:蘋果手機的一個序列號(NFC模塊的序列號)
  4. ECC:Elliptic Curve Cryptography(橢圓曲線加密),蘋果綁卡就是經(jīng)過ECC-V2加密傳輸?shù)摹?/li>
  5. PNO:Payment Network Operator丝格,支付網(wǎng)絡運營商撑瞧,我們目前PNO就是Visa。
  6. regular provisioning flow:常規(guī)認證流程显蝌。無論是蘋果支付预伺,或華為/小米/谷歌,有的只是加解密過程不一樣曼尊,最終解密完成之后的拿到明文Payload酬诀,執(zhí)行綁定操作。

綁卡流轉流程

image

蘋果文檔上介紹的涩禀,是單獨的綁卡過程料滥。下面講述的,是我們Bank綁卡的實際流程艾船,包括綁卡入口的判定葵腹。

1. Bank App判斷是否顯示綁卡按鈕。

想要顯示綁卡入口屿岂,需要滿足兩點:

  1. 設備及系統(tǒng)支持践宴。(iphone6 ios9.0+)
  2. Bank的綁卡功能開關。Bank添加了開關功能爷怀,基于此項開的情況下阻肩,才去判斷PKPassLibrary。
  3. 未被添加到當前設備(連接iwatch的情況下运授,需要兩者都被綁定才會不顯示)烤惊。

App向Bank后臺請求ApplePay相關數(shù)據(jù),根據(jù)拿到的DPAN吁朦,放到PKPassLibrary的canAddPaymentPass接口判斷是否已綁定柒室,如已綁定則不展示。

2. Bank用戶點擊Add To Apple Wallet按鈕逗宜,觸發(fā)綁卡流程雄右。

首先,Bank App會向Bank后臺發(fā)起一個請求纺讲,后臺返回啟動綁卡的config信息擂仍,包含的關鍵信息有:

  1. cardHolderName 持卡人姓名
  2. paymentNetwork 支付運營商
  3. primaryAccountNumberSuffix 主賬戶后4位
  4. primaryAccountIdentifier(DPAN)PassKit可以根據(jù)其判斷是否展示AppleWatch綁卡。

3. App生成Config熬甚,并通過PKAddPaymentPassViewController調(diào)起In-App界面逢渔。

PKAddPaymentPassViewController這個VC的生成有條件限制,不符合條件會返回nil乡括,后面會細說复局。

添加到Wallet入口 In-App綁卡界面 綁卡失敗示例
image
image
image

4. 點擊下一步冲簿,會觸發(fā)PassKit代理,待App上傳交易加密字段

加密發(fā)生在Bank后臺亿昏,這也是蘋果推薦的一種方式,保證了數(shù)據(jù)的安全档礁。

蘋果回調(diào)返回certificates/nonce/nonceSignature角钩,這三個數(shù)據(jù)發(fā)送給Bank后臺,后臺驗證證書鏈的合法性呻澜,如正確递礼,把綁卡相關用戶信息兩重加密,回傳給App羹幸。

App把從Bank后臺收到的encryptedPassData/ephemeralPublicKey/activationData脊髓,經(jīng)base64反解,組裝成PKAddPaymentPassRequest栅受,通過PassKit代理中的handler傳送給蘋果将硝。

5. 蘋果解密ECCV2數(shù)據(jù),并把解密后數(shù)據(jù)K傳給Visa

加解密是整個In-App綁卡中最重要和關鍵的部分屏镊,也是最容易出錯的部分依疼。比較難排查,需要有蘋果人員配合分析日志而芥。

Note:蘋果對于加解密律罢,有一份Test Vector,可以郵件蘋果或者直接給蘋果對接人要棍丐。有了這個Test Vector误辑,后臺就能準確對比加密過程中每一步的結果值,確保加密無誤歌逢。

Note:在我們多次的異常解決過程中巾钉,蘋果給的郵件回復往往能很直接的命中要害,所以要多郵件溝通趋翻。

6. Visa解密K得到原始JSON睛琳,進入常規(guī)認證流程。

encryptedPassData中Payload一般是這個樣子的踏烙,外面再套兩層加密(for Visa师骗,for Apple),也保證了數(shù)據(jù)傳輸?shù)陌踩浴?/p>

{
"primaryAccountNumberPrefix":"xxx626", "encryptedPrimaryAccountNumber":"TUJQxxxxxx1GSy0xxwNjQuMS0tVERFQS1BOEZFOEVGRTdFNzlFN",
"nonceSignature":"xxx089C255A06ExxxF1702BA74715D9xxx1C5CBD7xxxx90A6F06B94ED67D231765D", "networkName":"Visa",
"name":"xxxxxxxxxleseed",
"nonce":"0aa6xxx98"
}

綠色/橙色流程

對于每個綁卡認證請求讨惩,蘋果都會給出對應的風險等級建議辟癌。

  • 綠色流程:大部分蘋果給出的建議都是綠色流程,可以直接認證綁卡成功荐捻,不需要其它用戶身份驗證黍少。
  • 黃色流程:必須先驗證身用戶身份寡夹,由發(fā)卡行提供驗證選項,具體方式可以通過(SMS/EMAIL/phone-call)
  • 橙色流程:需要更嚴格的身份驗證厂置,蘋果推斷出可能存在欺詐行為(Apple賬戶/歷史記錄)菩掏,需要上報反欺詐團隊嚴格驗證。
  • 紅色流程:拒絕認證昵济。

蘋果Must條款

  1. 發(fā)卡行必須支持應用內(nèi)綁卡智绸,包括iPad(提供安全/無縫的用戶體驗)。
  2. 提交審核資料時访忿,必須附帶應用內(nèi)綁卡的相關視頻瞧栗。
  3. 不能自定義“Add To Apple Wallet”按鈕,否則蘋果會拒絕海铆。
  4. 必須支持遠程啟用/禁用功能迹恐。
  5. 必須支持推送通知(后臺邏輯)。
  6. 必須支持卡生命周期管理(后臺邏輯)卧斟。

iOS端接入PassKit

1. 提供原生“Add To Apple Wallet”按鈕

因為蘋果不支持自定義按鈕殴边,所以需要把原生按鈕,橋接到Flutter/RN唆涝,具體代碼不再展示找都,遵守其規(guī)則就行。

2. 是否展示“Add To Apple Wallet”按鈕

RN寫在了RNApplePayService廊酣,F(xiàn)lutter寫在了ApplePayFlutterService中能耻,功能包括4個:

  1. 是否需要展示按鈕
  2. 是否包含此卡
  3. 卡片激活狀態(tài)
  4. 開始綁卡流程
//RNApplePayService
//MARK: 獲取AppleWallet狀態(tài): 0.不支持 1.已綁定完成(iPhone/當前連接iWatch) 2.可綁定
@objc public func appleWalletState(_ primaryAccountIdentifier: String,
                                            _ resolve: RCTPromiseResolveBlock,
                                            _: RCTPromiseRejectBlock) {
        /// 檢查是否應該顯示添加到Wallet按鈕
        /// @param primaryAccountIdentifier 賬戶標識
        guard PKAddPaymentPassViewController.canAddPaymentPass() else {
            print("客戶端不能進行ApplePay的設備卡加載")
            resolve(0)
            return
        }
        // 從服務器緩存取applePaySwitch狀態(tài)
        if (!SingleInstanceSettings.applePaySwitch) {
            resolve(0)
            return
        }
        // 從SDK取結果
        let library = PKPassLibrary()
        let can = library.canAddPaymentPass(withPrimaryAccountIdentifier: primaryAccountIdentifier)
        resolve(can ? 2 : 1)
    }

2. 綁卡流程

import Foundation
import PassKit
import RxSwift
import XCGLogger

public typealias BankAddToWalletCallback = (_ finished: Bool, _ error: NSError?) -> Void

public class BankApplePayUtil: NSObject, PKAddPaymentPassViewControllerDelegate {
    public var callback: BankAddToWalletCallback?
    @objc public var cardNo = "" // 需要用戶傳入
    @objc public func addToWalletStart() {
        BankApplePayAPI.fetchPaymentConfiguration(cardNo: cardNo) { [weak self] result, error in
            guard let params = result else {
                XCGLogger.default.info("接口返回有誤,請檢查:\(error ?? "")")
                return
            }
            guard let config = self?.parseConfig(params) else {
                XCGLogger.default.info("生成PKAddPaymentPassRequestConfiguration失敗")
                return
            }
            // 主線程調(diào)用UI
            DispatchQueue.main.async {
                guard let addPaymentVC = PKAddPaymentPassViewController(requestConfiguration: config, delegate: self) else {
                    XCGLogger.default.info("AddPaymentVC生成失敗亡驰,請檢查晓猛!")
                    return
                }
                if #available(iOS 13.0, *) {
                    addPaymentVC.overrideUserInterfaceStyle = .light
                }
                self?.topVC?.present(addPaymentVC, animated: true, completion: nil)
            }
        }
    }

    private var topVC: UIViewController? {
        var controller = UIApplication.shared.keyWindow?.rootViewController
        if let rootVC = controller {
            var presentedController = rootVC.presentedViewController
            if let presentVC = presentedController, !presentVC.isBeingDismissed {
                controller = presentedController
                presentedController = controller?.presentedViewController
            }
            return controller
        }
        return controller
    }

    private func parseConfig(_ params: [String: Any]) -> PKAddPaymentPassRequestConfiguration? {
        guard let config = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
            XCGLogger.default.info("PKAddPaymentPassRequestConfiguration生成失敗凡辱!")
            return nil
        }
        if #available(iOS 12.0, *) {
            config.style = .payment
        }
        config.cardholderName = params["cardHolderName"] as? String
        config.paymentNetwork = PKPaymentNetwork(params["paymentNetwork"] as? String ?? "Visa")
        config.primaryAccountSuffix = params["primaryAccountNumberSuffix"] as? String
        config.primaryAccountIdentifier = params["primaryAccountIdentifier"] as? String
        config.localizedDescription = params["localizedDescription"] as? String
        return config
    }

    // MARK: PKAddPaymentPassViewControllerDelegate

    /// 向發(fā)卡方提供證書鏈戒职、nOnce, nOnceSignature等信息
    /// 重要:回調(diào)20s未執(zhí)行, 則會視為失敗
    /// - Parameters:
    ///   - controller: VC
    ///   - certificates: 證書鏈
    ///   - nonce: nonce
    ///   - nonceSignature: nonceSignature
    ///   - handler:
    ///   - activationData: ?次性加密OTP透乾,?于確保加載請求的安全合法洪燥,由發(fā)卡方生成和驗證(可省略)
    ///   - encryptedPassData: 數(shù)據(jù)加密后的JSON?件
    ///   - ephemeralPublicKey: ECC算法使用,發(fā)卡方生成的隨機公鑰
    ///   - wrappedKey
    public func addPaymentPassViewController(_: PKAddPaymentPassViewController,
                                             generateRequestWithCertificateChain certificates: [Data],
                                             nonce: Data,
                                             nonceSignature: Data,
                                             completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void) {
        // Data -> String
        func stringfy(_ data: Data) -> String {
            return data.base64EncodedString()
        }
        BankApplePayAPI.fetchPaymentData(cardNo: cardNo,
                             certificates: certificates.map { stringfy($0) },
                             nonce: stringfy(nonce),
                             nonceSignature: stringfy(nonceSignature)) { result, error in
            guard let params = result else {
                XCGLogger.default.info("接口返回有誤乳乌,請檢查:\(error ?? "")")
                return
            }
            guard let data = params["encryptedPassData"] as? String,
                let key = params["ephemeralPublicKey"] as? String,
                let otp = params["activationData"] as? String else {
                XCGLogger.default.info("接口返回參數(shù)有誤"); return
            }
            let encryptedPassData = Data(base64Encoded: data)
            let ephemeralPublicKey = Data(base64Encoded: key)
            let activationData = Data(base64Encoded: otp)
            let request = PKAddPaymentPassRequest()
            request.activationData = activationData

            request.encryptedPassData = encryptedPassData
            request.ephemeralPublicKey = ephemeralPublicKey
            handler(request)
        }
    }

    /// 加載完成結果
    /// - Parameters:
    ///   - controller: VC
    ///   - pass: 申請得到的pass
    ///   - error: 失敗參數(shù)
    public func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController, didFinishAdding pass: PKPaymentPass?, error: Error?) {
        XCGLogger.default.info("\(error?.localizedDescription)")
        controller.dismiss(animated: true, completion: nil)
        if let c = self.callback {
            if pass != nil {
                c(true, nil)
            } else if error != nil {
                c(false, NSError.init(domain: error!.domain, code: error!.code, userInfo: nil))
            }
        }
    }
}

public class BankApplePayAPI: NSObject {
    /// 查詢支付Configuration
    /// @param cardNum 卡號
    public static func fetchPaymentConfiguration(cardNo: String,
                                                callback: @escaping (_ result: [String: Any]?, _ error: String?) -> Void) {
        var bag: DisposeBag? = DisposeBag()
        APIFetch.fetch(host: host,
                      path: path,
                      parameters: ["cardNumber": cardNo],
                      options: nil,
                      method: Post,
                      disposeBag: bag!)
            .subscribe(onNext: { json in
                guard let dict = json as? [String: Any] else {
                    callback(nil, "返回字段非字典類型捧韵,請檢查"); return
                }
                callback(dict["value"] as? [String: Any], nil)
            }, onError: { error in
                callback(nil, error.localizedDescription)
            }) {
                bag = nil; print("清理")
            }.disposed(by: bag!)
    }

    /// 查詢支付數(shù)據(jù)
    /// @param cardNumber 卡號
    /// @param certificates 證書文件的base64字符串 0    葉子證書 1    中級證書 2    root證書  (這個沒有可不傳入)
    /// @param nonce 隨機數(shù)
    /// @param nonceSignature 加密后隨機數(shù)
    /// /mb/nmm33g/debit-card/apple/encrypt
    public static func fetchPaymentData(cardNo: String,
                                 certificates: [String],
                                 nonce: String,
                                 nonceSignature: String,
                                 callback: @escaping (_ result: [String: Any]?, _ error: String?) -> Void) {
        var bag: DisposeBag? = DisposeBag()
        BankFetch.fetch(host: host,
                      path: path,
                      parameters: [
                          "cardNumber": cardNo,
                          "certificates": certificates,
                          "nonce": nonce,
                          "nonceSignature": nonceSignature,
                      ],
                      options: nil,
                      method: Post,
                      disposeBag: bag!)
            .subscribe(onNext: { json in
                guard let dict = json as? [String: Any] else {
                    callback(nil, "返回字段非字典類型,請檢查"); return
                }
                callback(dict["value"] as? [String: Any], nil)
            }, onError: { error in
                callback(nil, error.localizedDescription)
            }) {
                bag = nil; print("清理")
            }.disposed(by: bag!)
    }
}

如何測試汉操?

測試必須是production環(huán)境再来。

蘋果有文檔指出可以以下3種方式:

1. Sandbox

2. TestFlight

但蘋果一直強調(diào),sandbox不穩(wěn)定,推薦TestFlight芒篷。Visa方不支持sandbox搜变,只有線上環(huán)境。所以我們選擇直接在TestFlight測試针炉。

3. AppStore

蘋果推薦挠他,在TestFlight通過后,上線時在AppStore驗證篡帕。

注意

  1. TestFlight測試绩社,ios最低版本必須選擇>=10.3,否則調(diào)不起in-app流程赂苗。
  2. 出現(xiàn)綁卡失敗問題,需要提供機器的SEID給蘋果贮尉,蘋果可以協(xié)助查找原因拌滋。

遇到的問題點

1. PKAddPaymentPassViewController返回nil,無法調(diào)起in-app流程

  1. 首先猜谚,蘋果要給開通In-app權限败砂,需要在develop.apple.com中,編輯并勾選權限關聯(lián)到證書中魏铅。
  2. 需要在Xcode中配置entitlements文件昌犹,添加com.apple.developer.payment-pass-provisioning為YES
  3. TestFlight測試,ios最低版本必須選擇>=10.3览芳,否則調(diào)不起來斜姥。

2. 調(diào)起in-app后,綁卡失敗

這個問題點就多了沧竟,大多失敗在Visa及FD铸敏,我簡單總結幾點

  1. App是否開了代理。蘋果能檢測到抓包悟泵,直接報網(wǎng)絡失敗杈笔。我調(diào)試時是先切抓包,map顯示卡tab糕非,然后切非抓包網(wǎng)絡蒙具,點擊進入in-app流程。
  2. 白名單(卡ID + SEID)
  3. 銀行后臺準備JSON字段錯誤
  4. 銀行后臺加密存在錯誤(蘋果加密一層朽肥,Visa加密一層禁筏。Bank傳給蘋果,蘋果解密后發(fā)給Visa鞠呈,Visa解密后拿到初始JSON)(1. ephemeralPublicKey 65bytes 2. ephemeralPublicKey需要轉為Hex)
  5. 需要在TestFlight測試融师,并且testFlight要求iOS >= 10.3(很奇怪,上線卻只要求>=9.0)

3. 綁卡后蚁吝,PKPassLibrary().passes()找不到對應卡片

檢查VCMM(Visa提供的錄入用戶數(shù)據(jù)的平臺)上associatedApplicationIdentifiers字段旱爆,他是由兩段組成“teamID.bundleId”舀射,需要填入App對應的數(shù)據(jù)。如果填錯怀伦,PKPassLibrary內(nèi)方法將返回不準確脆烟,及拿不到passes。

4. 綁卡后房待,PKPassLibrary().canAddPaymentPass仍返回true

同上

5. 無法智能提示iPhone或iWatch去綁卡

當設備同時有iPhone及iWatch時邢羔,如果我的iPhone已經(jīng)添加綁定,此時再次點擊“Add To Apple Wallet”按鈕桑孩,希望直接走iWatch的綁定(而不是出現(xiàn)選擇界面)拜鹤。

檢查PKAddPaymentPassRequestConfiguration的primaryAccountIdentifier配置,是否有傳入DPAN值流椒,它就是蘋果用來篩選設備的敏簿。我們就是因為后臺返回了空導致filter無效。

6. Flutter中宣虾,“Add To Apple Wallet”的橋接UI覆蓋住了Alert

Flutter的繪制原理惯裕,就是在bitmap上繪制合成完成,才進行渲染绣硝。對于原生來說蜻势,無論你Flutter在哪個頁面,頁面包含多少元素鹉胖,在原生調(diào)試就是薄薄的一層界面握玛。

而我們的首頁Alert也是Flutter實現(xiàn)的,所以原生按鈕無法被夾心次员,浮在了Alert的上方败许,導致?lián)踝lert。

解決辦法是:

在初始化FlutterPlatformView時,按鈕應在init中實例化,在獲取View時直接return此實例悯衬。Flutter會自動判斷被原生組件蓋住的部分叮称,然后再原生層上層再繪制被覆蓋的區(qū)域,看上去仿佛原生被夾心。

required init(frame: CGRect, viewID: Int64, args: Any?, binaryMessenger: FlutterBinaryMessenger) {
  button = PKAddPassButton(addPassButtonStyle: .blackOutline)
}
func view() -> UIView {
    return button
}

難點

1. 調(diào)試

相比其它需求開發(fā),調(diào)試相對是困難的。

  1. ---音羞。
  2. release環(huán)境,無法抓包仓犬,無法調(diào)試嗅绰,很多次都通過上TestFlight,Toast報調(diào)試信息。
  3. 測試必須發(fā)到TestFlight窘面,我使用Adhoc證書試翠语,都不可以。
  4. 想要有卡table入口财边,必須加白名單肌括。
  5. 無測試環(huán)境,只能發(fā)布生產(chǎn)驗證bug酣难,及bug修改后是否修復谍夭。

2. 英文溝通

無論是蘋果還是FD,對話都是英語憨募。

蘋果給出的官方文檔紧索,是全部英文的; 挺多次的視頻通話是全英文,很考驗聽力; 出問題時菜谣,咨詢蘋果也要英文對話齐板,全靠大能的谷歌翻譯。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葛菇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子橡羞,更是在濱河造成了極大的恐慌眯停,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卿泽,死亡現(xiàn)場離奇詭異莺债,居然都是意外死亡,警方通過查閱死者的電腦和手機签夭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門齐邦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人第租,你說我怎么就攤上這事措拇。” “怎么了慎宾?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵丐吓,是天一觀的道長。 經(jīng)常有香客問我趟据,道長券犁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任汹碱,我火速辦了婚禮粘衬,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己稚新,他們只是感情好勘伺,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枷莉,像睡著了一般娇昙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笤妙,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天冒掌,我揣著相機與錄音,去河邊找鬼蹲盘。 笑死股毫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的召衔。 我是一名探鬼主播铃诬,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苍凛!你這毒婦竟也來了趣席?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤醇蝴,失蹤者是張志新(化名)和其女友劉穎宣肚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠栓,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡霉涨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惭适。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笙瑟。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖癞志,靈堂內(nèi)的尸體忽然破棺而出往枷,到底是詐尸還是另有隱情,我是刑警寧澤凄杯,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布师溅,位于F島的核電站,受9級特大地震影響盾舌,放射性物質發(fā)生泄漏墓臭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一妖谴、第九天 我趴在偏房一處隱蔽的房頂上張望窿锉。 院中可真熱鬧酌摇,春花似錦、人聲如沸嗡载。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洼滚。三九已至埂息,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遥巴,已是汗流浹背千康。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铲掐,地道東北人拾弃。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像摆霉,于是被迫代替她去往敵國和親豪椿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354