def test_withdraw(self): user_old_cash = self.fund.cash d = to_decimal("1.12") transfer = do_withdraw( self.shop_user, d, order_id="test_order_id", note="test_withdraw" ) self.assertEqual(transfer.amount, d) self.assertEqual(transfer.order_id, "test_order_id") self.assertEqual(transfer.note, "test_withdraw") self.fund.refresh_from_db() self.assertEqual(self.fund.cash, user_old_cash - d) # test insufficient cash delta = self.fund.cash + to_decimal("0.1") with self.assertRaises(exceptions.NotEnoughBalance): do_withdraw( self.shop_user, delta, order_id="test_order_id2", note="test_withdraw" ) user_old_cash = self.fund.cash self.fund.refresh_from_db() self.assertEquals(self.fund.cash, user_old_cash)
def test_transfer(self): old_amount = self.fund.amount_d old_amount2 = self.fund2.amount_d delta = to_decimal("2.3") transfer = do_transfer( self.shop_user, self.shop_user2, delta, note="test transfer" ) self.fund.refresh_from_db() self.fund2.refresh_from_db() self.assertEquals(self.fund.total, old_amount["total"] - delta) self.assertEquals(self.fund.hold, old_amount["hold"] - delta) self.assertEquals(self.fund.cash, old_amount["cash"]) self.assertEquals(self.fund2.total, old_amount2["total"] + delta) self.assertEquals(self.fund2.cash, old_amount2["cash"] + delta) self.assertEquals(self.fund2.hold, old_amount2["hold"]) self.assertEquals(transfer.note, "test transfer") # test insufficient cash old_amount = self.fund.amount_d old_amount2 = self.fund2.amount_d delta = self.fund.total + to_decimal("0.1") with self.assertRaises(exceptions.NotEnoughBalance): do_transfer(self.shop_user, self.shop_user2, delta, note="test transfer2") self.fund.refresh_from_db() self.fund2.refresh_from_db() self.assertEquals(self.fund.amount_d, old_amount) self.assertEquals(self.fund2.amount_d, old_amount2)
def test_order_update_deposit(self): csettings = CashBackSettings() csettings.threshold = "1" minimal = self.minimal_example order = self.app.pay.create_order(self.wechat_user, self.request, **minimal) result = self.success(self.app.pay, order) old_hold = self.fund.hold order.update(result) # test deposit new_fund = Fund.objects.get(id=self.fund.id) self.assertEqual(self.fund.cash + to_decimal("1.01"), new_fund.cash) self.assertEqual(old_hold + to_decimal("1.01"), new_fund.hold) self.assertEqual(self.fund.total + to_decimal("2.02"), new_fund.total) transfer = FundTransfer.objects.get( to_fund=self.fund, order_id=order.id, type="DEPOSIT" ) self.assertEqual(transfer.note, f"user:{self.shop_user.id} deposit") # test cashback cashback_transfer = FundTransfer.objects.get( to_fund=self.fund, order_id=order.id, type="CASHBACK" ) self.assertEqual(cashback_transfer.amount, to_decimal("1.01")) fund_action = FundAction.objects.get(fund=self.fund, transfer=cashback_transfer) self.assertDictEqual( fund_action.balance, json.loads(json_dumps(new_fund.amount_d)) )
def test_order_update_transfer(self): # test deposit & transfer minimal = self.minimal_example minimal["ext_info"]["to_user_id"] = self.shop_user2.id order = self.app.pay.create_order(self.wechat_user, self.request, **minimal) result = self.success(self.app.pay, order) order.update(result) new_fund = Fund.objects.get(id=self.fund.id) new_fund2 = Fund.objects.get(id=self.fund2.id) self.assertEqual(self.fund.cash, new_fund.cash) self.assertEqual(self.fund2.cash + to_decimal("1.01"), new_fund2.cash) transfer = FundTransfer.objects.get( to_fund=self.fund, order_id=order.id, type="DEPOSIT" ) self.assertEqual(transfer.note, f"deposit&buy") self.assertEqual(transfer.amount, to_decimal("1.01")) transfer = FundTransfer.objects.get( from_fund=self.fund, to_fund=self.fund2, order_id=order.id, type="TRANSFER" ) self.assertEqual(transfer.note, f"deposit&buy") self.assertEqual(transfer.amount, to_decimal("1.01"))
def test_transfer_api(self): self.client.authenticate(self.user) gql = """ mutation _($input: TransferInput!){ transfer(input: $input){ success } }""" test_request_uuid = uuid.uuid4().hex variables = { "input": { "to": str(self.shop_user2.uuid), "amount": "0.1", "note": "test transfer", "requestId": test_request_uuid, "paymentPassword": "******", } } # test with not set payment password data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEquals("need_set_payment_password", data.errors[0].message) # test resbumitted data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEquals("resubmitted", data.errors[0].message) self.shop_user.set_payment_password("654321") # test wrong paymentpassword variables["input"]["paymentPassword"] = "******" variables["input"]["requestId"] = uuid.uuid4().hex data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEquals("wrong_password", data.errors[0].message) # test true paymentpassword variables["input"]["paymentPassword"] = "******" variables["input"]["requestId"] = uuid.uuid4().hex old_amount = self.fund.amount_d old_amount2 = self.fund2.amount_d data = self.client.execute(gql, variables) self.assertIsNone(data.errors) self.fund2.refresh_from_db() self.fund.refresh_from_db() self.assertEquals(self.fund.total, old_amount["total"] - to_decimal("0.1")) self.assertEquals(self.fund2.cash, old_amount2["cash"] + to_decimal("0.1"))
def get_quota(self, name: str, default=0) -> Decimal: try: sq = self.get(name=name) return sq.quota except self.model.DoesNotExist: pass return to_decimal(default)
def expired_days(self) -> int: r = SystemQuota.objects.get_quota( self.CASHBACK_EXPIRED_DAYS, default=to_decimal("365") ) if r: return int(r) else: return 0
def order_updated(result, order, state, attach, **kwargs): # TODO: how to unify wechat, alipay? if state != UnifiedOrderResult.State.SUCCESS: logger.info(f"{order} deposit signal, skip for no-success state") return if FundTransfer.objects.filter(order_id=order.id).exists(): logger.info(f"{order} deposit signal, skip for duplicate trigger") return con = get_redis_connection() lock_name = f"order:{order.id}_update_signal" provider = order.ext_info["provider"] user = ShopUser.objects.get_user_by_openid(provider, order.openid) amount = to_decimal(order.total_fee / 100) is_transfer = bool(order.ext_info and order.ext_info.get("to_user_id")) if is_transfer: to_user_id = order.ext_info["to_user_id"] to_user = ShopUser.objects.get(id=to_user_id) note = "deposit&buy" else: note = f"user:{user.id} deposit" # Ensure cocurrent callback in 10 seconds with con.lock(lock_name, timeout=10): with transaction.atomic(): if is_transfer: do_deposit(user, amount, order_id=order.id, note=note) do_transfer(user, to_user, amount, order_id=order.id, note=note) else: do_deposit(user, amount, order_id=order.id, note=note) do_cash_back(user, amount, order_id=order.id, note=note) logger.info(f"{order} deposit success: {note}")
def test_withdraw_api(self): self.client.authenticate(self.user) gql = """ mutation _($input: WithdrawInput!){ withdraw(input: $input){ success } }""" test_request_uuid = uuid.uuid4().hex variables = { "input": { "amount": decimal2str(to_decimal("0.1") + self.fund.cash), "requestId": test_request_uuid, "provider": "WECHAT", } } # test balance not enough data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEquals("not_enough_balance", data.errors[0].message) # test resbumitted data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEquals("resubmitted", data.errors[0].message) variables["input"]["amount"] = "0.1" variables["input"]["requestId"] = uuid.uuid4().hex old_fund_cash = self.fund.cash # test fail and revert def withdraw_fail(*args, **kw): raise exceptions.WithdrawError("fail") with patch.object(WeChatProvider, "withdraw") as mock_withdraw: mock_withdraw.side_effect = withdraw_fail data = self.client.execute(gql, variables) self.assertIsNotNone(data.errors) self.assertEqual("fail", data.errors[0].message) self.fund.refresh_from_db() self.assertEqual(old_fund_cash, self.fund.cash) # test withdraw success variables["input"]["requestId"] = uuid.uuid4().hex mocked_result = { "mch_appid": "wx478898d89cf437dc", "mchid": "1231736602", "nonce_str": "pslL019BgHIzSDqfv8FUy6EC32OtZauK", "partner_trade_no": "1231736602201910260304298054", "payment_no": "10100101184011910260023619583860", "payment_time": "2019-10-26 11:04:32", "result_code": "SUCCESS", "return_code": "SUCCESS", "return_msg": None, } with patch.object(WeChatProvider, "withdraw") as mock_withdraw: mock_withdraw.return_value = mocked_result data = self.client.execute(gql, variables) self.assertIsNone(data.errors) self.fund.refresh_from_db() self.assertEqual(old_fund_cash - to_decimal("0.1"), self.fund.cash) self.assertTrue(data.data["withdraw"]["success"])
def test_fund_api(self): self.client.authenticate(self.user) gql = """ query { fund{ total cash hold currency } }""" data = self.client.execute(gql) self.assertIsNone(data.errors) expected = { "total": decimal2str(self.fund.cash + self.hold_fund.amount), "cash": decimal2str(self.fund.cash), "hold": decimal2str(self.fund.hold), "currency": "CNY", } self.assertDictEqual(ordered_dict_2_dict(data.data["fund"]), expected) new_add_cash = to_decimal("1.2") transfer = do_deposit( self.shop_user, new_add_cash, order_id="1", note="test deposit" ) old_cash = self.fund.cash self.fund.refresh_from_db() self.assertEquals(old_cash + new_add_cash, self.fund.cash) data = self.client.execute(gql) self.assertIsNone(data.errors) expected = { "total": decimal2str(self.fund.cash + self.hold_fund.amount), "cash": decimal2str(self.fund.cash), "hold": decimal2str(self.fund.hold), "currency": "CNY", } self.assertDictEqual(ordered_dict_2_dict(data.data["fund"]), expected) gql = """ query _($before: String) { ledgerList(before: $before){ edges{ node{ id type amount note status orderId createdAt } } pageInfo{ startCursor endCursor hasNextPage hasPreviousPage } } }""" variables = {"before": None} data = self.client.execute(gql, variables) self.assertIsNone(data.errors) expected = { "id": str(transfer.uuid), "type": "DEPOSIT", "amount": decimal2str(new_add_cash), "note": "test deposit", "status": "SUCCESS", "orderId": "1", "createdAt": transfer.created_at.isoformat(), } self.assertDictEqual( ordered_dict_2_dict(data.data["ledgerList"]["edges"][0]["node"]), expected ) page_info = data.data["ledgerList"]["pageInfo"] self.assertIsNotNone(page_info["startCursor"]) self.assertIsNotNone(page_info["endCursor"]) self.assertIsNotNone(page_info["hasNextPage"]) self.assertIsNotNone(page_info["hasPreviousPage"])
def threshold(self): return SystemQuota.objects.get_quota( self.CASHBACK_THRESHOLD, default=to_decimal("1000") )
def expired_days(self, value): return SystemQuota.objects.set_quota( self.CASHBACK_EXPIRED_DAYS, to_decimal(value) )
def threshold(self, value): return SystemQuota.objects.set_quota(self.CASHBACK_THRESHOLD, to_decimal(value))