微信公众号开发--支付完整流程

微信公众号支付的完整流程,首先需要微信授权,获取openId,因为openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户,同时也是微信JSAPI支付的必传参数。

首先解释一下微信公众号中的一些概念,想要完成支付,需要被认证的公众号外,还需要商户号。这两个都需要有一定资质才能申请。单纯拥有公众号,只能进行微信授权操作,需要公众号和商户号绑定后才能完成支付操作。
用个不够恰当的例子来解释:公众号就类比于银行的前台,商户号就类比于银行,前台不绑定银行的话那她就是一个普通人,不能完成银行的各类经济业务,前台和银行绑定后才可以操作款项。而这个前台也要通过有一定的资质认证,才能和银行绑定。
注:微信支付的接口不只有公众号支付一种,但是无论哪种支付接口,都需要绑定商户号才能进行支付操作。

获取OpenId

获取OpenId,有两种方式,“手工方式”和“利用第三方API”,最终目的都是一样的,但是在实际开发中还是用轮子比较容易。手工方式最主要的是一步一步的了解获取OpenId的过程,如果以使用为主,可以直接跳过“手工方式”,查看“利用第三方API”。


手工方式

首先,很重要也是很多人懒得去做的事情就是仔细看看【微信支付】商户接入文档,内容很多,因为是微信公众号,所以我选择JSAPI支付

普通商户接入文档界面

打开链接可以看到JSAPI支付的详细内容,接下来的操作都是根据JSAPI支付中的“业务流程”逐步完成。
JSAPI支付详细

PS.其实下图链接网页授权获取用户openid接口文档也是真的写的很清楚了。当trade_type=JSAPI时(即公众号支付),openId必传。
openId的重要性

1. 设置网页授权域名

按照文档来,在公众号中【设置网页授权域名】,这里接入的是外网地址,通俗但不够准确的讲,就是你程序所在的域名。


填写完域名之后,记得下载文件,因为规定很多,还需要ICP备案什么的,作为调试,我选择使用了https://natapp.cn/穿透内网,就可以使得微信这边访问到自己的电脑。

在NATAPP中,注册/登录,购买隧道,免费的只能临时用一下,还会随便换域名/端口,所以我购买了9/月的。

查看购买到的隧道,这个域名是你在购买过程中,他让你自己输入的。

查看教程NATAPP 1分钟快速图文教程,启动NATAPP。启动后会看到域名映射到当前本地端口。
此时通过在浏览器中,输入localhost:8080/myselfhttp://my.natapp.com/myself 访问的是同一个界面则表示成功。
再将之前在微信【网页授权域名】中下载的 MP_verify_nxxxxxx.txt文件放到源码文件中。
注意:这里要求文件的位置,必须是在域名的根目录下,在本例中也就是在浏览器中输入http://my.natapp.com/MP_verify_nxxxxxx.txt后,页面不报错时,在点击【确认】才能在微信网页授权域名中添加成功。
当然,添加成功域名之后,这个MP_verify_nxxxxxx.txt文件也可以从源码文件中删除。

2. 获取code

可以查看微信文档,并了解相应参数说明。以下是微信文档的相应截图,我们只需要了解的就是替换掉链接中的appId和redirect_uri。
参数说明

用户同意授权后,跳转到redirect_uri并返回code。
用户同意授权
3. 换取access_token

获取了code之后,以code作为票据再换access_token。以下是微信文档的相应截图,我们只需要了解的就是替换掉链接中的appId为自己的appId和code为刚刚后台获取的code(注:code时效只有5min)。
换取access_token
4. 得到openId

在上一个步骤中,获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。请求上述链接时,正确时会返回如下JSON数据包。其中就包含了openId。
JSON数据包

更多使用,可以仔细查看微信文档!!文档写的很细致的!!

以我自己为例,当我手机微信访问了下面这个地址(网页授权URL)之后
https://open.weixin.qq.com/connect/oauth2/authorize?appid=myappId&redirect_uri=http://my.natapp.com/myRedirectURL&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
手机微信会自动跳转到http://my.natapp.com/myRedirectURL/code=xxcodexx,虽然手机页面是空白,但是后台已经获得了code的信息。后台可以通过拼接字符串等操作,再发起请求https://api.weixin.qq.com/sns/oauth2/access_token?appid=myappId&secret=SECRET&code=xxcodexx&grant_type=authorization_code,之后获得一个包含了openId信息的JSON数据包。


