Files
xin-procurement/tests/Feature/MiniMpPaymentTest.php
T
2026-09-04 21:22:46 +08:00

337 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\BillModel;
use App\Models\PaymentModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Services\WechatPayService;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
/**
* 公众号 H5 JSAPI 在线支付(scene=mp):网页授权 code 换 openid → 公众号 appid 下单 → 调起参数
*
* - 微信公众号维度 openid 与小程序 openid 不同:分场景绑定(store.mp_openid / store.openid 互不覆盖)
* - 微信官方渠道:JSAPI 下单 appid=公众号 appid,调起参数喂 WeixinJSBridge getBrandWCPayRequest
* - 旺铺渠道:sub_appid=公众号 appid、payway_code 取 mp_payway_code(留空回退 payway_code
*/
class MiniMpPaymentTest extends ProcurementTestCase
{
/** 测试专用微信商户 API 私钥(自签名,仅测试使用) */
private const string WXPAY_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChGoUMvCaiaf97\nXcUExEA3VP4FVwUCPQf/GPvm2cjHZACcPj65rjKNs5nJ0xaKELrJkUCZ3+N5YJJ4\nVGBBhh9GXekrzFckeqUn1sRS9FFbWfNeqAvz3qgps+ohnriPMkTRTw/SONTVRuNV\nesKz8FTYbT5Vl6jSHv71CMMcm6qrhvZ9av1nB03MrUN06v2OMhfXrZTBV5KI5zvI\nOwxetOz0MTAHcfRyrW8cejiJo3baRsRRHXsnDRB7p4H1KIPx+N7yTJKv/L2+tgXY\nmhIjKJRC5hDmrLrLOzievqKlBTsBcG41UWzcLEDG96BcR6Le+dzlJIXXi2taoFXG\nFuDUVt/bAgMBAAECggEAEagPv57kt8AuQ/MO+MAZhExIWtrNWGV1Tua0v6RMIQOQ\nyDmk3kIn6ufYNF/HhJzjcTFThzRxKzMDr98aKD7u2+NxqV8yiHOjTi53mIU2YBtc\n/5l4qzhSmIojYEk+U6i1aiCOzKzLMWTHiELKvAsxrAFXvOxrkDvdbYrm92ey2Nbm\nnvFTzTlPNEVf8HwKIFpbbV/r5RlrqDOqj8np6t5X+PlpL4Kx+oO8XVaaWjjb+2NB\nPX7laZNLa2oJ+sY4qWnwPyCeZQu2glPtBjJQRWX8+eP5p7xMMWylosL056LHasb2\nNC+KDjXSA/4O19NhBwsX8Q7QDbuJrVt/2WMboJxfYQKBgQDU7U64gzdTFezTgJ18\nb4hXqvVyzksireABV2BfKiah25yMH5s4YtgFneusn0flh5yKOh1phbn9EmMGAnob\nyHYfcyiFFR1i+4j3ELblKTkbXeaMIthTqL9QRULc3HwVjKINs3tUAlpSHqdFrzAT\nQb8JcgHfmnDrqSNCNXLJY1cgLwKBgQDBsXj7PDX+URT4m7kMWD4GzTlAbAotLBO5\nGxugKAxczGbAk3G+xmYbwgbuKUHTzBAXX0atfCMjbK9DdowRlyVbI948o8drXqz2\nR2l6u3VyV0ERP+9BV0xfLNbiQGWiPZiYR9hYg+ewbzCmsQEyAUJGisxPZZtNz598\nmqhhQLeEFQKBgQCqTCJp8IiPKzn+7x8GJy4k79bfu57cXbSLXhb2BgBf9AWBDMZY\nkrWzyFp19e7K5WuOImzjuNDIV6xbYh/HmMzg2nnN9tVKFWO2NugQ4KeL+84oxrW+\nM3jP+pU/kBiuI4x46NP6nOcgRuQCF9ubizn/k+9rp1opIV3R1m24JHvKgQKBgFUd\nZtuIHvXtrXh9/bg2ArO8dR8hGuu0xcn+5onfb2dMDw8q+73oszAZeDAqAxpOPvf2\nTzmnJk3H0dCkhHKqZ6kAPwWItvYwuXLT+L8NJ1QikZ5B6SJeeVoNezQbNk4wISEx\n2Rk0hZibk9Z8S9ksgnI7RgLR1IhB54S69ake5kXtAoGANB/pN7MBP9ZeUBKq6eoB\nTHcHZjcD8ZRC/4rsgDcwpHd67xWloALc7FXA1gQfX2VcwFbtf4pl6umQd8kmsmEN\n0AJJ+riyMMrPcHZV0PCo9Kq4ex+6bLFENdRbjUBNk2nrQ/le8BxZLNi0QZPZTOLW\n+XXHpYd3dKJ/BJA/RfiP7sg=\n-----END PRIVATE KEY-----";
/** 测试专用微信商户 API 证书(自签名 X509,请求签名序列号取自证书) */
private const string WXPAY_CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIC4TCCAcmgAwIBAgIUIDIEwVtecMJKfY0NCx1l8Fwv7LowDQYJKoZIhvcNAQEL\nBQAwADAeFw0yNjA5MDExMzM5NThaFw0zNjA4MjkxMzM5NThaMAAwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChGoUMvCaiaf97XcUExEA3VP4FVwUCPQf/\nGPvm2cjHZACcPj65rjKNs5nJ0xaKELrJkUCZ3+N5YJJ4VGBBhh9GXekrzFckeqUn\n1sRS9FFbWfNeqAvz3qgps+ohnriPMkTRTw/SONTVRuNVesKz8FTYbT5Vl6jSHv71\nCMMcm6qrhvZ9av1nB03MrUN06v2OMhfXrZTBV5KI5zvIOwxetOz0MTAHcfRyrW8c\nejiJo3baRsRRHXsnDRB7p4H1KIPx+N7yTJKv/L2+tgXYmhIjKJRC5hDmrLrLOzie\nvqKlBTsBcG41UWzcLEDG96BcR6Le+dzlJIXXi2taoFXGFuDUVt/bAgMBAAGjUzBR\nMB0GA1UdDgQWBBQ2SD9beEd/0EU6ouWHHSW6Pdh8OjAfBgNVHSMEGDAWgBQ2SD9b\neEd/0EU6ouWHHSW6Pdh8OjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA\nA4IBAQByZckpIfdi5BeWwX4FZK9D5+78hZpOpDi+a8rxyd22BYYbkft5WfWnTjWh\nuXZgwOBPoYwmQ9FtC8rIeXWCadcp/ohGCkVVm9Y2CtkutrTC+uxdl4LnDPRQjXPL\nuRubAzvv3leRrphd1dOmAku32TfE0bK6Xul6pCnQaEgIlFp8zp8UkCrYcm9SPNye\nRt/1vt5iBPB6UiZ9oQL1IDUdqJV2Hl9snzHi0AzuZHy+ZMZY6WEY2lBohN8wvfak\nqrejyeA3nY7+a+QRPCg9fAOhxJlIj/eJXJXUUgJTiwTDHN16JQYcqgX5/AdrqnNj\ndkhBOR8mPPmE9hCoXwcDt017y7YV\n-----END CERTIFICATE-----";
/** 测试专用微信 APIv3 密钥(32 字节) */
private const string WXPAY_SECRET_KEY = 'testSecr3tKey0123456789abcdef01';
/** 测试专用旺铺 RSA 密钥对(仅测试使用,公私钥同源便于加解密回环) */
private const string WANGPU_PUBLIC_KEY = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZ4z7UatOnbBolcpFhW2q585II7atHK9w/opQ/k8QUqjwkwIVNYzC9qf/SFDoOnMBufZwS2tM8UUPcgNoy0hbmR5QqBsp9ugBaSL1ZHb5jStgpikzWEjFkyVR5WLtxl4nxKv0R5pV/mfq0RXsIpbutZqugnXRvHPYaqbKomXzf0FKlxmbpvPOXsH9L9rYrnOSKQ1t16vAa6UrqUdkgdQBH7+hI4kiDwY0MIZVOhDhB4r0ODrpWMd0U3yrbhwpHZdtF2B3dtn3JSS92F16Yo7Hec8+TUzNKX0Mu1rn07F6yGZeUf+5/egXsBc2Mx1TgZswFlvkmHS96kbH9gqJyGOVwIDAQAB';
private const string WANGPU_PRIVATE_KEY = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpnjPtRq06dsGiVykWFbarnzkgjtq0cr3D+ilD+TxBSqPCTAhU1jML2p/9IUOg6cwG59nBLa0zxRQ9yA2jLSFuZHlCoGyn26AFpIvVkdvmNK2CmKTNYSMWTJVHlYu3GXifEq/RHmlX+Z+rRFewilu61mq6CddG8c9hqpsqiZfN/QUqXGZum885ewf0v2tiuc5IpDW3Xq8BrpSupR2SB1AEfv6EjiSIPBjQwhlU6EOEHivQ4OulYx3RTfKtuHCkdl20XYHd22fclJL3YXXpijsd5zz5NTM0pfQy7WufTsXrIZl5R/7n96BewFzYzHVOBmzAWW+SYdL3qRsf2ConIY5XAgMBAAECggEAANg+h97uaNFYF9c5XjNeLYU0ULqkeD6SKAApIpgTY+6w6FkBoWIuT1y+q0jNYqKpwN+Ds2+vfizT7GX5Bz1iLoN8etHawBeLCmIus83A75VeAr20gOyy6pWv3Qlty1yvMsczEydqsL3vA0+gdQoB2RW+ib4uKmxl66WNR+xQE0DaGp3GGe117l4PrhqZWsMMugQ2sLJy6ZZ/L6+ILnTPYwz+6NdOor8owoSEYHpKJ03wTcDGdMtRkzanEtfBNklHolymof+aDwXeugw5teGm+/AtaTf8dINeUIGp6AFrUa+g78wyO/piUf+inLNa+e6vgGqck+ErsbRPb6VFQFBsQQKBgQDqkgfXGETdBtE5g9QiV2aY+DDHzcmQZBLgRGDoHFhn2SgRdG2E66Xq6uGEnwIpqLuKsCYe4t2JvNjSVFZ14Ul8DOow6NQfWLPxU6YT3fShAh/gTMgccKb7/Pe3fQqwl+6aEV3oMOKNFErKSGrkSptLh9Nf/ii9khpMFhUARErgjQKBgQC5HRwRb45CIkEeS2Q1Gz2xS4ezXACw9P0h4Qp5bMQmqgn/fjN+t4HJRlHCuDfLTnHZ48Vpihwu7BU1RmK1ozXJswDzQXCYE4ejEXTGVoUNxCHht4/VlAbBVbcnOsIREgvtZRIOHvPaMqUQqeSX9e3HcEAHfIaJRkVAhalQnKMrcwKBgDUtH7viU5Irvnikaw3R9H9PHHffLgeeuCzBM5rK+juong2+8CkG5tknoDJZfbsF9mYNYsbztTdJaXndBrC4ftkxcFHgJl5o1Hor9WVhlth9S86keWUBIMnVYi7lmOvJtZyVvU0q7+D9rarH2fug8i2gQAnt6zx2h6GiC+bAlJztAoGBAI/YxgnqhUJw+ec/sKPwAjW2usGu2b6o8deU153Z3mcpNVG70OpEUW+F3F0S6BBtad1muO41a4cu36AhjO0W4eJV3oQpMwSKEJmwI+1IKGa1JZsQGI5gVAuCvyuV5l57hpc4NhqRBO9m8YwMaV2ItviHCsqGgslDuObVtue0gLtvAoGAWSPjf+POrVy2VJ0yY9zJ6Ks1oGLYzejUTx6Ue/DmjZcOcn6IBGqvgj10D5Gu9vX847GiC8D632A3US4FMLJZki0IxEYWME3d3pg0vhqS24YxZwIo97JvxnrHOqMvd486K+Cyvlft8xgxlZdL79Ez5eec4Lvw7OoQaaoxcdDvf08=';
protected function setUp(): void
{
parent::setUp();
config([
'services.wechat.mp.appid' => 'wx-mp-test',
'services.wechat.mp.secret' => 'wx-mp-secret',
]);
}
/** 模拟公众号网页授权 code 换 openid 成功 */
private function fakeMpOauth(string $openid = 'oMpOpenid001'): void
{
Http::fake([
'https://api.weixin.qq.com/*' => Http::response([
'access_token' => 'mp-access-token',
'expires_in' => 7200,
'refresh_token' => 'mp-refresh-token',
'openid' => $openid,
'scope' => 'snsapi_base',
], 200),
]);
}
/** 以 MockHttpClient 替换微信支付接口应答,并注入容器(控制器经由容器取同一实例) */
private function fakeWechatApi(callable $callback): void
{
$service = app(WechatPayService::class);
$service->app()->setHttpClient(new MockHttpClient($callback, 'https://api.mch.weixin.qq.com'));
$this->app->instance(WechatPayService::class, $service);
}
/** 密钥 PEM 包装(与服务内实现一致) */
private function pem(string $body, string $kind): string
{
return "-----BEGIN {$kind} KEY-----\n" . wordwrap($body, 64, "\n", true) . "\n-----END {$kind} KEY-----";
}
/**
* 模拟网关加密信封(demo 协议:AES-128-ECB 加密报文 + RSA 公钥加密 AES 密钥)
*
* @param array<string, mixed> $data 业务报文
* @return array<string, string>
*/
private function gatewayEnvelope(array $data): array
{
$key = Str::random(16);
openssl_public_encrypt($key, $encryptedKey, $this->pem(self::WANGPU_PUBLIC_KEY, 'PUBLIC'));
return [
'serialNo' => Str::random(32),
'version' => '1.0',
'timestamp' => now()->format('YmdHis'),
'data' => base64_encode((string) openssl_encrypt((string) json_encode($data), 'AES-128-ECB', $key, OPENSSL_RAW_DATA)),
'signature' => base64_encode($encryptedKey),
'extras' => '',
'organizNo' => 'org001',
];
}
/**
* 解密我方发往网关的请求信封(断言上送报文用;服务侧 urlencode 需先解码)
*
* @param array<string, mixed> $body 请求信封
* @return array<string, mixed>
*/
private function decryptRequest(array $body): array
{
openssl_private_decrypt(
(string) base64_decode(urldecode((string) $body['signature'])),
$key,
$this->pem(self::WANGPU_PRIVATE_KEY, 'PRIVATE')
);
$plain = openssl_decrypt(
(string) base64_decode(urldecode((string) $body['data'])),
'AES-128-ECB',
(string) $key,
OPENSSL_RAW_DATA
);
return (array) json_decode((string) $plain, true);
}
/** 旺铺渠道测试配置 */
private function configWangpu(): void
{
config([
'services.wangpu.base_url' => 'https://wangpu.test',
'services.wangpu.organiz_no' => 'org001',
'services.wangpu.mer_no' => 'mer001',
'services.wangpu.mer_code' => 'code001',
'services.wangpu.term_code' => 'term001',
'services.wangpu.public_key' => self::WANGPU_PUBLIC_KEY,
'services.wangpu.private_key' => self::WANGPU_PRIVATE_KEY,
'services.wangpu.payway_code' => 'WECHAT_MINI',
'services.wangpu.mp_payway_code' => 'WECHAT_JSPAY',
]);
}
/** 造一张指定金额的未支付账单(总额=商品金额) */
private function makeBill(StoreModel $store, string $amount, array $attributes = []): BillModel
{
return BillModel::create(array_merge([
'bill_no' => 'ZD' . random_int(100000000000, 999999999999),
'purchase_id' => PurchaseOrderModel::factory()->create()->id,
'store_id' => $store->id,
'bill_date' => '2026-09-04',
'product_amount' => $amount,
'delivery_fee' => '0.00',
'box_num' => 0,
'tray_num' => 0,
'box_price' => '0.00',
'tray_price' => '0.00',
'added_amount' => '0.00',
'total_amount' => $amount,
'status' => BillModel::STATUS_UNPAID,
], $attributes));
}
/** 微信官方渠道公众号下单:JSAPI appid=公众号、付款人=公众号 openid、调起参数同构、mp_openid 绑定且不覆盖小程序 openid */
public function test_mp_scene_wechat_channel(): void
{
config([
'services.pay.online_channel' => 'wechat',
'services.wxpay.mch_id' => '1630000001',
'services.wxpay.private_key' => self::WXPAY_PRIVATE_KEY,
'services.wxpay.certificate' => self::WXPAY_CERTIFICATE,
'services.wxpay.secret_key' => self::WXPAY_SECRET_KEY,
]);
$this->fakeMpOauth();
$captured = [];
$this->fakeWechatApi(function (string $method, string $url, array $options) use (&$captured) {
$captured = compact('method', 'url', 'options');
return new MockResponse((string) json_encode(['prepay_id' => 'wx-mp-prepay-001']), ['http_code' => 200]);
});
$store = StoreModel::factory()->create(['openid' => 'oMiniOpenidKeep']);
$bill = $this->makeBill($store, '66.00');
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/payment/online', [
'bill_ids' => [$bill->id],
'code' => 'mp-oauth-code',
'scene' => 'mp',
])->assertJsonPath('success', true);
// 调起参数:appId=公众号 appidpackage 含 prepay_idH5 喂 WeixinJSBridge getBrandWCPayRequest
$payParams = $response->json('data.pay_params');
$this->assertSame('wx-mp-test', $payParams['appId']);
$this->assertSame('prepay_id=wx-mp-prepay-001', $payParams['package']);
$this->assertNotEmpty($payParams['timeStamp']);
$this->assertNotEmpty($payParams['nonceStr']);
$this->assertSame('RSA', $payParams['signType']);
$this->assertNotEmpty($payParams['paySign']);
// 上送微信报文:appid=公众号 appid、payer=公众号 openid、金额元转分
$body = (array) json_decode((string) ($captured['options']['body'] ?? ''), true);
$this->assertSame('wx-mp-test', $body['appid'] ?? null);
$this->assertSame('1630000001', $body['mchid'] ?? null);
$this->assertSame('oMpOpenid001', $body['payer']['openid'] ?? null);
$this->assertSame(6600, $body['amount']['total'] ?? null);
// 支付单与门店绑定:公众号 openid 写 mp_openid,不覆盖小程序 openid
$payment = PaymentModel::where('payment_no', $response->json('data.payment_no'))->first();
$this->assertSame('oMpOpenid001', $payment->openid);
$store->refresh();
$this->assertSame('oMpOpenid001', $store->mp_openid);
$this->assertSame('oMiniOpenidKeep', $store->openid, '公众号场景不得覆盖小程序 openid');
}
/** 旺铺渠道公众号下单:sub_appid=公众号 appid、payway_code=公众号支付方式代码、open_id=公众号 openid */
public function test_mp_scene_wangpu_channel(): void
{
$this->configWangpu();
$this->fakeMpOauth();
Http::fake([
'https://api.weixin.qq.com/*' => Http::response(['openid' => 'oMpOpenid001', 'access_token' => 'at'], 200),
'https://wangpu.test/industrial/payment/order' => Http::response(
['code' => '0000', 'msg' => '调用成功'] + $this->gatewayEnvelope([
'order_id' => 'WP202609040001',
'tradeNo' => 'T20260904001',
'user_openid' => 'oMpOpenid001',
]),
200
),
]);
$store = StoreModel::factory()->create();
$bill = $this->makeBill($store, '88.00');
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/payment/online', [
'bill_ids' => [$bill->id],
'code' => 'mp-oauth-code',
'scene' => 'mp',
])->assertJsonPath('success', true);
$paymentNo = $response->json('data.payment_no');
$this->assertSame('oMpOpenid001', $store->fresh()->mp_openid);
// 上送网关的加密信封:解出业务报文校验公众号 sub_appid / 公众号 payway_code / 公众号 openid
Http::assertSent(function ($request) use ($paymentNo) {
if (! str_contains($request->url(), '/industrial/payment/order')) {
return false;
}
$plain = $this->decryptRequest($request->data());
return ($plain['mer_order_id'] ?? '') === $paymentNo
&& ($plain['order_amt'] ?? '') === '88.00'
&& ($plain['open_id'] ?? '') === 'oMpOpenid001'
&& ($plain['sub_appid'] ?? '') === 'wx-mp-test'
&& ($plain['payway_code'] ?? '') === 'WECHAT_JSPAY'
&& ! empty($plain['notifyurl']);
});
}
/** 公众号网页授权失败:errcode 应答 → 下单拒绝并提示授权失败 */
public function test_mp_scene_oauth_failure(): void
{
Http::fake([
'https://api.weixin.qq.com/*' => Http::response(['errcode' => 40029, 'errmsg' => 'invalid code'], 200),
]);
$store = StoreModel::factory()->create();
$bill = $this->makeBill($store, '10.00');
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/payment/online', [
'bill_ids' => [$bill->id],
'code' => 'bad-code',
'scene' => 'mp',
])->assertJsonPath('success', false);
$this->assertStringContainsString('微信网页授权失败', (string) $response->json('msg'));
// 授权失败不产生支付单、账单不被锁定
$this->assertSame(0, PaymentModel::count());
$this->assertSame(0, $bill->fresh()->payment_id);
}
/** 公众号未配置 AppID/Secret:明确报错 */
public function test_mp_scene_requires_mp_config(): void
{
config([
'services.wechat.mp.appid' => '',
'services.wechat.mp.secret' => '',
]);
$store = StoreModel::factory()->create();
$bill = $this->makeBill($store, '10.00');
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/payment/online', [
'bill_ids' => [$bill->id],
'code' => 'mp-oauth-code',
'scene' => 'mp',
])->assertJsonPath('success', false);
$this->assertStringContainsString('微信公众号未配置', (string) $response->json('msg'));
$this->assertSame(0, PaymentModel::count());
}
/** 不传 scene 默认走小程序链路(code2session,小程序 appid 下单) */
public function test_default_scene_remains_mini(): void
{
config([
'services.wechat.mini.appid' => 'wx-mini-test',
'services.wechat.mini.secret' => 'wx-secret-test',
'services.pay.online_channel' => 'wechat',
'services.wxpay.mch_id' => '1630000001',
'services.wxpay.private_key' => self::WXPAY_PRIVATE_KEY,
'services.wxpay.certificate' => self::WXPAY_CERTIFICATE,
'services.wxpay.secret_key' => self::WXPAY_SECRET_KEY,
]);
Http::fake([
'https://api.weixin.qq.com/*' => Http::response(['openid' => 'oMiniOpenid001', 'session_key' => 'sk'], 200),
]);
$captured = [];
$this->fakeWechatApi(function (string $method, string $url, array $options) use (&$captured) {
$captured = compact('method', 'url', 'options');
return new MockResponse((string) json_encode(['prepay_id' => 'wx-mini-prepay-001']), ['http_code' => 200]);
});
$store = StoreModel::factory()->create();
$bill = $this->makeBill($store, '20.00');
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/payment/online', [
'bill_ids' => [$bill->id],
'code' => 'wx-login-code',
])->assertJsonPath('success', true);
// 小程序链路:appId=小程序 appid、openid 绑定到 store.openidmp_openid 保持空
$this->assertSame('wx-mini-test', $response->json('data.pay_params.appId'));
$body = (array) json_decode((string) ($captured['options']['body'] ?? ''), true);
$this->assertSame('wx-mini-test', $body['appid'] ?? null);
$store->refresh();
$this->assertSame('oMiniOpenid001', $store->openid);
$this->assertSame('', (string) $store->mp_openid);
}
}