利用第三方API

直接看Github上的SDK:https://github.com/Wechat-Group/WxJava。里面文档、工具都非常详细。
因为我本地程序用的Maven,所以直接引用。[pom代码1]

<dependency>
  <groupId>com.github.binarywang</groupId>
  <artifactId>weixin-java-mp</artifactId>
  <version>3.5.0</version>
</dependency>

在本地函数中的使用,主要查看文档https://github.com/Wechat-Group/WxJava/wiki/MP_OAuth2网页授权
根据文档逐步完成本地代码。
新建WechatController.class,控制网络授权。[授权代码1]

@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatController {

    @Autowired
    WxMpService wxMpService = new WxMpServiceImpl();

    @GetMapping("/authorize")
    public String authorize(@RequestParam("returnUrl") String returnUrl) {
        // 1.配置,项目中配置应该是进行一个统一配置,供程序各个部分使用。
        // 2.调用方法,下面这个回调地址 是我自己的地址,你需要用你自己的
        String url = "http://sell35.natapp1.cc/sell/wechat/userInfo";
        String redirectUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAUTH2_SCOPE_BASE, URLEncoder.encode(returnUrl));
        log.info("【微信网页授权】获取code, result={}", redirectUrl);

        return "redirect:" + redirectUrl;
    }

    // 获取用户信息 
    @GetMapping("/userInfo")
    public String userInfo(@RequestParam("code") String code,
                         @RequestParam("state") String returnUrl) {
        WxMpOAuth2AccessToken wxMpOAuth2AccessToken = new WxMpOAuth2AccessToken();
        try {
            wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
        } catch (WxErrorException e) {
            log.error("【微信网页授权】{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        String openId = wxMpOAuth2AccessToken.getOpenId();

        return "redirect:" + returnUrl + "?openid=" + openId;

    }
}

建立config文件夹,并在下面新建WeChatMpConfig.class。将Service作为一个Bean、配置也作为Bean。 其中的AppId和AppSecret我们可以从配置文件中读取。[授权代码2]

@Component
public class WechatMpConfig {

    @Autowired
    private WechatAccountConfig accountConfig;

    @Bean
    public WxMpService wxMpService(){
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
    }

    @Bean
    public WxMpConfigStorage wxMpConfigStorage(){
        WxMpInMemoryConfigStorage wxMpConfigStorage =new WxMpInMemoryConfigStorage();
        wxMpConfigStorage.setAppId(accountConfig.getMpAppId());
        wxMpConfigStorage.setSecret(accountConfig.getMpAppSecret());
        return wxMpConfigStorage;
    }
}

配置文件

微信账号相关的部分先写一个配置文件。WechatAccountConfig.class[授权代码3]

@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {

    private String mpAppId;

    private String mpAppSecret;
}


前端调试

首先先讲一下请求过程,微信访问sell.com,前端回会重定向到 /sell/wechat/authorize,并携带returnUrl:http://sell.com/abc。 通过上一步骤的授权操作获取openid,最后后端返回给前端 :http://sell.com/abc?openid=oxfjhaojdnsjcos

所以需要先在前端重定向,进入虚拟机(前端部署部分)
cd /opt/
cd code/
cd sell_fe_buyer/
cd config/
配置文件
vim index.js
在配置文件中
sellUrl对应的是项目地址:http://sell.com
openidUrl获取openId的地址:http://sell35.natapp.cc/sell/wechat/authorize
wechatPayUrl支付地址(当前主要是配置授权,先无需配置这一项)

配置完成后,回到前端项目的根目录(cd ..)。

再构建一下(npm run build)

构建好的文件,在dist目录下,所以需要将构建好的文件copy到前端的根目录下,语句如下。
cp -r dist/* /opt/data/wwwroot/sell

但是此时我们通过手机访问"sell.com"是访问不了的。这是因为当前目标网址“sell.com”是在电脑端,电脑之所以能访问,是因为本机设置了host,他将域名直接指向虚拟机地址,所以可以成功访问,但是由于手机无法更改host。所以需要用代理解决这个问题。将手机的所有请求转发到电脑上,此时就可以访问了。Mac下可以使用Charles,Windows下可以使用fiddler,

通过终端输入ifconfig,得到当前电脑ip为 192.168.1.103.
再通过手机查询当前手机的ip为 192.168.1.105.

最好二者接通前在terminal中ping一下。

ping通之后,在手机中设置手动代理
服务器中输入:电脑ip(103)
端口输入:8888(因为Charles的默认端口就8888)

此时再在手机端访问sell.com,就可以通过电脑访问到公众号网站。


微信支付

支付业务流程:生成商户订单(开发者生成的订单) —> 调用统一下单API —> 生成预付单后会返回一个预付单信息 —> 通过JSAPI页面调用的支付参数并签名(此时才会唤起支付) —> 支付完成后等待一个异步通知结果 —> 依据这个结果通知更改订单状态为已支付 —> 调用查询API,查询支付结果(用于对账)

选择SDK,可以选择之前的那个SDK,这里我选择的是Best Pay SDK

请求过程:

  1. 重定向到 /sell/pay/create,携带参数(orderId:123456returnUrl:http://xxx.com/abc/order/123456
  2. 最后返回到 http://xxx.com/abc/order/123456

这里需要注意的是,支付过程中,只需要传过来订单ID即可,至于需要支付多少钱,可以通过订单ID去数据库查看。不能将支付金额作为参数往后传递,因为这样即便金额不对,也能够支付成功,或者后台再校验一边金额,无论怎样,都是多此一举。

引入SDK,在Pom文件中添加依赖。[pom代码2]

<dependency>
  <groupId>cn.springboot</groupId>
  <artifactId>best-pay-sdk</artifactId>
  <version>1.1.0</version>
</dependency>

新建一个PayController Class,主要完成订单查询和支付操作。[该段代码非最终代码,至于此处以便与思考] 。

@Controller
@RequestMapping("/pay")
public class PayController {

    @Autowired
    private OrderService orderService;

    // PayService之后作为服务新建,当前并不存在。
    @Autowired
    private PayService payService;

    // 为了重定向,完成请求过程的第一步
    @GetMapping("/create")
    public void create(@RequestParam("orderId") String orderId,
                       @RequestParam("returnUrl") String returnUrl) {
        //1. 查询订单
        OrderDTO orderDTO = orderService.findOne(orderId);
        if (orderDTO == null) {
            throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
        }

        //2. 发起支付
        PayResponse payResponse = payService.create(orderDTO);
    }
}

根据SDK规则,微信账户相关内容需要配置,配置在WechatAccountConfig中。[代码3]

@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {

    private String mpAppId;

    private String mpAppSecret;

    //    商户号
    private String mchId;
    //    商户密钥
    private String mchKey;
    //    商户证书路径
    private String keyPath;
    //    微信支付异步通知地址
    private String notifyUrl;
}

同时修改给配置文件增加相应内容

配置一下WechatPayConfig(),并把service作为Bean配置进去。[代码4]

@Component
public class WechatPayConfig {
    @Autowired
    private WechatAccountConfig accountConfig;

    @Bean
    public BestPayServiceImpl bestPayService(){
        WxPayH5Config wxPayH5Config=new WxPayH5Config();
        wxPayH5Config.setAppId(accountConfig.getMpAppId());
        wxPayH5Config.setAppSecret(accountConfig.getMpAppSecret());
        wxPayH5Config.setMchId(accountConfig.getMchId());
        wxPayH5Config.setMchKey(accountConfig.getMchKey());
        wxPayH5Config.setKeyPath(accountConfig.getKeyPath());
        wxPayH5Config.setNotifyUrl(accountConfig.getNotifyUrl());

        BestPayServiceImpl bestPayService=new BestPayServiceImpl();
        bestPayService.setWxPayH5Config(wxPayH5Config);

        return bestPayService;
    }

支付操作作为一个服务,新建PayService,并建立该方法的实现PayServiceImpl。[代码6] 并且将之前BestPayServiceImpl配置好的注入进Service。上述的JsonUtil是个JSON格式化工具类,已附追在文章末尾。

@Service
@Slf4j
public class PayServiceImpl implements PayService {
    private static final String ORDER_NAME = "微信点单订餐";

    @Autowired
    private BestPayServiceImpl bestPayService;

    @Override
    public PayResponse create(OrderDTO orderDTO) {
        PayRequest payRequest = new PayRequest();
        payRequest.setOpenid(orderDTO.getBuyerOpenid());
        payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
        payRequest.setOrderId(orderDTO.getOrderId());
        payRequest.setOrderName(ORDER_NAME);
        payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
        log.info("【微信支付】,发起支付,request={}", JsonUtil.toJson(payRequest));

        PayResponse payResponse = bestPayService.pay(payRequest);
        log.info("【微信支付】,发起支付,response={}", JsonUtil.toJson(payResponse));
        return payResponse;
    }
}

最后返回的 response 内容包含了 "appId""timeStamp""nonceStr""packAge""signType""paySign"的值。
此时完成了业务流程中的:调用统一下单API,并且返回预付单信息prepay_id"packAge"对应的值中)。
下一步我们要做的就是发起支付


从网页发起支付

支付操作的详细内容先仔细阅读文档JSAPI支付开发者文档
需要向后端先传递这些参数。

之后会返回如下图所示的前端代码,这个代码就是最后生成微信支付页的部分。

所以在代码部分,我们接下来的工作就是动态构造如上图所示的代码。

这里我们选择模版技术,用到了freemarker这个组件,现在pom文件中引入dependency

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

完善之前的PayController Class[代码5] 。将返回的参数从void改成ModelAndView,最后return返回的“pay/create”路径下的create实际上就是一个create.flt文件(模版文件)。

@Controller
@RequestMapping("/pay")
public class PayController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PayService payService;

    // 为了重定向,完成请求过程的第一步
    @GetMapping("/create")
    public ModelAndView create(@RequestParam("orderId") String orderId,
                               @RequestParam("returnUrl") String returnUrl,
                               Map<String, Object> map) {
        //1. 查询订单
        OrderDTO orderDTO = orderService.findOne(orderId);
        if (orderDTO == null) {
            throw new SellException(ResultEnum.PRODUCT_NOT_EXIST);
        }

        //2. 发起支付
        PayResponse payResponse = payService.create(orderDTO);
        map.put("payResponse", payResponse);
        map.put("returnUrl", returnUrl);

        return new ModelAndView("pay/create");

    }
}

create.flt中放的就是微信内H5调起支付
文档中返回的代码格式

<script>
    function onBridgeReady() {
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                "appId":"${payResponse.appId}",     //公众号名称,由商户传入
                "timeStamp":"${payResponse.timeStamp}",         //时间戳,自1970年以来的秒数
                "nonceStr":"${payResponse.nonceStr}", //随机串
                "package":"${payResponse.packAge}",
                "signType": "MD5",         //微信签名方式:
                "paySign":"${payResponse.paySign}" //微信签名
            },
            function (res) {
                // if (res.err_msg == "get_brand_wcpay_request:ok") {
                // }     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。
                location.href="${returnUrl}"
            }
        );
    }

    if (typeof WeixinJSBridge == "undefined") {
        if (document.addEventListener) {
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        } else if (document.attachEvent) {
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    } else {
        onBridgeReady();
    }
</script>

此时已经完成动态注入参数了,但是完成支付还需要我们在前端文件中配置一下,参考之前的【前端调试】模块。

记得改完之后,build和拷贝文件。

此时再去支付,支付完成后发现并没有得到“支付成功”的通知,这是因为我们没有修改订单状。在微信的支付业务流程中,我们还没有做处理微信异步通知结果这一步。
所以我们下一步的工作就是:接受微信的异步通知结果,并根据结果更改订单的支付状态。


微信异步通知

在微信内H5调起支付时,前端也可以接收到一个是否成功的标志。

注意这行注释,我们知道不能通过get_brand_wcpay_request的值去判断是否支付成功。因为在前端,该代码是有可能被篡改的。更安全的方式是根据后端的异步通知来确定是否支付成功。

在PayController Class中加入一个接受微信异步通知的方法notify。直接使用SDK中的notify处理方法。[代码7]

    @PostMapping("/notify")
    public void notify(@RequestBody String notifyData){
        payService.notify(notifyData);
    }

将异步通知的逻辑写入PayService、PayServiceImpl。[代码8]

    @Override
    public PayResponse notify(String notifyData) {
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        log.info("【微信支付】异步通知,payResponse={}", payResponse);
        return payResponse;
    }

同时需要在配置文件application.yml文件中配置notify地址。


支付成功后,需要修改订单的支付状态。也就是更改一下代码8中的内容。[代码9]

    @Override
    public PayResponse notify(String notifyData) {
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        log.info("【微信支付】异步通知,payResponse={}", payResponse);

        // 修改订单支付状态
        // 1 先查询一下当前订单状态
        OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
        // 2 修改订单状态
        orderService.paid(orderDTO);
        
        return payResponse;
    }

此时,我们可以发现代码安全性不足。在微信异步通知中,有几方面需要注意:

  1. 验证签名(验证一下这个签名是不是真正来自于微信,不然别人模拟一个微信验证请求,我们也会傻fufu的通过)
  2. 支付的状态(虽然会得到异步通知,但是消息的内容不一定是支付成功,也有失败等多种情况)
  3. 支付金额(有可能程序错误,导致微信回调之后的金额不够统一,所以需要校验金额)
  4. 付款人(下单人 == 支付人)(根据业务需要确定下单人和支付人是否一直,所以根据情况可以校验确认一下)

由于使用了SDK,所以第1、2点是不需要我们去做的。代码中我们还需要做第3步。
在判断金额中,要判断微信返回金额与系统金额是否一致,不仅需要保证二者的数据类型相同,也需要精度一致。所以把判断金额这个部分写入了单独的utils
MathUtil.class [代码10]

public class MathUtil {
    private static final Double Money_Range = 0.01;

    public static Boolean equals(Double d1, Double d2){
        Double result =  Math.abs(d1 - d2);
        if (result < Money_Range){
            return true;
        } else {
            return false;
        }
    }
}

完成MathUtil.class之后,我们也需要相应的更改代码9。[代码11]

    @Override
    public PayResponse notify(String notifyData) {
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        log.info("【微信支付】异步通知,payResponse={}", payResponse);

        // 修改订单支付状态
        // 1 先查询一下当前订单
        OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
        // 2 判断订单是否存在
        if (orderDTO == null) {
            log.error("【微信支付】异步通知,订单不存在。orderId={}", payResponse.getOrderId());
            throw new SellException(ResultEnum.ORDER_NOT_EXIST);
        }
        // 3 判断金额是否一致(因为很多判断中,由于精度的不同,会判断两个金额不一致,比如0.10和0.1;所以采用相减的方式,写在util工具类中)
        if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) {
            log.error("【微信支付】异步通知,订单不存在。orderId={}, 微信通知金额={}, 系统金额 ={}", payResponse.getOrderId(), payResponse.getOrderAmount(), orderDTO.getOrderAmount());
            throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
        }
        // 4 2、3步都通过后,再修改订单状态
        orderService.paid(orderDTO);

        return payResponse;
    }

根据微信支付业务流程,在支付成功后需要给微信返回“支付通知”,否则将会一直回调PayService 中的notify。如图是微信支付成功的API文档。


在PayController Class中,同发起支付一样,选择返回ModelAndView模版,完成微信异步通知,也就是完善代码5中的代码。[代码12]

 @PostMapping("/notify")
    public ModelAndView notify(@RequestBody String notifyData) {
        payService.notify(notifyData);
        // 返回给微信处理结果
        return new ModelAndView("pay/success");
    }

到这里,微信公众号支付的流程就全部结束了。

代码:JsonUtil

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonUtil {
    public static String toJson(Object object) {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setPrettyPrinting();
        Gson gson = gsonBuilder.create();
        return gson.toJson(object);
    }
}

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